回溯法

回溯法(内容摘自《算法设计技巧与分析》)

基本特征:
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种颜色取值情况。回溯算法则是按照字典序考虑笛卡尔积 X 1 ∗ X 2 ∗ . . . ∗ X n X_1*X_2*...*X_n X1X2...Xn中的所有元素。某种特定解法可以用 { x 1 , x 2 , . . . x n } \lbrace x_1, x_2, ...x_n\rbrace {x1,x2,...xn}表示, X i X_i Xi取值范围 { 1 , 2 , . . . m } \lbrace 1, 2, ...m\rbrace {1,2,...m}
算法从最初空集开始,然后选择 X 1 X_1 X1中最小的元素作为 x 1 x_1 x1,如果 ( x 1 ) (x_1) (x1)是一个部分解,算法从 X 2 X_2 X2选择最小的元素作为 x 2 x_2 x2继续,如果 ( x 1 , x 2 ) (x_1, x_2) (x1x2)是一个部分解,那么继续往 ( X 3 ) (X_3) (X3)推进,否则 ( x 2 ) (x_2) (x2)被置为下一个元素。一般地,假定算法已经检测到部分解为 ( x 1 , x 2 , . . . , x j ) (x_1, x_2, ... , x_j) (x1,x2,...,xj),然后再去考虑向量 v = ( x 1 , x 2 , . . . , x j , x j + 1 ) v = (x_1, x_2, ... , x_j,x_{j+1}) v=(x1,x2,...,xj,xj+1),我们有下面的情况。

  • 如果v表示问题的最后解,算法记录下它作为一个解,在仅希望获得一个解时终止,或者继续去找出其他解。
  • (向前步骤)。如果v表示一个部分解,算法通过选择集合 X j + 2 X_{j+2} Xj+2中的最小元素向前。
  • 如果v既不是最终的解,也不是部分解,则有两种子情况。
    (a)如果从集合 X j + 1 X_{j+1} Xj+1中还有其他的元素可选择,算法将 x j + 1 x_{j+1} xj+1置为 X j + 1 X_{j+1} Xj+1中的下一个元素
    (b)(回溯步骤) 如果从集合 X j + 1 X_{j+1} Xj+1中没有更多的元素可选择,算法通过将 x j x_j xj置为 X j X_{j} Xj中的下一个元素回溯;如果从集合 X j X_{j} Xj中仍然没有其他的元素可以选择,算法通过将 x j − 1 x_{j-1} xj1置为 X j − 1 X_{j-1} Xj1中的下一个元素回溯,以此类推。

算法

就是迭代和递归,跟上文提到的程序一样

递归法
//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 = 60 X = \lbrace10,20,30,40,50,60\rbrace,y = 60 X={10,20,30,40,50,60},y=60,找出不同的 X X X的子集使得他们的和为 y y y,例如{10, 20,30}是其中一个满足条件的子集

解题思维

将每个数据的选择与否看成0,1情况,也就是有 2 6 2^6 26种搜索情况,而在这种题设条件下,可以有一个剪枝条件,就是一旦发现总和大于等于 y y y,便可以立即停止该节点以后的搜索,我也不知道这种想法算不算分支限界。

程序

//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; 
	}
} 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值