2.回溯法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/L_i_n_o/article/details/79583007

基本概念

回溯法思路的简单描述是:把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。
基本思想类同于:

  • 图的深度优先搜索
  • 二叉树的后序遍历

基本思想及策略

  • 在包含问题的说有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度搜索解空间树。当搜索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。
  • 回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。剪枝函数包括两类:1. 使用约束函数,剪去不满足约束条件的路径;2.使用限界函数,剪去不能得到最优解的路径。
  • 问题的关键在于如何定义问题的解空间,转化成树(即解空间树)。解空间树分为两种:子集树和排列树。两种在算法结构和思路上大体相同。

适用情况

一般啊框架

  • 子集树

    • 所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间成为子集树。
      如0-1背包问题,从所给重量、价值不同的物品中挑选几个物品放入背包,使得在满足背包不超重的情况下,背包内物品价值最大。它的解空间就是一个典型的子集树。
      这里写图片描述

      回溯法搜索子集树的算法范式如下:

      void backtrack (int t)  
      {  
      if (t>n) output(x);  
      else  
      for (int i=0;i<=1;i++) {  
      x[t]=i;  
      if (legal(t)) backtrack(t+1);  
      }  
      }
      

      这里写图片描述

  • 排列树
    • 所给的问题是确定n个元素满足某种性质的排列时,相应的解空间就是排列树。
      如旅行售货员问题,一个售货员把几个城市旅行一遍,要求走的路程最小。它的解就是几个城市的排列,解空间就是排列树。
      回溯法搜索排列树的算法范式如下:
        void backtrack(int t)
        {
            if(t>n) output(x);
            else{
                for(int i=t;i<=n;i++){
                    swap(x[t],x[i]);择优排列
                    if(legal(t)) backtrack(t+1);
                    swap(x[t],x[i]);回溯还原
                }
        }
    }

案例分析

  • 0-1背包问题
    • 问题:给定n种物品和一背包。物品i的重量是wi,其价值为pi,背包的容量为C。问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
    • 分析:问题是n个物品中选择部分物品,可知,问题的解空间是子集树。比如物品数目n=3时,其解空间树如下图,边为1代表选择该物品,边为0代表不选择该物品。使用x[i]表示物品i是否放入背包,x[i]=0表示不放,x[i]=1表示放入。回溯搜索过程,如果来到了叶子节点,表示一条搜索路径结束,如果该路径上存在更优的解,则保存下来。如果不是叶子节点,是中点的节点(如B),就遍历其子节点(D和E),如果子节点满足剪枝条件,就继续回溯搜索子节点。

这里写图片描述

代码:

#include <stdio.h>

#define N 3         //物品的数量
#define C 16        //背包的容量

int w[N]={10,8,5};  //每个物品的重量
int v[N]={5,4,1};   //每个物品的价值
int x[N]={0,0,0};   //x[i]=1代表物品i放入背包,0代表不放入

int CurWeight = 0;  //当前放入背包的物品总重量
int CurValue = 0;   //当前放入背包的物品总价值

int BestValue = 0;  //最优值;当前的最大价值,初始化为0
int BestX[N];       //最优解;BestX[i]=1代表物品i放入背包,0代表不放入

//t = 0 to N-1
void backtrack(int t)
{
    //叶子节点,输出结果
    if(t>N-1) 
    {
        //如果找到了一个更优的解
        if(CurValue>BestValue)
        {
            //保存更优的值和解
            BestValue = CurValue;
            for(int i=0;i<N;++i) BestX[i] = x[i];
        }
    }
    else
    {
        //遍历当前节点的子节点:0 不放入背包,1放入背包
        for(int i=0;i<=1;++i)
        {
            x[t]=i;

            if(i==0) //不放入背包
            {
                backtrack(t+1);
            }
            else //放入背包
            {
 <span style="white-space:pre">             </span>//约束条件:放的下
                if((CurWeight+w[t])<=C)
                {
<span style="white-space:pre">                  </span>CurWeight += w[t];
                    CurValue += v[t];
                    backtrack(t+1);
                    CurWeight -= w[t];
                    CurValue -= v[t];
                }
            }
        }
        //PS:上述代码为了更符合递归回溯的范式,并不够简洁
    }
}

int main(int argc, char* argv[])
{
    backtrack(0);

    printf("最优值:%d\n",BestValue);

    for(int i=0;i<N;i++)
    {
       printf("最优解:%-3d",BestX[i]);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/L_i_n_o/article/details/79583007