zoukankan      html  css  js  c++  java
  • 浅谈回溯算法

    1.定义:
           回溯算法是一种在穷举查找基础上的增强变形。主要是在尝试搜索的过程中,每次只构造解的一个分量,当发现部分构造解满足求解条件时,就接受下一个分量所做的第一个合法选择;当发现部分构造解不满足求解条件时,就回溯返回,尝试另外的路径。这种走不通就回头的算法称为回溯算法。
           主要思想:通过对所做的选择构造一颗状态空间树,按照深度优先的策略,从根开始深度搜索状态空间树。当搜索到某一结点,先判断该节点是否可以构成完整解,如果可以就继续向下搜索;否则就逐层返回回溯,尝试其他路径。(隐式图的深度优先搜索)
           核心思想:个人以为,回溯法的核心是状态空间树的构造(可能是隐式构造的),一旦确立了状态空间树,就可以直接得出问题的解。状态空间树的构造思想是:按照深度优先的方式,如果当前节点的下一个分量所构造的部分解可以导致完整解,就生成节点的子女,并添加下一个分量的第一个合法选择;否则就回溯到该节点的父母,考虑部分解的下一个分量选择。
     
    2.回溯算法的一般步骤(解决某些约束下的最优解):
           (1)确定问题的解空间(包含所有解的一颗状态空间树),确定完全解的形式,以及部分构造解无法构成完全解的剪枝规则。( ps:问题的解空间(状态空间树)是在DFS的过程中动态生成的
           (2)确定节点(分量)的扩展规则,即下一个节点的选择规则。 (ps:如曼哈顿问题中,按照字母顺序选择下一个分量
           (3)以深度优先的方式搜索解空间,并在搜索的过程中用剪枝函数判断该节点是否可以生成完全解。如果可以,则进入该节点的子树下一步搜索(构造下一个分量);如果不能,则跳过该节点的子树(不再生成下一个分量),逐层回溯。(ps:剪枝函数包括约束函数和界限函数,分别剪去不满足约束的节点和不能得到最优解的函数
                             
    3.回溯的算法框架:
    (1)递归的方式:
    int x[n];
    void backtrack(int t)
    {
        if(t>n)              //到达叶子节点,输出结果,x是可行解
            output(x);
        else
        {
            for i = 1 to k   //该节点的子节点(分量的所有下一个分量)
            {
                x[t] = value(i);    //取出子节点的值
                if(constraint(t) && bount(t) )    //剪枝函数:判断约束和界限
                    backtrack(t+1);    //可以生成完全解,继续递归下去
            }
        }
    }
       特点:思路简单,设计简单,但算法时间效率很差。
        
    (2)递推的方式:
    void backtrack()
    {
        int t=1;
        while(t>0)
        {
            if(existSubNode(t))    //存在子节点:该结点还有可以构造的节点(下一个分量)
            {
                for i = 1 to k
                {
                    x[t] = value(i);    //相当于在此处建立一个结点
                    if(constraint(t) && bount(t) )    //剪枝函数判断约束和界限
                    {
                        if(isResult(t) )    //得到了一个结果,输出
                            output(x);
                        else                //还没有得到结果,继续向下搜索
                            t++;
                    }
                    else
                    {
                        eraseSubNode(t) //该结点无法构成完全解,故删去该结点,并设该结点不可再作为子节点。
                    }
                }
            }
            else
            {
                eraseSubNode(t);    //该结点没有子结点,也不能完全解,所以删去该结点。
                t--;                //进行回溯
            }    
        }
    }
    特点:设计复杂,但算法时间效率很高。
     
    4.用回溯的思想实现背包问题:
            用回溯算法解决01背包问题,步骤:
            (1)问题的解空间是子集树,节点表示前 t 个物品的存放状态,树枝的值表示第 t 个物品有没有放入背包。完全解的形式是01组成的N个数,避免无法构造完全解的约束剪枝规则是:当前背包的物品重量CurWeight加新增的物品重量不能超过背包的承重C(CurWeight+w[t]<C)。
                当物品数为3时,解空间(状态空间树)如下:
            
            (2)节点扩展规则:数字递增顺序,也就是说第t个物品放入背包和没有放入背包的状态,即 0 和 1.
            (3)回溯搜索:如果搜索到叶子节点,表示一条路径搜索结束,如果存在更优解则记录。
                                  如果没有搜索到叶子节点,则遍历其子节点,满足剪枝条件时继续向下搜索,不满足时回溯。
    #include <iostream>
    using namespace std;
    #define N  4                     //物品总数
    #define C  5                     //背包的承重
    int w[N] = {2,1,3,2};           //物品重量
    int v[N] = {12,10,20,15};     //物品价值
    int CurWeight = 0;            //当前总重量
    int CurValue = 0;               //当前总价值
    int x[N] ={0,0,0};                 //当前的背包选取情况(1表示选取,0表示不选取)
    int MaxValue = 0;              //最大价值
    int MaxPack[N] = {0,0,0};     //最大价值下的背包选取情况(1表示选取,0表示不选取)
    void backtrack(int t)
    {
        if(t >= N)    //到达叶子处,说明已经求得一个满足约束的完全解,接下来判断是否最优
        {
            if(CurValue > MaxValue)
            {
                MaxValue = CurValue;
                for(int i=0;i<N;i++)
                    MaxPack[i] = x[i];
            }
        }
        else    //没到叶子处,还要继续向下搜索下去
        {
            for(int i=0;i<=1;i++)
            {
                x[t] = i;            //01背包问题中,每个物品的存放状态是 0 或 1。此处表示第 t 个物品在背包中的状态为 0 或 1。
                if(x[t] == 0)     //第 t 个物品没有放入背包,不进行剪枝判断
                {
                    backtrack(t+1);     
                }
                else            //第 t 个物品放入背包,要进行剪枝判断
                {
                    if(CurWeight + w[t] <= C)    //剪枝判断为满足约束条件
                    {
                        CurWeight += w[t];   //当前结点满足约束,则增加结点的值到当前值中
                        CurValue += v[t];
                        backtrack(t+1);          //继续向下搜索
                        CurWeight -= w[t];    //为了回溯到父节点的状态,要将子节点新增的内容删掉
                        CurValue -= v[t];
                    }    
                }
            }
        }
    }
    
    int main()
    {
        backtrack(0);
        cout<<"the max value pack combination: ";
        for(int i=0; i<N; i++)
            cout<<MaxPack[i]<<" ";
        cout<<"
    the max value: "<<MaxValue<<endl;
    }
  • 相关阅读:
    获取Delphi焦点位置的方法,及所在的控件、以及如何通过控件名称访问控件并赋值
    常用自定义函数
    Delphi编程SQL语句中引号(')、quotedstr()、('')、format()在SQL语句中的用法
    throw new Error() 真实的用法和throw error 的却别
    分别基于和不基于unittest单元测试框架对一个加法做单元测试
    PAT 甲级 1073 Scientific Notation (20 分) (根据科学计数法写出数)
    python flask框架学习(三)——豆瓣微信小程序案例(二)整理封装block,模板的继承...
    第一章 概述
    python flask框架学习(三)——豆瓣微信小程序案例
    python flask框架学习——开启debug模式
  • 原文地址:https://www.cnblogs.com/ladawn/p/8472733.html
Copyright © 2011-2022 走看看