算法设计与分析课-实验-回溯法

算法设计与分析课-实验-回溯法

第一题

装载问题:

回溯的定义:回溯法就是一步一步的向下进行,当遇到非法的情况时回退一步或多步,尝试别的路径与方法。大致步骤如下:

  • 1、判断当前情况是否非法,非法则返回

  • 2、判断当前情况是否满足递归结束条件,满足则保存当前结果并返回

  • 3、在当前情况的基础上遍历所有可能出现的下一情况并进行尝试

  • 4、递归完毕立即回溯,即除去前一次的尝试操作

对于本题,先判断c1最多装多少,然后把剩余的装到c2上,如果能装下则输出结果,反之输出No Solution。

用回溯法判断c1最多装多少就是寻找n个集装箱随机组合成最接近于c1的重量的组合,记当前重量为currentw,最优重量为best,最优组合数组为bestw,记录已装入集装箱的数组为v,当满足递归结束条件且currentw>best时,用数组v更新bestw。

解题代码:

#include<iostream>
#include<cstring>
#define maxn 1000+5
using namespace std;
int c1,c2;
int w[maxn];
int n;
int currentw;
int best;
int bestw[maxn];
int v[maxn];
int s[maxn];
//利用回溯法解决问题,先装c1,剩下的装c2能装多少装多少,装不下则为No Solution
//回溯首先判定是否已经满足条件,满足则return,否则进行遍历
void backtrack(int t, int c);

int main()
{
    cin>>n>>c1>>c2;
    for(int i=0; i<n; ++i) cin>>w[i];
    memset(v, 0, sizeof(v));
    memset(bestw, 0, sizeof(bestw));
    memset(s, 0, sizeof(s));
    currentw=0;
    best=0;
    backtrack(0, c1);
    int tmp=0;
    for(int i=0; i<n; ++i)
    {
        cout<<bestw[i]<<endl;
        if(bestw[i]==0)
        {
            s[i]=1;
            tmp+=w[i];
        }
    }
    cout<<best<<endl;
    if(tmp<=c2)
    {
        for(int i=0; i<n; ++i)
            if(bestw[i]) cout<<i+1<<" ";
        cout<<endl;
        for(int i=0; i<n; ++i)
            if(s[i]) cout<<i+1<<" ";
        cout<<endl;
    }
    else
    {
        cout<<"No Solution!"<<endl;
    }
    return 0;
}


void backtrack(int t, int c)
{
    if(t+1==n)
    {
        if(currentw>best)
        {
            best=currentw;
            for(int i=0; i<n; ++i) bestw[i]=v[i];
        }
        else
        {
            return ;
        }
    }
    else
    {
        for(int i=0; i<n; ++i)
        {
            if(!v[i])
            {
                if(currentw+w[i]<=c)//这里可以等于,这一点迷了
                {
                    v[i]=1;
                    currentw+=w[i];
                    backtrack(t+1, c);
                    currentw-=w[i];
                    v[i]=0;
                }
                else
                {
                    //v[i]=0;
                    backtrack(t+1, c);
                }
            }
        }
        /*此处的另一种写法,但是该解法的生成树是从第一个能装入c1的货物开始的,感觉不太严谨 但是好像又没错。
        if(currentw+w[t]<=c)
        {
            currentw+=w[t];
            v[t]=1;
            backtrack(t+1, c);
            currentw-=w[t];
            v[t]=0;
        }
        else
        {
            v[t]=0;//这里置零与否无所谓
            backtrack(t+1, c);
        }
        */
    }
}

for循环中先判断v[i]再判断currentw+w[i]<=c(这里是<=c,不是只有<,这点迷了一会儿),之所以这样判断而不是写在一个if里面判断,因为如果一起判断,那么在进行递归的时候会出现死循环,例如第一个可以装载,那么v[0]为1,然后进行递归backtrack(t+1,c),但是for循环还是从0开始的,v[0]为1,条件不成立直接执行else中的内容,那么直接什么也不做就进行下一次递归,这样只要第一个装载了就是死循环,所以要分开判断(这点也迷了)。

另外注释掉的那种写法,他是一定从第一个物品开始判断是否装载,如果第一个装载了那就会一直占着位置而不会释放,因为他只有一遍操作,由于本题例子给的数组是有序的所以答案正确,但是换其他的例子可能无法得正确解。而且,这种写法会得到一个最大值,但不一定是这n个物品随机组合的最大的接近c的值,因为只要第一个物品可以装载,他就装载了且不会释放第一个物品,但是答案并不一定需要这个最重的物品。(仅是个人观点,感觉这个解法是不太严谨)

posted @ 2022-10-11 21:54  HD0117  阅读(58)  评论(0编辑  收藏  举报