【回溯法】--01揹包問題

轉載自:https://blog.csdn.net/qian2213762498/article/details/79420269

【回溯法】--01揹包問題

1、問題描述

  給定n種物品和一揹包。物品i的重量是wi>0,其價值爲vi>0,揹包的容量爲c。問應如何選擇裝入揹包中的物品,使得裝入揹包中物品的總價值最大? (要求使用回溯法)

 例如:

2、算法分析

【整體思路】

  01揹包屬於找最優解問題,用回溯法需要構造解的子集樹。對於每一個物品i,對於該物品只有選與不選2個決策,總共有n個物品,可以順序依次考慮每個物品,這樣就形成了一棵解空間樹: 基本思想就是遍歷這棵樹,以枚舉所有情況,最後進行判斷,如果重量不超過揹包容量,且價值最大的話,該方案就是最後的答案。

      在搜索狀態空間樹時,只要左子節點是可一個可行結點,搜索就進入其左子樹。對於右子樹時,先計算上界函數,以判斷是否將其減去(剪枝)。

  上界函數bound():當前價值cw+剩餘容量可容納的最大價值<=當前最優價值bestp。 

    爲了更好地計算和運用上界函數剪枝,選擇先將物品按照其單位重量價值從大到小排序,此後就按照順序考慮各個物品。

【舉例說明】

  對於n=4的0/1揹包問題,其解空間樹如圖所示,樹中的16個葉子結點分別代表該問題的16個可能解。 

【算法設計】

    利用回溯法試設計一個算法求出0-1揹包問題的解,也就是求出一個解向量xi (即對n個物品放或不放的一種的方案)

 其中, (xi = 0 或1,xi = 0表示物體i不放入揹包,xi =1表示把物體i放入揹包)。

在遞歸函數Backtrack中,
    當i>n時,算法搜索至葉子結點,得到一個新的物品裝包方案。此時算法適時更新當前的最優價值
    當i<n時,當前擴展結點位於排列樹的第(i-1)層,此時算法選擇下一個要安排的物品,以深度優先方式遞歸的對相應的子樹進行搜索,對不滿足上界約束的結點,則剪去相應的子樹。

【時間複雜度&&優化】  

  因爲物品只有選與不選2個決策,而總共有n個物品,所以時間複雜度爲

  因爲遞歸棧最多達到n層,而且存儲所有物品的信息也只需要常數個一維數組,所以最終的空間複雜度爲O(n)。

相關鏈接1

相關鏈接2

相關鏈接3

【源代碼】  

#include <iostream>
#include <stdio.h>
//#include <conio.h>
using namespace std;
int n;//物品數量
double c;//揹包容量
double v[100];//各個物品的價值 value
double w[100];//各個物品的重量 weight
double cw = 0.0;//當前揹包重量 current weight
double cp = 0.0;//當前揹包中物品總價值 current value
double bestp = 0.0;//當前最優價值best price
double perp[100];//單位物品價值(排序後) per price
int order[100];//物品編號
int put[100];//設置是否裝入,爲1的時候表示選擇該組數據裝入,爲0的表示不選擇該組數據
 
 
//按單位價值排序
void knapsack()
{
    int i,j;
    int temporder = 0;
    double temp = 0.0;
 
    for(i=1;i<=n;i++)
        perp[i]=v[i]/w[i]; //計算單位價值(單位重量的物品價值)
    for(i=1;i<=n-1;i++)
    {
        for(j=i+1;j<=n;j++)
            if(perp[i]<perp[j])//冒泡排序perp[],order[],sortv[],sortw[]
        {
            temp = perp[i];  //冒泡對perp[]排序
            perp[i]=perp[i];
            perp[j]=temp;
 
            temporder=order[i];//冒泡對order[]排序
            order[i]=order[j];
            order[j]=temporder;
 
            temp = v[i];//冒泡對v[]排序
            v[i]=v[j];
            v[j]=temp;
 
            temp=w[i];//冒泡對w[]排序
            w[i]=w[j];
            w[j]=temp;
        }
    }
}
 
//回溯函數
void backtrack(int i)
{   //i用來指示到達的層數(第幾步,從0開始),同時也指示當前選擇玩了幾個物品
    double bound(int i);
    if(i>n) //遞歸結束的判定條件
    {
        bestp = cp;
        return;
    }
    //如若左子節點可行,則直接搜索左子樹;
    //對於右子樹,先計算上界函數,以判斷是否將其減去
    if(cw+w[i]<=c)//將物品i放入揹包,搜索左子樹
    {
        cw+=w[i];//同步更新當前揹包的重量
        cp+=v[i];//同步更新當前揹包的總價值
        put[i]=1;
        backtrack(i+1);//深度搜索進入下一層
        cw-=w[i];//回溯復原
        cp-=v[i];//回溯復原
    }
    if(bound(i+1)>bestp)//如若符合條件則搜索右子樹
        backtrack(i+1);
}
 
//計算上界函數,功能爲剪枝
double bound(int i)
{   //判斷當前揹包的總價值cp+剩餘容量可容納的最大價值<=當前最優價值
    double leftw= c-cw;//剩餘揹包容量
    double b = cp;//記錄當前揹包的總價值cp,最後求上界
    //以物品單位重量價值遞減次序裝入物品
    while(i<=n && w[i]<=leftw)
    {
        leftw-=w[i];
        b+=v[i];
        i++;
    }
    //裝滿揹包
    if(i<=n)
        b+=v[i]/w[i]*leftw;
    return b;//返回計算出的上界
 
}
 
 
 
int main()
{
    int i;
    printf("請輸入物品的數量和揹包的容量:");
    scanf("%d %lf",&n,&c);
    /*printf("請輸入物品的重量和價值:\n");
    for(i=1;i<=n;i++)
    {
        printf("第%d個物品的重量:",i);
        scanf("%lf",&w[i]);
        printf("第%d個物品的價值是:",i);
        scanf("%lf",&v[i]);
        order[i]=i;
    }*/
    printf("請依次輸入%d個物品的重量:\n",n);
    for(i=1;i<=n;i++){
        scanf("%lf",&w[i]);
        order[i]=i;
    }
 
    printf("請依次輸入%d個物品的價值:\n",n);
    for(i=1;i<=n;i++){
        scanf("%lf",&v[i]);
    }
 
 
    knapsack();
    backtrack(1);
 
 
    printf("最優價值爲:%lf\n",bestp);
    printf("需要裝入的物品編號是:");
    for(i=1;i<=n;i++)
    {
        if(put[i]==1)
            printf("%d ",order[i]);
    }
    return 0;
}

 

發佈了17 篇原創文章 · 獲贊 11 · 訪問量 2384
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章