算法设计与分析——回溯法

目录

1.回溯法基本思想

2.回溯法的算法框架

2.1问题的解空间

2.2剪枝函数的分类和设计

2.3回溯法的求解过程

2.4回溯法的时间复杂性


1.回溯法基本思想

回溯法是一种用来寻找问题所有解的通用算法。其思想为:能进则进,进不了退,换条路再试。

回溯法步骤如下:

1)针对所给问题,定义问题的解空间。

2)确定易于搜索的解空间结构。

3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。(通常采用两种方式避免无效搜索:1.用约束条件减去得不到可行解的子树。2.用目标函数减去得不到最优解的子树。

2.回溯法的算法框架

2.1问题的解空间

解空间是进行穷举的搜索空间,所以解空间包含了所有的可能解。

确定正确的解空间很重要,如果没有确定正确的解空间就开始进行搜索,可能会造成增加很多重复解,或者根本搜索不到正确的解。

解空间的组织结构一般是由树或图形式表示,常用的典型解空间树是子集树排列树。

子集树:当所给的问题是从n个元素的集合S中找到S满足某种性质的子集时,相对应的解空间树称为子集树。通常有2^{x}个叶子结点。例如:n个物品的0-1背包问题所对应的解空间树是子集树

排列树:当所给的问题是确定n个元素满足某种性质的排列时,相对应的解空间树称为排列树。通常有n!个叶子结点。

确定好问题的解空间后,可用不同的剪枝函数和最优解表示方法来获得最终结果。

2.2剪枝函数的分类和设计

1.可行性剪枝。

作用:当前路径不满足问题的约束条件时,终止分支。

应用场景:约束满足问题(如N皇后,数独),组合优化问题。

示例:

N皇后问题:当前皇后的位置与之前皇后冲突(同行/同列/对角线)时,剪枝。

子集和问题:当前子集的和已超过目标值,剪枝。

2.最优性剪枝

作用:前路径无法得到比已知最优解更好的解时,终止分支。

应用场景:最优化问题(如TSP,0-1背包)

示例:

旅行商问题(TSP):当前路径长度已超过已知最短路径,剪枝。

0-1背包问题:当前物品总价值 + 剩余物品的最大可能价值 ≤ 已知最优价值,剪枝。

3.去重剪枝

作用:免生成重复解(尤其在解空间含重复元素时)。

应用场景:全排列II、组合总和II。

示例:

全排列II:排序后跳过 nums[i] == nums[i-1] 且 nums[i-1] 未被使用的分支。

2.3回溯法的求解过程

回溯法对解空间做深度优先搜索,一般情况下可以用递归函数实现回溯法。

递归函数模板如下:

void BackTrace(int t)
{
    if(t > n)
        Output(x);
    else
        for(int i = f(n,t); i <= g(n,t); i++)
        {
            x[t] = h(i);
            if(Constraint(t) && Bound(t))
                BackTrace(t+1);
        }
}
        

解释模板

1.函数定义和参数

void BackTrace(int t)

表示当前递归层级 

例如在全排列问题中,t可能表示正在填充第t个位置的数字

2.终止条件

if(t > n)
    Output(x);

代表问题规模(如数组长度,皇后数量等)

t > n 代表已经处理完所有层级,此时输出可行解。

Output(x):自定义函数,输出或保存当前解(如打印排列、记录路径等)

3.递归主体

for(int i = f(n,t); i <= g(n,t); i++)

f(n,t) 和 g(n,t):动态生成当前层级 t 的候选集范围。

作用:灵活控制每层的可选值,避免无效搜索。

4.赋值与约束检查

 x[t] = h(i);
 if(Constraint(t) && Bound(t))
     BackTrace(t+1);

x[t] = h(i):将候选值 i 赋给解向量的第 t 个位置。

h(i):可能对 i 做转换(例如映射索引到实际值)。

Constraint(t) :检查当前解向量 x[1..t] 是否满足问题的 显式约束

Bound(t) :检查当前解向量是否有潜力达到 最优解(用于剪枝)。

若约束和边界条件均满足,则递归进入下一层 t+1。

使用迭代法也可以实现回溯法。模板如下:

void IterativeBackTrace(void)
{
    int t = 1;
    while(t > 0)
    {
        if(f(n,t) <= g(n,t))
        for(int i = f(n,t); i <= g(n,t); i++)
        {
            x[t] = h(i);
            if(Constraint(t) && Bound(t))
            {
                if(Solution(t))
                    Output(x);
                else
                    t++;
            }
        }
        else 
            t--;
        }
}

Solution(t)判断在当前拓展结点处是否已得到问题的一个可行解。

2.4回溯法的时间复杂性

1.子集树

在子集树中,每个结点都有相同数目的子树,通常为2,因此遍历子集树需要\Omega(2^n)时间。

用回溯法搜索子集树的一般算法:

void backtrack(int t)
{
    if(t > n)
        Output(x);
    else
        for(int i = 0; i <= 1;i++)
        {
            x[t] = i;
            if(legal(t))
            backtrack(t+1);
        }
}

2.排列树

通常第一层结点子树为n,逐层递减,最后一层为1,所以排列树中共有n!个子结点,因此遍历排列树需要\Omega (n!)时间。

用回溯法搜索排列树的一般算法:

void backtrack(int t)
{
    if(t > n)
        output(x);
    else
        for(int i = t; i <= n; i++)
        {
            swap(x[t],x[i]);
            if(legal(t))
            backtrace(t+1);
            swap(x[t],x[i]);
        }
}

你可能感兴趣的:(算法分析与设计,算法,笔记)