[回溯算法]五大常用算法之回溯法
[回溯算法]五⼤常⽤算法之回溯法
算法⼊门6:回溯法
⼀. 回溯法 – 深度优先搜素
1. 简单概述
回溯法思路的简单描述是:把问题的解空间转化成了图或者树的结构表⽰,然后使⽤深度优先搜索策略进⾏遍历,遍历的过程中记录和寻所有可⾏解或者最优解。
基本思想类同于:
分⽀限界法:⼴度优先搜索
思想类同于:图的⼴度优先遍历
⼆叉树的层序遍历
2. 详细描述
详细的描述则为:
回溯法按深度优先策略搜索问题的解空间树。⾸先从根节点出发搜索解空间树,当算法搜索⾄解空间树的某⼀节点时,先利⽤剪枝函数
剪枝函数判断该节点是否可⾏(即能得到问题的解)。如果不可⾏,则跳过对该节点为根的⼦树的搜索,逐层向其祖先节点回溯;否则,进⼊该⼦树,继续按深度优先策略搜索。
回溯法的基本⾏为是搜索,搜索过程使⽤剪枝函数来为了避免⽆效的搜索。剪枝函数包括两类:1. 使⽤约束函数,剪去不满⾜约束条件的路径;2.使⽤限界函数,剪去不能得到最优解的路径。
问题的关键在于如何定义问题的解空间,转化成树(即解空间树)。解空间树分为两种:⼦集树和排列树。两种在算法结构和思路上⼤体相同。
3. 回溯法应⽤
当问题是要求满⾜某种性质(约束条件)的所有解或最优解时,往往使⽤回溯法。
它有“通⽤解题法”之美誉。
⼆. 回溯法实现 - 递归和递推(迭代)
回溯法的实现⽅法有两种:递归和递推(也称迭代)。⼀般来说,⼀个问题两种⽅法都可以实现,只是在算法效率和设计复杂度上有区别。
【类⽐于图深度遍历的递归实现和⾮递归(递推)实现】
1. 递归
思路简单,设计容易,但效率低,其设计范式如下:
[cpp]
1. //针对N叉树的递归回溯⽅法
2. void backtrack (int t)
3. {
4.    if (t>n) output(x); //叶⼦节点,输出结果,x是可⾏解
5.    else
6.        for i = 1 to k//当前节点的所有⼦节点
7.        {
8.            x[t]=value(i); //每个⼦节点的值赋值给x
9.            //满⾜约束条件和限界条件
10.          if (constraint(t)&&bound(t))
11.                backtrack(t+1);  //递归下⼀层
12.        }
13. }
2. 递推
做梦梦见牙掉了
算法设计相对复杂,但效率⾼。
[cpp]
1. //针对N叉树的迭代回溯⽅法
2. void iterativeBacktrack ()
3. {
4. int t=1;
5. while (t>0) {
6. if(ExistSubNode(t)) //当前节点的存在⼦节点
7.        {
8. for i = 1 to k  //遍历当前节点的所有⼦节点
9.            {
10.                x[t]=value(i);//每个⼦节点的值赋值给x
11. if (constraint(t)&&bound(t))//满⾜约束条件和限界条件
12.                {
13. //solution表⽰在节点t处得到了⼀个解
14. if (solution(t)) output(x);//得到问题的⼀个可⾏解,输出
15. else t++;//没有得到解,继续向下搜索
16.                }
17.            }
18.        }
19. else//不存在⼦节点,返回上⼀层
20.        {
21.            t--;
22.        }
23.    }
24. }
三. ⼦集树和排列树
1. ⼦集树
所给的问题是从n个元素的集合S中出满⾜某种性质的⼦集时,相应的解空间成为⼦集树。
如0-1背包问题,从所给重量、价值不同的物品中挑选⼏个物品放⼊背包,使得在满⾜背包不超重的情况下,背包内物品价值最⼤。它的解空间就是⼀个典型的⼦集树。
回溯法搜索⼦集树的算法范式如下:
[cpp]
1. void backtrack (int t)
2. {
3. if (t>n) output(x);
4. else
5. for (int i=0;i<=1;i++) {
6.        x[t]=i;
7. if (constraint(t)&&bound(t)) backtrack(t+1);
8.      }
9. }
10.
滴答滴答歌词
2. 排列树
所给的问题是确定n个元素满⾜某种性质的排列时,相应的解空间就是排列树。
如旅⾏售货员问题,⼀个售货员把⼏个城市旅⾏⼀遍,要求⾛的路程最⼩。它的解就是⼏个城市的排列,解空间就是排列树。
回溯法搜索排列树的算法范式如下:
[cpp]
1. void backtrack (int t)
2. {
3. if (t>n) output(x);
4. else
5. for (int i=t;i<=n;i++) {
6.        swap(x[t], x[i]);
7. if (constraint(t)&&bound(t)) backtrack(t+1);
8.        swap(x[t], x[i]);
9.      }
10. }
11.
四. 经典问题
(1)装载问题
(2)0-1背包问题
(3)旅⾏售货员问题
(4)⼋皇后问题
(5)迷宫问题
(6)图的m着⾊问题
上海好玩的地方排行榜>ps去水印1. 0-1背包问题
问题:给定n种物品和⼀背包。物品i的重量是wi,其价值为pi,背包的容量为C。问应如何选择装⼊背包的物品,使得装⼊背包中物品的总价值最⼤?
分析:问题是n个物品中选择部分物品,可知,问题的解空间是⼦集树。⽐如物品数⽬n=3时,其解空间树如下图,边为1代表选择该物品,边为0代表不选择该物品。使⽤x[i]表⽰物品i是否放⼊背包,x[i]=0表⽰不放,x[i]=1表⽰放⼊。回溯搜索过程,如果来到了叶⼦节点,表⽰⼀条搜索路径结束,如果该路径上存在更优的解,则保存下来。如果不是叶⼦节点,是中点的节点(如B),就遍历其⼦节点(D和E),如果⼦节点满⾜剪枝条件,就继续回溯搜索⼦节点。
代码:
[cpp]
1. #include <stdio.h>
2.
3. #define N 3        //物品的数量
4. #define C 16        //背包的容量
5.
6. int w[N]={10,8,5};  //每个物品的重量
7. int v[N]={5,4,1};  //每个物品的价值
8. int x[N]={0,0,0};  //x[i]=1代表物品i放⼊背包,0代表不放⼊
9.
10. int CurWeight = 0;  //当前放⼊背包的物品总重量
11. int CurValue = 0;  //当前放⼊背包的物品总价值
12.
13. int BestValue = 0;  //最优值;当前的最⼤价值,初始化为0
14. int BestX[N];      //最优解;BestX[i]=1代表物品i放⼊背包,0代表不放⼊
15.
16. //t = 0 to N-1
17. void backtrack(int t)
18. {
19. //叶⼦节点,输出结果
20. if(t>N-1)
21.    {
22. //如果到了⼀个更优的解
23. if(CurValue>BestValue)
24.        {
25. //保存更优的值和解
26.            BestValue = CurValue;
27. for(int i=0;i<N;++i) BestX[i] = x[i];
28.        }
29.    }
30. else
31.    {
32. //遍历当前节点的⼦节点:0 不放⼊背包,1放⼊背包
33. for(int i=0;i<=1;++i)
34.        {
35.            x[t]=i;
36.
37. if(i==0) //不放⼊背包
38.            {
39.                backtrack(t+1);
40.            }
41. else//放⼊背包
42.            {
43. //约束条件:放的下
44. if((CurWeight+w[t])<=C)
45.                {
46.                    CurWeight += w[t];
47.                    CurValue += v[t];
48.                    backtrack(t+1);
49.                    CurWeight -= w[t];
50.                    CurValue -= v[t];
51.                }
52.            }
53.        }
54. //PS:上述代码为了更符合递归回溯的范式,并不够简洁
55.    }
56. }
57.
58. int main(int argc, char* argv[])
59. {
60.    backtrack(0);
七夕贺卡61.
62.    printf("最优值:%d\n",BestValue);
63.
64. for(int i=0;i<N;i++)
65.    {
66.        printf("最优解:%-3d",BestX[i]);
67.    }
68. return 0;
69. }
2. 旅⾏售货员问题
3. 详细描述N皇后问题
问题:在n×n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与之处在同⼀⾏或同⼀列或同⼀斜线上的棋⼦。
N皇后问题等价于在n×n格的棋盘上放置n个皇后,任何2个皇后不放在同⼀⾏或同⼀列或同⼀斜线上。
分析:从n×n个格⼦中选择n个格⼦摆放皇后。可见解空间树为⼦集树。初中文凭可以考大专吗
使⽤Board[N][N]来表⽰棋盘,Board[i][j]=0 表⽰(I,j)位置为空,Board[i][j]=1 表⽰(I,j)位置摆放有⼀个皇后。
全局变量way表⽰总共的摆放⽅法数⽬。
使⽤Queen(t)来摆放第t个皇后。Queen(t) 函数符合⼦集树时的递归回溯范式。当t>N时,说明所有皇
后都已经摆  放完成,这是⼀个可⾏的摆放⽅法,输出结果;否则,遍历棋盘,皇后t所有可⾏的摆放位置,Feasible(i,j) 判断皇后t能否摆放在位置(i,j)处,如果可以摆放则继续递归摆放皇后t+1,如果不能摆放,则判断下⼀个位置。
Feasible(row,col)函数⾸先判断位置(row,col)是否合法,继⽽判断(row,col)处是否已有皇后,有则冲突,返回0,⽆则继续判断⾏、列、斜⽅向是否冲突。斜⽅向分为左上⾓、左下⾓、右上⾓、右下⾓四个⽅向,每次从(row,col)向四个⽅向延伸⼀个格⼦,判断是否冲突。如果所有⽅向都没有冲突,则返回1,表⽰此位置可以摆放⼀个皇后。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。