算法学习(2):回溯法

回溯法一般解题步骤:

  1. 针对所给问题的解空间,首先应明确定义问题的解空间,问题的解空间应至少包含问题的一个最优解。
  2. 确定结点的扩展搜索规则。
  3. 以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。

结点扩展:类似暴力法的深度优先搜索的扩展方式。

剪枝:用约束函数剪不满足约束条件的子树,用界限函数剪除不能得到最优解的子树。

回溯法的非递归框架

int x[n],i;          //x存放解向量,全局变量
初始化数组x[];
i = 1;
while (i > 0 (有路可走) and (未达到目标)) {  //还未回溯到头
    if (i > n) {                       //搜索到叶子结点
        搜索到1个解,输出;
    }
    else                             //处理第i个元素
    {
        x[i]取第1个可能的值;
        while (x[i]不满足约束条件且在搜索空间内) {
            x[i]下一个可能的值;
        }
        if (x[i]在搜索空间内) {
            标识占用的资源;
            i = i + 1;
        }
        else
        {
            清理所占用的状态空间;         //回溯
            i = i - 1;
        }
    }
}

子集树和排列树

1. 子集树

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

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

void backtrack(int i) //i为树的根
{  
    if (i > n)  //达到叶子结点
        output(x);   //找到一个解
    else  
    {
        for (int j=0; j<=1; j++) {  //每个结点只有两个子树 
            x[i]=j;  
            if (constraint(i) && backtrack(i))
                backtrack(i+1);  
        }
    }
}

2. 排列树

      所给的问题是确定n个元素满足某种性质的排列时,相应的解空间就是排列树。
如旅行售货员问题,一个售货员把几个城市旅行一遍,要求走的路程最小。它的解就是几个城市的排列,解空间就是排列树。
      回溯法搜索排列树的算法范式如下:

void backtrack (int i)  
{  
    if (i>n)
    {
        output(i);
    }
    else
    {
        for (int j=i;i<=n;i++)
        {  //为了保证每个元素的不同,通过x[i], x[j]来交换实现
            swap(x[i], x[j]);  
            if (constraint(i)) backtrack(i+1);  
            swap(x[i], x[j]);  //回复
        }          
    } 
} 

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),如果子节点满足剪枝条件,就继续回溯搜索子节点。

#include <stdio.h>
#define N 4         //物品的数量
#define C 7        //背包的容量

int w[N]={5, 3, 2, 1};   //每个物品的重量
int v[N]={4, 4, 3, 1};   //每个物品的价值
int x[N]={0, 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)
    {
        //如果找到了一个更优的解
        if(CurValue > BestValue)
        {
            //保存更优的值和解
            BestValue = CurValue;
            for(int i=0; i<N; ++i) BestX[i] = x[i];
        }
    }
    else
    {
        x[t]=1;
        //约束条件:放的下
        if((CurWeight + w[t]) <= C) //不超过背包承受的重量
        {
            CurWeight += w[t];
            CurValue  += v[t];
            backtrack(t+1);
            CurWeight -= w[t];
            CurValue  -= v[t];
        }
        x[t] = 0;//回溯
        backtrack(t+1);
    }
}
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;
}

求n个元素中m个元素的全排列

如:1,2,3,当m = 2 时,全排列为:(1,2),(1,3),(2,3)

#include <stdio.h>
#include <string.h>
#define MAXN 20
#define MAXM 10
int m, n;
int  x[MAXM];          //用装载一个排列
bool used[MAXN];       //表示第i个元素是否在当前排列中
int  mcount = 0;       //统计部分全排列的个数
void partperm(int i)   //求n个元素中m个元素的全排列
{
    int j;
    if (i > m - 1) {
        printf("[");
        for (j = 0; j < m; ++j)
            printf("%d", x[j]);//输出一个排列
        printf("] ");
        if (++mcount % 5 == 0) printf("\n");
    } else {
        for (j = 0; j < n; ++j) {
            if (!used[j]) {
                used[j] = true;
                x[i] = j;
                partperm(i + 1);
                used[j] = false;
            }
        }
    }
}
int main(int argc, char* argv[])
{
    m = 2;
    n = 5;
    memset(used, 0, sizeof(used));
    partperm(0);
    printf("cout = %d\n", mcount);
    return 0;
}

输出:

[01] [02] [03] [04] [10]
[12] [13] [14] [20] [21]
[23] [24] [30] [31] [32]
[34] [40] [41] [42] [43]
cout = 20

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值