回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。用回溯算法解决这个问题的一般步骤为:
1、定义一个解空间。它包括问题的解。
2、利用适于搜索的方法组织解空间。
3、利用深度优先法搜索解空间。
4、利用限界函数避免移动到不可能产生解的子空间。
问题的解空间一般是在搜索问题的解的过程中动态产生的。这是回溯算法的一个重要特性。
经典样例(N后问题):
要求在一个n*n格的棋盘上放置n个皇后,使得她们彼此不受攻击,即使得不论什么两个皇后不能被放在同一行或同一列或同一斜线上(国际象棋中一个皇后能够攻击与之处在同一行或同一列或同一斜线上的其它不论什么棋子)。
注意一般会多种可行的放置方案。
n皇后问题是NP全然的。用回溯法求解:
(1)定义问题的解空间:用n元组x[1:n]表示它的解。即一个可行的放置方案。当中x[i]表示皇后i放在棋盘的第i行第x[i]列。因为不同意将不论什么两个皇后放在同一列。所以解向量中诸x[i]互不同样。而不论什么两个皇后不能放在同一斜线上是问题的隐约束。在搜索时,可通过这个斜线约束来剪去无效的子树。将棋盘看作是二维方阵,从左上角到右下角的主对角线及其平行线上(即斜率为-1的各斜线),
元素的两个下标值的差值相等。
同理,斜率为+1的每一条斜线上,元素的两个下标值的和相等。
因此,若两个皇后放置位置为(i,j)和(k,l),且i-j=k-l或i+j=k+l,则这两个皇后处于同一斜线上。这里两个等式可统一表示为|i-k|=|j-l|。
(2)确定解空间树的结构:能够用一棵全然n叉树来表示n皇后问题的解空间。
树枝上用放置的列号来标记,从树根到任一叶子结点的路径都是一种放置方案。这个全然n叉树共同拥有n**n(即n的n次方)个叶子结点,因此遍历解空间树的算法须要O(n**n)的时间复杂度。
(3)以深度优先方式搜索整个解空间,找出所要的解:约束函数为Plack(k)。它用来測试将皇后k放在x[k]列是否与前面已放置的k-1个皇后都不在同一列。且都不在同一斜线上。在回溯函数中。若搜索到达叶子结点(i>n)。则得到一个新的n皇后互不攻击的可行方案,可行方案数加1。
若还未到达叶子结点(i<=n),因为是n叉树。当前扩展结点有n个放置列号x[i]=1,2,...,n所表示的n个儿子结点,对皇后i的每一种列号放置情况,用结束函数Place检查其可行性,若可行则进入这个子树进行递归搜索,否则剪去这个子树。
算法实现例如以下:
//问题及其解空间描写叙述 class Queen{ private: friend int nQueen(int); //算法实现函数 bool Place(int k); //约束函数 void Backtrack(int i); //回溯搜索函数 int n; //皇后个数 int* x; //当前解 long sum; //当前已找到的可行方案数 }; //约束函数 bool Queen::Place(int k){ for(int j=1;j<k;j++) //推断若将皇后k放在(k,x[k])处,与前面的每个皇后(j,x[j])是否都不在同一列,且都不在同一斜线上 if( (x[j]==x[k]) || (abs(k-j)==abs(x[j]-x[k])) ) return false; return true; } void Queen::Backtrack(int i){ if(i>n) //搜索到达叶子结点,得到一个新的可行的放置方案 sum++; else //否则没达到叶子结点。当前扩展结点有n个列号x[i]=1,2,...,n表示的n个儿子结点 for(int j=1;j<=n;j++){ x[i]=j; //将皇后i放置在第j列 if(Place(i)) //若这个放置可行,则进入这个子树进行递归搜索 Backtrack(i+1); } } //算法实现函数:返回可行的放置方案个数,这里数组x的长度应该为n+1。 //x[1]~x[n]存放了当中的一个可行放置方案 int nQueen(int* x,int n){ Queen alg; alg.n=n; alg.sum=0; for(int i=1;i<=n;i++) //初始化各皇后的放置情况 alg.x[i]=0; alg.Backtrack(1); //搜索整个解空间树 delete[] alg.x; return alg.sum; }
由于解空间树有n**n(即n的n次方)个叶子结点,因此算法的时间复杂度为O(n**n),非常耗时。回溯法有通用解题法之称。由于它採用的是搜索整个解空间的策略。对一些无从下手的、组合数较大的问题(特别是非常多NP全然问题),回溯法是一个有力的工具。回溯法对解空间进行了有效的组织(组织成树或图的结构)。还能够用剪枝函数来提高平均搜索效率,因此它的效率大大高于纯粹的穷举法。
小结:
考试尽管已经过去。偶尔回首看看,还是有非常多收获的 。 回顾着写这些博客。可能有错误,也可能有一些理解不到的地方。如有问题。欢迎指正!