【算法设计zxd】第6章 回溯法

目录

6.1 回溯法的设计技术 :

四皇后问题

回溯法:

算法框架:

思考题:

回溯算法的适用条件

【例6-1】求满足下列不等式的所有整数解:

6.2回溯算法的经典例题

【例6-2】装载问题

 问题分析

 计算模型

 算法设计与描述

算法分析:

代码:

【例6-3】n皇后问题。

 问题分析 算法思想详见开篇。

 计算模型

 算法设计与描述

 算法分析

另一种:随机算法

【例6-4】 0-1背包问题。

 问题分析

 数学模型

 计算模型

 算法设计与描述

 算法分析

代码:

【例6-5】旅行商问题(Traveling Salesman Problem,简称TSP)。

 问题分析

 计算模型

算法设计与描述:

小结

分支限界法的设计技术

分支限界法:

 约束条件

 剪枝

 分支限界法的设计步骤

思考题:

【例6-6】装载问题。

 计算模型

【例6-7】背包

 问题分析

 问题分析

计算模型

 计算模型

 算法设计与描述

【例6-8】旅行商问题(Traveling Salesman Problem,TSP):

 问题分析

 计算模型

 算法设计与描述


6.1 回溯法的设计技术 :

活结点:可以生成子节点
扩展结点:
死结点:不能再进行子节点生成的

回溯法:

约束条件下对解空间树进行深度优先搜索的过程,
并在搜索过程中去那些不满足条件的分支。
问题的解
为n元组(X 1 ,…,X i ,…X n ),其中X i 选自有限集S ·
当选出一组值X=(x 1 ,…,x i ,…x n )能够使评价函数P(x 1 ,…,x i ,…x n ) 满足问题的某种约束条件或到达极值。
基本策略
每次只考虑一个分量,逐次扩大建立n元组,
并随时用评价函数P i (X 1 ,…,X i ,…X n)去判断正在形成的n元组是否有成功的希望,
一旦确定部分元组(X 1 , … ,X i)不能求出解时,则立即停止这种搜索,“剪掉”以当前结点为
根的分枝,
并逐次向上一个分量回溯,
然后向其它分支继续探索

算法框架:

(1) 开始结点 是一个活结点,也是一个 扩展结点
(2) 如果能从当前的扩展结点移动到一个新的结点,那么这
个新结点将变成一个活结点和可扩展结点,旧的扩展结点
仍是一个活结点。
(3) 如果不能移动到一个新结点(已经找到一个解或违反约
束的情况),当前的扩展结点就变成了一个 死结点 ,便只能
返回到最近被考察的活结点(回溯),这个活结点就变成了新
的扩展结点。
(4) 当找到了最优解或者没有活结点的时候(回溯尽了所有的活结点),搜索过程结束
回溯的递归框架 回溯的非递归模式
输入:n
输出:所有的解x[]

int x[n],i=1;
search(int i)
{
    if(i>n)//递归出口

       输出结果;
    else//枚举所有可能的路径 
    { 
        for(j=下界;j<=上界;j=j+1)//当前结点的子节点的同一层
        {
            //满足 界限函数和约束条件
            if(P(j))//
            {//深度 下一层
                x[i]=j;//将符合要求的结点的编号,存储到解的数组里
                ...//其他操作
                search(i+1);
                回溯前的清理工作;//再进行for循环 下一个结点
            } 
        } 
    } 
}


int x[n],i=1;
//还未回溯到头, i为真 表示有路可走 
while(i&&(未达到目标) )
{
    if(i>n)//搜索到叶节点 
        搜索到一个解,输出;
    else//处理第i个元素 
    { 
        x[i]第一个可能的值;
        //x[i]不满足约束条件,但在搜索空间内
        while( !P(x[i]) && x[i]在搜索空间内 ) 
            x[i]下一个可能的值;
            
        if( x[i]在搜索空间内)
        {
            标识占用的资源;
            i=i+1;//扩展下一个结点 
        }
        else
        {
            清理所占的状态空间;
            i=i-1;//回溯 
        } 
        
    } 
}

思考题1:

【算法设计zxd】第6章 回溯法_第1张图片

(1)不是唯一, n元组 

(3)剪枝:判断是否满足约束条件。执行:将结点标为死结点。

回溯算法的适用条件

多米诺性质
设向量X i = 1 , x 2 ,…,x i >,X i \subseteq X,X = < x 1 , x 2 ,…,x i ,…,x n >,
将X i 输入评价函数P,可以得到X i 的一组 特征值P(X i ),
取X i+1 = 1 , x 2 ,…,x i , x i+1 >【增加一个元素】,Xi+1 \subseteq X,则P(Xi+1) 真蕴涵P(X i ),即

 P(Xi+1) ->  P(Xi)   i∈(0,n) ,其中,n代表解向量的维数。

【子集,否则可能丢解】

【例6-1】求满足下列不等式的所有整数解:

解:令X i = 1 , x 2 ,…,x i >,P(X i )为对X i 的评估,判断其Xi是否满
足不等式,即P(X i ) ≤10。依据题目可知,本例中向量为一个三元
组< x 1 , x 2 , x 3 >

【算法设计zxd】第6章 回溯法_第2张图片

【丢解】

 当x1=1、x2=2时, P(x1 ,x2 )= 5x1+4x2=5*1+4*2=13>10 不满足约束条件,分支x2=2将被剪去

然而,当x 1 =1、x 2 =2、x 3=3时, P(x 1 ,x 2 ,x 3 )= 5x 1 +4x 2 - x 3=5*1+4*2 - 3=10 满足约束条件,即<1, 2, 3>是不等式的解,
显然,此例中P(X 3 )不真蕴涵P(X 2),违反了多米诺性质,从而丢解了。

【解决】

如果令x’3=4-x3 ,将原不等式变换为:5x1+4x2+x’3≤14 1≤xi , x’3≤3 i=1, 2

则该不等式满足多米诺性质,可以使用回溯法,对所得到的解x 1 、x 2 、x’ 3
换成原不等式的解x 1 、x 2 、x 3 即可。

代码:

#include
using namespace std;


//
int n=3;
int x[3]={1,1,1};

void show(int a[]) 
{
	for(int i=0;in-1)//已经结束了最后一个数 
	{
//		cout<<"结果:";
		show(x);
		return;//结束最后一层,回溯到上一层 
	}
	//若不变 
	if(P(x,i)&&x[i]<4)//符合约束条件 
	{
		x[i]=1;
		fun(i+1); //下一层 
	}
	//若2
	if(P(x,i)&&x[i]<4)//符合约束条件 
	{
		int t=x[i];
		x[i]=2;
		fun(i+1); //下一层 
		x[i]=t;//回复 
	}
	
	//若3
	if(P(x,i)&&x[i]<4)//符合约束条件 
	{
		int t=x[i];
		x[i]=3; 
		fun(i+1); //下一层 
		x[i]=t;//回溯后,本层尝试下一个可能 
	} 
}

int main()
{
	fun(0);
	return 0;
	
} 
/*
1       1       1

1       1       2

1       1       3

1       2       1

1       2       2

1       2       3
*/

6.2回溯算法的经典例题

【例6-2】装载问题

有n个集装箱要装上一艘载重量为c的轮船,其中,集装箱i的重量为w i 。找出一种最优装载方案,让轮船尽可能多装集装箱,即在装载体积不受限制的情况下,尽可能使轮船满载

 问题分析

设集装箱数量n=5,轮船载重c=10,集装箱的重量w={7,2,6,5,4}

【算法设计zxd】第6章 回溯法_第3张图片

对于n个集装箱的装载问题,可将该问题的解定义为一个n元组
(x 1 ,…,x i ,…x n ), i∈Z, 1≤i≤n, x i ∈{0,1},
x i =1表示装入集装箱i,x i=0表 示不装入集装箱i。

【算法设计zxd】第6章 回溯法_第4张图片

 其中,wi 表示第i个集装箱的重量。

满足多米诺条件

 计算模型

1. 数据结构定义
 静态数据结构:
轮船载重量为c,集装箱数量为n,集装箱重量数组 w[],这些变量在运算中不会发生改变。
 动态数据结构:
i表示搜索的层数,加入一个集装箱后计算出的当前 载重量nowc、当前解x[]、当前最优重量maxc、当前最优解maxx[], 剩余的集装箱重量r(初值为全部集装箱重量),
这些值都是边测试 边生成的,当所有计算全部完成后,maxc和maxx[]就是题目要求的 最优值和最优解。
2. 迭代公式
【算法设计zxd】第6章 回溯法_第5张图片

 算法设计与描述

输入:c, n, w[]
输出:最优值maxc和最优解maxx[]
void search (int i){ /*递归法*/
	if(i>n){//搜索完
		if(nowc>maxc){//现在重量值
			maxc=nowc;
			for(int j=1;j<=n;j++)
				maxx[j]=x[j];
		}
		return;
	}
	
	//剩余量 
	r=r-w[i]; //搜索第i层,同时减少可用量
	//若不装,则岸上重量减去
	if(nowc+w[i]<=c){ //满足约束,左子树
		x[i]=1;
		nowc=nowc+w[i];
		search(i+1);//递归搜索i+1层
		nowc=nowc-w[i];//回溯后恢复nowc
	} 
	/*下面开始搜索右子树*/
	if(nowc+r>maxc){ /*大于当前最优*/
		x[i]=0;
		search(i+1); //递归搜索i+1层
	}
	r=r+w[i];//对第i层搜索完毕,恢复r
}

算法分析:

(1)由算法设计与描述推导
T(1) = 2T(1) //集装箱1选择与不选择
2T(1) = 2*2T(1)=2^ 2 T(1)
……
2 ^( n-1) T(1) = 2*2^( n-1) T(1) = 2^ n T(1)
T(n) = T(1)+ 2T(1)+ 2^ 2 T(1)+……+2^ n T(1)
= T(1)*(2^( n+1)  – 1)≤2×2^ n =O(2^ n )
(2) 由选择树推导
结点数为: 1+2+2^ 2 +……+2^ n
=2^( n+1)  - 1≤ 2×2^ n =O(2^ n )

代码:

#include
using namespace std;

int n=5;//集装箱数量 
int c=10;//轮船载重
int w[]={7,2,6,5,4};//集装箱的重量

int nowc=0;//当前载重量 
int x[5];//当前解
int maxc=0;//当前最优重量 
int maxx[5]; //当前最优解
int r=7+2+6+5+4;//剩余集装箱的总重量 


void show(int a[]) 
{
	for(int i=0;in-1){//搜索完 递归出口 
		if(nowc>maxc){//现在重量值>当前最优 
			//更新最优解 
			maxc=nowc;
			//记录最优解的路径 
			for(int j=1;j<=n;j++)
				maxx[j]=x[j];
		}
		return;//结束最后一层的函数,回溯到上一层进行递归调用 
	}
	
	r=r-w[i]; //此时 陆地上集装箱的剩余重量 
	//若不装,则岸上重量减去
	
	if(nowc+w[i]<=c){ //满足约束(小于轮船载重量),左子树
		x[i]=1;
		nowc=nowc+w[i];
		search(i+1);//递归搜索i+1层
		nowc=nowc-w[i];//回溯后恢复nowc
		//回到上一层 
	}
	/*下面开始搜索右子树 */
	//右子树是否需要递归
	if(nowc+r>maxc){ /*上界函数 此时当前轮船已载重量+剩余集装箱重量 > 当前最优*/
		x[i]=0;
		search(i+1); //递归搜索i+1层
	}//否则不进行递归,也就是可以确认为死结点 
	
	r=r+w[i];//对第i层搜索完毕,恢复r
	//陆地上增加 
}

int main()
{
	search(0);//从第1层开始 
	cout<

【例6-3】n皇后问题。

在n*n的棋盘上放置相互攻击不到的n个皇后。
国际象棋规则:任意两个皇后之间不能处在同一行、同一列和同一斜线上,否则皇后间就可以相互攻击。
请给出满足条件的所有方案。

 问题分析 算法思想详见开篇。

 计算模型

(1)数据结构
        皇后数量为n; sum为可行解的数量。
        x[]记录皇后的摆放位置,下标为皇后所在行,值为列。
/*若数组下标 参与运算,会大大遍历*/
(2)计算模型
【算法设计zxd】第6章 回溯法_第6张图片

其中,式(2)与式(3)共同构成了对式(1)中所取得的值的一个评价,所以可以统称式(2)和式(3)为评价函数P。

 算法设计与描述

输入:皇后的数量n
输出:皇后的摆放位置x[],可以有多组

/*判断当前格局x[1...i],是否符合约束*/
bool ok(int i){
    for(int j=1;jn){//找到可行解
        for(int j=1;j<=n;j++)
            printf("%-5d",x[j]);
        printf("\n");
        sum=sum+1;//可行解数目+1 
    }
    
    for(int j=1;j<=n;j++){//每一层都有n个元素 
        x[i]=j;
        if(ok(i))//如果满足约束
            queen(i+1);//搜索第i+1层
        //否则就结束在这一层 
    }
}

 算法分析

由选择树可知:

由ok函数:

时间复杂度为:

另一种:随机算法

程序设计可能很麻烦,实际实现效率非常差。
回溯和随机相结合——更好

【例6-4】 0-1背包问题。

已知有n件物品,物品i的重量为wi、价值为pi。现从 中选取一部分物品装入一个背包内,背包最多可容纳的总重量是m,如何选择才能使得物品的总价值最大

问题分析

物品数量n=3
物品重量w={10,20,30}
物品价值p={60,100,120}
背包承重m=50
【算法设计zxd】第6章 回溯法_第7张图片

 数学模型

【算法设计zxd】第6章 回溯法_第8张图片

pi第i个物品价值>0,xi第i个物品是否被选择[0,1]

 计算模型

1. 数据结构
背包容量m,物品数量n,物品重量数组w[],物品价值数组p[]。
当前背包重量bagw,当前背包总价bagp,当前最优解x[]
当前最优总价值maxp,最优解maxx[],可用的物品价值r。
2. 迭代公式

算法设计与描述

void bag(int i){
        if(i>n){
                if(bagp>maxp){
                        maxp=bagp;
                        for(int j=1;j<=n;j++){
                                maxx[j]=x[j];
                        }
                }
                return;
        }
        r=r-p[i]; //对第i层进行搜索,用r减少
        if(bagw+w[i]<=m){ //满足约束
                x[i]=1;
                bagw=bagw+w[i];
                bagp=bagp+p[i];
                bag(i+1);
                bagw=bagw-w[i]; //回溯后恢复bagw
                bagp=bagp-p[i]; //回溯后恢复bagp
        }
        if(bagp+r>maxp){ //搜索右子树
                x[i]=0;
                bag(i+1);
        }
        r=r+p[i]; //对第i层进行搜索回来,恢复r
}

算法分析

思考题:

【算法设计zxd】第6章 回溯法_第9张图片

蛮力法  回溯法
区别 回溯法有对右子树的剪枝
时间渐进复杂度 T(n)=O(2^n) T(n)=O(2^n)
区别 在实际运算中,同样情况下回溯法的时间复杂度优于蛮力法,回溯法最坏情况下的时间渐进复杂度与蛮力法相同。
原因 回溯法存在剪枝操作。

代码:

#include

using namespace std;

int n=3;//物品数量 
int w[]={10,20,30};//物品重量 
int p[]={60,100,120};//物品价值 
int m=50;//背包称重
//使物品总价值最大 

int bagw=0;//当前背包重量
int bagp=0;//当前背包价值
int x[3];//当前最优解
int maxp=0;//当前最优总价值
int maxx[3];//最优解
int r=60+100+120;//可用的物品价值,也就是物品总价值剩余量 

void show(int a[]) 
{
	for(int i=0;in-1)
	{
		if(bagp>maxp)
		{
			maxp=bagp;
			for(int j=0;jmaxp)//如果当前价值+剩余价值> 当前最大价值 还有递归的必要
	//如果是 当前价值+剩余价值 <= 当前最大价值,那么其实就没有递归的必要了 
	{
		x[i]=0;//右子树 
		bag(i+1);
	}
	r=r+p[i];//对第i层进行搜索回来,回复r 
	
}

int main()
{
	bag(0); 
	cout<

【例6-5】旅行商问题(Traveling Salesman Problem,简称TSP)。

【算法设计zxd】第6章 回溯法_第10张图片

 问题分析

假设城市数量n=4,V={A,B,C,D},城市间的距离如图6-9所示的图结构。
设出发城市为A,问题的解空间为{A→{B,C,D三者的全排列}→A}

计算模型

1. 数据结构
城市数量n,距离矩阵d[][],城市名称city[]。
当前路径距离nowd,当前路径nowx[],最短距离mind,最优路径x[]。
2. 迭代公式
【算法设计zxd】第6章 回溯法_第11张图片

算法设计与描述:

输入:城市数量n,距离d[][],
城市名city[]
输出:最优路径x[],最优值mind
算法分析
对由n个城市形成的全排列树来说,所含的结点数目为:
【算法设计zxd】第6章 回溯法_第12张图片

【算法设计zxd】第6章 回溯法_第13张图片
蛮力法 回溯法
区别
时间渐进复杂度 O( (n-1)! ) O( (n-1)! )

小结

二分查找算法
分治算法策略的设计模式
大整数乘法和Strassen矩阵乘法
棋盘覆盖问题
选择性问题

你可能感兴趣的:(算法zxd,算法)