数据结构之回溯

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

数据结构之回溯
回溯
利⽤回溯算法求解⼋皇后问题
利⽤回溯算法求解0-1背包问题
利⽤回溯算法求解⼋皇后问题
⼋皇后问题(eight queens problem)是国际西洋棋棋⼿马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放⼋个皇后,使其不能互相攻击,即任意两个皇后都不能处于同⼀⾏、同⼀列或同⼀斜线上,问有多少种摆法。

可以把8皇后问题扩展到n皇后问题,即在nxn的棋盘上摆放n个皇后,使任意两个皇后都不能处于同⼀⾏、同⼀列或同⼀斜线上。

回溯法设计思路
回溯法从解空间树的根节点出发,按照深度优先策略搜索满⾜约束条件的解。

在搜索⾄树中节点时,先判断该节点对应的部分解是否满⾜条件,也就是判断该节点是否包含问题的(最优)解,如果肯定不包含,则跳过以该节点为根的⼦树,即所谓剪枝。

否则进⼊以该节点给根的⼦树,继续按照深度优先策略进⾏搜索。

#include <iostream>
#include<math.h>
using namespace std;
int Place(int k,int x[])
{
for(int i=0;i<k;i++)
if(x[i]==x[k] || abs(i-k)==abs(x[i]-x[k]))
return 1;
return 0;
}
int Queen(int n,int x[],int sum)
{
int k=0;
int flag=0;//有⽆解标志
while (k>=0)
{
x[k]++;
while(x[k]<n && Place(k,x)==1)
x[k]++;
if(x[k]<n && k==n-1)
{
flag=1;//有解
sum++;
for(int i=0;i<n;i++)
cout<<x[i]+1<<" ";
cout<<endl;
if(k<n && x[0]<n)
{
x[k--]=-1;//回溯
continue;
}
}
if(x[k]<n && k<n-1)
k+=1;
else
x[k--]=-1;
}
if(flag==0)
cout<<"⽆解。

"<<endl;
return sum;
}
int main()
{
int sum=0;
int n=8;//皇后个数
int x[8];
for(int i=0;i<n;i++)
x[i]=-1;
sum=Queen(n,x,sum);
cout<<"解毕,共有 "<< sum <<" 个解。

"<<endl; return 0;
}
#include<iostream>
using namespace std;
int Capacity; //背包容量
bool selected[10000]; //当前选择⽅案
bool optimal[10000]; //最佳选择⽅案
int maxTotalValue = 0; //最⼤价值
int valueofPackage = 0; //当前背包价值
int residualCapacity; //剩余背包价值
int n;
int weight[10000]; //背包重量
int value[10000]; //背包价值
void dfs(int i)
{
if(i > n){
if(valueofPackage >= maxTotalValue){
for(int i = 1 ; i <= n ; i++){
optimal[i] = selected[i];
}
maxTotalValue = valueofPackage;
}
return;
}else{
residualCapacity -= weight[i];
if(residualCapacity >= 0){ //遍历左⼦树
selected[i] = 1;
valueofPackage += value[i];
dfs(i+1);
selected[i] = 0;
valueofPackage -= value[i];
residualCapacity += weight[i];
}else{//不满⾜原路返回
residualCapacity += weight[i];
}
}
//遍历右⼦树
dfs(i+1);
}
int main(){
cout<<"输⼊背包容量:"<<endl;
cin>>Capacity;
residualCapacity = Capacity;
cout<<"请输⼊背包个数:"<<endl;
cin>>n;
cout<<"请输⼊每个背包重量:"<<endl;
for(int i = 1 ; i <= n ; i++){
cin>>weight[i];
}
cout<<"请输⼊每个背包价值:"<<endl;
for(int i = 1 ; i <= n ; i++){
cin>>value[i];
}
dfs(1);
cout<<"最佳⽅案为:"<<endl;
for(int i = 1 ; i <= n ; i++){
if(optimal[i] == 1){
cout<<i<<" ";
}
}
cout<<endl<<"最⼤背包价值为:"<<endl<<maxTotalValue;
return 0;
}
回溯法本质是⽤来搜索问题的解,典型地就是使⽤深度优先搜索,搜索路径⼀般沿树形结构进⾏,在搜索过程中,⾸先会判断所搜索的树结点是否包含问题的解,如果肯定不包含,则不再搜索以该结点为根的树结点,⽽向其祖先结点回溯;否则进⼊该⼦树,继续按深度优先策略搜索。

可能这么说不是很容易懂,咱们来的实例吧,那就是经典的0-1背包问题,关于这⼀问题后边很多算法都会涉及到,咱们⼀点点深⼊~我们还是使⽤典型的三背包为例,问题描述如下:设有三个背包,其重量分别为:16,15, 15;价值分别为: 45,25,25;请选择背包,使其重量不超过30,但价值最⼤。

可以看出我们的约束条件为总重量不超过30,⽬标是价值最⼤,那我们就可以使⽤回溯法的思想来求解:
每个背包都可以被选择中或者不选,理论上如果不加任何限制的话⼀共有⼋种可能(2×2×2),但我们在搜索的过程中要时刻注意总重量不可超过30 ,在这个基础上使其总价值最⼤,于是我们可以从第⼀个背包开始,先选中它,其重量是16,⼩于30,没有问题,然后在选择第⼆个背包,其重量为15,⼆者总重量为31,超过30,所以不能选择第⼆个背包,同理第三个背包也不能选,也就是说在选中第⼀个背包的前提下,另外两个背包就都不能再选了,这是⼋种理论可能情况中的⼀种;以此类推,不选第⼀个背包,在此基础上选择第⼆个背包,总重量为15,没有问题,再选第三个,此时总重量为30,也没有问题;对照这两种情况,前者总价值为45,后者总价值为50,当然,我们以上帝视⾓可以看出最⼤价值也就是50,但落实到具体的算法该如何全⽅位多⾓度深层次地解决这⼀问题呢?
我们来看⼀个完全⼆叉树结构图:
对于上述的背包问题,在此⼆叉树结构中可以简单地理解为:从A出发,往左⼦树⽅向⾛说明选中了A,往右⼦树⽅向⾛说明没有选中A,
即“左选右不选”,落实到上图中就是1代表选中0代表未选中;我们上边说道的第⼀种情况,即只选中第⼀个背包的情况对应上图的A->B->E->K;那这⾥有朋友可能会问了为啥⼆叉树会有四层,不是⼀共三个背包嘛,对的,因为我们每⼀层所代表的背包选与不选都得由下⼀层所决定,⽐如节点E代表第三个背包,如果我们⾛到K就说明不选E,反之,若⾛到J则说明选中了E,因此三个背包我们需要四层完全⼆叉树,同理,若有N个背包则需要N+1层。

当我们的搜索路径到达K后,得到了⼀组值,即总重量为16,总价值为45,此时,由于已经到了树的叶⼦结点,因此需要回溯直到根,再继续进⾏后续的搜索,在后续搜索过程中,⼀⽅⾯要进⾏结点的判断,另⼀⽅⾯,⼀旦得到了⼀个合符要求的价值,则与前⼀次搜索所得到的结果进⾏⽐较,如果⽐前⼀次搜索得到的值⼤,则取代,反之,继续搜索直到整个树搜索结束所得到的最⼤值即为问题的解。

总的来说,上述的求解过程在程序实现过程中可以这样来理解:我们把⼆叉树的每⼀层看成是某⼀个物品。

当我们选择物品时总是从第⼀个物品开始进⾏选择,可能选,也可能不选。

如果选中,则从⼆叉树的左边⼦树开始搜索,如果未选中,则从⼆叉树的右边⼦树开始搜索。

以此类推即可~
如果将每个物品的重量对应每⼀层的⼀个节点,在每次选择每⼀个物品时进⾏重量的判断,并记录权值,则可决定是否应该继续搜索下⼀层的⼦树。

如果设物品的重量存放在W数组中, W[i]为第i个物品的重量,P[i]表⽰第i个物品的价值。

C表⽰背包能够承受的最⼤重量。

cw表⽰当前物品的重量,cp表⽰当前物品的价值(稍微记忆⼀下这⼏个符号量的意义)。

在选中情况下(即搜索左⼦树)执⾏下⾯的操作:
( 1 )⾸先判断加上该物品重量后是否满⾜最⼤重量不超过C的要求,如果不超过,则: cw+=w[i]; cp+=p[i]; 反之则搜索右⼦树;( 2 )继续搜索下⼀层,执⾏相同的操作;
( 3 )当搜索到最后⼀层时,显然可得到从根到该结点所选择的所有物品的价值,如果该价值⼤于前⼀次得到的最⼤价值,则替代前⼀次的价值,反之,则不取代;
( 4 ) 退回到上⼀层,即其双亲结点所在的⼀层,显然此时应执⾏: cw-=w[i]; cp-=p[i];
执⾏上述语句的⽬的在于为访问右⼦树做准备。

在访问右⼦树时,显然不需要计算其重量和价值,因为右⼦树表⽰未选中该物品最后这⼏步是我在⼀些资料中看到的...总感觉有问题,尝试做了⼀些修改,还是有些别扭,其实⼤家也可以理解,算法这东西变幻莫测,再基本的原理也可以有很多表现形式,可以进⼀步优化,我们不要以上帝视⾓看待这个过程,这样会想当然地⼈为进⾏条件约束;。

相关文档
最新文档