您的位置:首页 > 其它

回溯算法详解

2014-04-30 16:43 211 查看
回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。

回溯方法的步骤如下:

1) 定义一个解空间,它包含问题的解。

2) 用适于搜索的方式组织该空间。

3) 用深度优先法搜索该空间,利用限界函数避免移动到不可能产生解的子空间。

以上为框架总结,相信大家随便找都可以找到,现在我们用一个例子来说明回溯的解题过程(含思路和编程步骤)

Generate Parentheses( leetcode上的题目):

Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.

For example, given n = 3, a solution set is:

"((()))", "(()())", "(())()", "()(())", "()()()"

简便起见,我们来看一下n=2时的思路:

这里要先提一下画括号的一个性质1,也就是要先(,才能),也就是说在求解的一步步的路上,左括号的数量一定是大于等于右括号的。根据这个性质以及n=2,我们可以排除很多不可能的解空间。下图的每一行表示第几个字符是左括号还是右括号,X掉的表示不符合要求的解。

好,现在先来看第一个字符,这里可以填(,也可以填),所以解空间可以分成第一行1和2两个节点,但是由于我们性质1,所以2节点是不满足要求的,所以我们就不在2节点的解空间进行。然后我们看第二个字符,这时候3和4节点都满足条件,所以我们就可以分成两个更小的解空间,以此类推,每个子节点都是一个解。这里n=2时的答案就是(())和()()。

如果使用穷举法,那么就需要判断2^2n个解是否正确,如果使用回溯的方法,那么会排除掉大部分的非解空间。



这个例子说明的是回溯这种方法通过构图所得的解,但其实编程的时候不是这样进行的,而是先优先选择左括号,直到得到第一个解,然后往回退到前一个含左括号(的节点,然后改成)后继续按我们规则求解,然后再往回退到更前一个左括号(,再求解。也就是说编程的时候不是同时进行解空间的扩展,而是一个个求解的(如1和2节点不是同时期的,而是1节点的解空间计算完之后退到2节点的)。

以上是解题思路说明,接下来对编程的步骤进行分析:

能用回溯来解决的问题一般有以下几个特点:

1、解是多维特征,并且每一维的取值范围都是一样的且有限(括号题目中的取值就是左括号或者右括号,并且每个节点都是这样的取值,这点是最重要的)

2、一般来说,题目要求可能性解的数量,这样子比较可能采用回溯

对题目进行分析概括之后,符合回溯的就可以进行编程了:

1、包含一个保存解的全局变量(用于保存一个解)

2、状态控制的全局变量(用于对当前步骤的进度进行分析,从而排除一些不可能解)

3、编程的过程采用一步步进行,也就是一维一维的进度知道最后每一维都计算完得到一个解;

4、3得到一个符合解之后,往回退,直到某个节点可以选择其他值,重复步骤3;

class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> result;
num = n;
str.clear();
for(int i = 0; i < n*2; ++i)
{
str.push_back('0');
}
l = 0;
r = 0;
bt(result, 0);

return result;
}
//全局状态控制变量,l和r分别表示已用左右括号的数量;
int num;
int l;
int r;
//解(全局变量);
string str;

//迭代回溯函数;
//第一个参数其实是为了保存每一个解,如果只需要打印出来那就不需要这个参数了;
//k表示当前计算的解的前进进度;
void bt(vector<string> &result, int k)
{
//迭代终点,也就是得到了一个新解;
if(k == num*2)
{
result.push_back(str);
}
//解的计算未到终点,继续进度;
else
{
//先进行尝试性填充左括号来求解;
if(l < num)
{
str[k] = '(';
l++;
bt(result, k+1);
//////!!!这一步是必须的,恢复到上一节点,选择其他,这样子才能达到回溯的目的!;
--l;
}
//填充右括号;
if(r < num && l > r)
{
str[k] = ')';
r++;
bt(result, k+1);
--r;
}//到这里有隐含的排除信息,也就是不符合if内要求的解空间都被排除掉了;
}
}
};


关于回溯还有很多题目,都是类似的,有:8皇后,跳马全棋盘,零钱问题等等。

详情见资源下载:

http://download.csdn.net/detail/tinyway/7287083

如果大家有新的编程思路的话希望能够告诉我,谢谢。

有人说,回溯实际上是递归的展开,但实际上。两者的指导思想并不一致。

打个比方吧,递归法好比是一个军队要通过一个迷宫,到了第一个分岔口,有3条路,将军命令3个小队分别去探哪条路能到出口,3个小队沿着3条路分别前进,各自到达了路上的下一个分岔口,于是小队长再分派人手各自去探路——只要人手足够(对照而言,就是计算机的堆栈足够),最后必将有人找到出口,从这人开始只要层层上报直属领导,最后,将军将得到一条通路。所不同的是,计算机的递归法是把这个并行过程串行化了。

而回溯法则是一个人走迷宫的思维模拟——他只能寄希望于自己的记忆力,如果他没有办法在分岔口留下标记(电视里一演到什么迷宫寻宝,总有恶人去改好人的标记)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: