zoukankan      html  css  js  c++  java
  • 数据结构之回溯

    回溯

    • 利用回溯算法求解八皇后问题
    • 利用回溯算法求解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];       
    执行上述语句的目的在于为访问右子树做准备。在访问右子树时,显然不需要计算其重量和价值,因为右子树表示未选中该物品
          最后这几步是我在一些资料中看到的...总感觉有问题,尝试做了一些修改,还是有些别扭,其实大家也可以理解,算法这东西变幻莫测,再基本的原理也可以有很多表现形式,可以进一步优化,我们不要以上帝视角看待这个过程,这样会想当然地人为进行条件约束;
  • 相关阅读:
    Excel 单元格不能设置超链接
    jenkins 构建到最后报权限的问题
    xshell远程登录工具的星号密码查看方法
    xshell的一些常用配置
    zabbix 添加自动发现端口并监控
    细说websocket -Node篇
    让PHP更快的提供文件下载
    一个只有99行代码的JS流程框架
    40行代码的人脸识别实践
    PHP异步:在PHP中使用 fsockopen curl 实现类似异步处理的功能
  • 原文地址:https://www.cnblogs.com/hrnn/p/13346723.html
Copyright © 2011-2022 走看看