回溯法(内容摘自《算法设计技巧与分析》)
基本特征:
1.节点是用深度优先搜索的方法生成的
2.不需要存储整颗搜索树,只需要存储根到当前活动节点的路径
经典回溯问题——3着色问题
问题描述
给出一个无向图 G = (V, E),需要用三种颜色之一为V中的每个顶点着色,三种颜色分别为1,2和3,使得没有两个邻接的顶点有同样的颜色
问题解决
一般回溯问题有两种解法:递归和迭代
算法
递归法
//3-COLORREC
//输入:无向图G= (V, E)
//输出:G的顶点的3着色c[1...n],其中每个c[j]为1,2,3
main(){
for k = 1:n
c[k] = 0
flag = false
graphcolor(1)
if flag
return c
else
return "no solution"
}
graphcolor(k){
for color = 1:3
c[k] = color
if c is legal
flag = true
return
else if c is partially legal
graphcolor(k+1)
}
迭代法
//3-COLORITER
//输入:无向图G= (V, E)
//输出:G的顶点的3着色c[1...n],其中每个c[j]为1,2,3
main(){
for k = 1:n
c[k] = 0
flag = false
k = 1
while k >= 1
while c[k] <= 2
c[k] = c[k] + 1
if c is legal
flag = true
return
else if c is partially legal
k = k + 1
c[k] = 0
k = k - 1
if flag
return c
else
return "no solution"
}
八皇后问题
问题描述
如何在8*8的国际象棋棋盘上安排8个皇后,使得没有两个皇后能互相攻击?如果皇后处在同一行、同一列或同一条对角线上,则她们能互相攻击。
算法(在这里简单期间,讨论4皇后)
//4-QUEENS
//输入:空
//输出:对应于4皇后问题的解的向量层c[1...4]
main(){
for k = 1:4
c[k] = 0
flag = false
k = 1
while k >= 1
while c[k] <= 3
c[k] ++
if c is legal
flag = true
else if c is partially legal
k ++
c[k] = 0
k -- //回溯
if flag
return c
else
return "no solution"
/*
八皇后问题,迭代法;C++源代码:解出共92种放置方法
*/
#include<iostream>
#include<vector>
using namespace std;
int abs(int a, int b){
if (a > b)
return a - b;
else
return b - a;
}
int is_legal(int state[], int row, int n){ //一维数组,存储每行皇后所在列号,0开头
if(state[row] >= n) return 0;
for(int i = 0; i < row; ++ i){
if(state[i] == state[row] || abs(state[row], state[i]) == abs(row, i))
return 0;
}
if(row == n - 1) return 2;//0表示不合法,1表示部分合法,2表示全部合法
return 1;
}
void cout_queen(int state[]){
for(int i = 0; i < 8 ; ++ i){
for (int j = 0; j < state[i]; ++ j)
cout << "- ";
cout << "0 ";
for (int k = state[i] + 1; k < 8; ++ k)
cout << "- ";
cout << endl;
}
}
int queen(int n, int state[]){
int row = 0;
int count = 0;
while(row < n && row >= 0){
//cout << "test"<< endl;
while(state[row] < n){
++ state[row];
int flag = is_legal(state, row, n);
if(flag == 2){
cout << "the " << ++ count << " queen"<<endl;
cout_queen(state);
}
else if(flag == 1){
++ row;
}
}
state[row] = -1;
-- row;
}
return -1;
int main(){
int state[8] = {-1, -1, -1, -1, -1, -1, -1, -1};
queen(8, state);
}
一般的回溯方法
从上面两个例子中,可以很明显看到回溯法的特征,针对着色问题的描述给出抽象特征,来寻求一般回溯问题的解。
对于n个点的着色问题,每个点有m种颜色取值情况。回溯算法则是按照字典序考虑笛卡尔积X1∗X2∗...∗XnX_1*X_2*...*X_nX1∗X2∗...∗Xn中的所有元素。某种特定解法可以用{x1,x2,...xn}\lbrace x_1, x_2, ...x_n\rbrace{x1,x2,...xn}表示,XiX_iXi取值范围{1,2,...m}\lbrace 1, 2, ...m\rbrace{1,2,...m}。
算法从最初空集开始,然后选择X1X_1X1中最小的元素作为x1x_1x1,如果(x1)(x_1)(x1)是一个部分解,算法从X2X_2X2选择最小的元素作为x2x_2x2继续,如果(x1,x2)(x_1, x_2)(x1,x2)是一个部分解,那么继续往(X3)(X_3)(X3)推进,否则(x2)(x_2)(x2)被置为下一个元素。一般地,假定算法已经检测到部分解为(x1,x2,...,xj)(x_1, x_2, ... , x_j)(x1,x2,...,xj),然后再去考虑向量v=(x1,x2,...,xj,xj+1)v = (x_1, x_2, ... , x_j,x_{j+1})v=(x1,x2,...,xj,xj+1),我们有下面的情况。
- 如果v表示问题的最后解,算法记录下它作为一个解,在仅希望获得一个解时终止,或者继续去找出其他解。
- (向前步骤)。如果v表示一个部分解,算法通过选择集合Xj+2X_{j+2}Xj+2中的最小元素向前。
- 如果v既不是最终的解,也不是部分解,则有两种子情况。
(a)如果从集合Xj+1X_{j+1}Xj+1中还有其他的元素可选择,算法将xj+1x_{j+1}xj+1置为Xj+1X_{j+1}Xj+1中的下一个元素
(b)(回溯步骤) 如果从集合Xj+1X_{j+1}Xj+1中没有更多的元素可选择,算法通过将xjx_jxj置为XjX_{j}Xj中的下一个元素回溯;如果从集合XjX_{j}Xj中仍然没有其他的元素可以选择,算法通过将xj−1x_{j-1}xj−1置为Xj−1X_{j-1}Xj−1中的下一个元素回溯,以此类推。
算法
就是迭代和递归,跟上文提到的程序一样
递归法
//BACKTRACKREC
//输入:集合X1,X2,...,Xn的描述
//输出:解向量v = (x1, x2, ..., xi),0 <= i <= n
main(){
v = () //初始化空向量
flag = false
advance(1)
if flag
return c
else
return "no solution"
}
advance(k){
for x in X_k
x_k = x
x_k -> v
if c is legal
flag = true
return
else if c is partially legal
graphcolor(k+1)
}
迭代法
//3-COLORITER
//输入:无向图G= (V, E)
//输出:G的顶点的3着色c[1...n],其中每个c[j]为1,2,3
main(){
v = ()
flag = false
k = 1
while k >= 1
while X_k has left
x_k = X_k.next
x_k -> v
if c is legal
flag = true
return
else if c is partially legal
k = k + 1
reset X_k
k = k - 1
if flag
return c
else
return "no solution"
}
##分支限界法
求和问题
问题描述
给定一个集合X={10,20,30,40,50,60},y=60X = \lbrace10,20,30,40,50,60\rbrace,y = 60X={10,20,30,40,50,60},y=60,找出不同的XXX的子集使得他们的和为yyy,例如{10, 20,30}是其中一个满足条件的子集
解题思维
将每个数据的选择与否看成0,1情况,也就是有262^626种搜索情况,而在这种题设条件下,可以有一个剪枝条件,就是一旦发现总和大于等于yyy,便可以立即停止该节点以后的搜索,我也不知道这种想法算不算分支限界。
程序
//C++ 源码
#include<iostream>
using namespace std;
int judge(int x[], int flag[], int y,int n = 6){
int count = 0;
for(int i = 0; i < n; ++ i){
if(flag[i] == 2)
count += x[i];
else if(flag[i] == 0)
break;
else if(flag[i] == 3)
return 0;//不合法
}
if (count > y) return 0;//不合法
if(count < y) return 1;//部分合法
if (count == y) return 2;//合法,且此时后面数据无需再次搜索
}
void cout_output(int x[], int flag[], int n = 6){
for(int i = 0; i < n; ++ i){
if(flag[i] == 2)
cout << x[i] << " ";
else if(flag[i] == 0)
break;
}
cout << endl;
}
int main(){
int a[6] = {10, 20, 30, 40, 50, 60};
int flag[6] = {0};
int n = 6;
int count = 0;
int y = 60;
for(int i = 0; i >= 0; -- i){
while(flag[i] <= 2){
++ flag[i];
int sum = judge(a, flag, y);
if(sum == 0){
break;
}
else if(sum == 2){
cout << "The " << ++ count << "st solution: ";
cout_output(a, flag);
break;
}
if(i < 5) ++ i;//该问题的特殊限制条件,也可以放入judge函数中
}
flag[i] = 0;
}
}
955

被折叠的 条评论
为什么被折叠?



