【0-1背包问题】“回溯法”——《算法设计与分析(第五版)》

文章目录

  • 一、算法要求
    • 1. 思路
    • 2. 示例
  • 二、完整代码
    • 1. 主文件
    • 2. 头文件
    • 3. 效果展示
  • 三、补充


一、算法要求

假设n个物品和1个购物车,每个物品i对应价值为v;,重量w;,购物车的容量为W(你也可以将重量设定为体积)。每个物品只有一件,要么装入,要么不装入,不可拆分。如何选取物品装入购物车,使购物车所装入的物品的总价值最大?要求输出最优值(装入的最大价值)和最优解(装入了哪些物品)。

1. 思路

根据题意,从n个物品中选择一些物品,相当于从n个物品组成的集合S中找到一个子集,这个子集内所有物品的总重量不超过购物车容量,并且这些物品的总价值最大。S的所有的子集都是问题的可能解,这些可能解组成了解空间,我们在解空间中找总重量不超过购物车容量且价值最大的物品集作为最优解。
这些由问题的子集组成的解空间,其解空间树称为子集树。

2. 示例

问题的解空间描述了2n种可能解,也可以说是n个元素组成的集合所有子集个数。例如3个物品的购物车问题,解空间是: {0,0,0},{0,0,1},{0,1,0},{0,1,1},{1,0,0},{1,0,1},{1,1,0},{1,1,1}。该问题有23个可能解。
可见,问题的解空间树为子集树,解空间树的深度为问题的规模n。

【0-1背包问题】“回溯法”——《算法设计与分析(第五版)》_第1张图片


二、完整代码

1. 主文件

main.cpp:

// Project4_2: 0-1背包问题

#include"Basic2.h"

int main() {

    int begin = 1;
    //控制台
    Console();
    //核心算法
    UnitValueSort();
    BackTracking(begin);

    //结果展示
    cout << "\n\nThe maximum total value of the items in the backpack is:" 
        << bestValue;
    cout << "\nThe selected item situation is:";
    for (int i = 1; i <= numItem; i++) {
        if (judgeArray[i] == 1)
            cout << setw(3) << orderID[i];
    }
    cout << endl;
    return 0;
}

2. 头文件

Basic2.h:

#pragma once

#ifndef __BASIC2__
#define __BASIC2__

#include 
#include 
#include 
#define N 100
using namespace std;

int numItem = 5,        //物品数量
    capacityPack = 10,  //背包容量
    weightItem[] = { 0, 2, 2, 6, 5, 4 },    //物品重量
    valueItem[] = { 0, 6, 3, 5, 4, 6 },     //物品价值
    orderID[N],         //物品编号
    judgeArray[N];      //设置是否装入,为1的时候表示选择该组数据装入,为0的表示不选择该组数据

double curWeight = 0.0, //当前背包重量 current weight
       curValue = 0.0,  //当前背包中物品总价值 current value
       bestValue = 0.0, //当前最优价值best price
       perValue[N];     //单位物品价值(排序后) per price

void Console() {
    cout << "The following are the default items’quantity, valueand weightand backpack capacity : \n"
        << "\n#Number of items:" << setw(3) << numItem
        << "\n#Number of backpacks:" << setw(3) << capacityPack;

    cout << "\n#Corresponding weight: ";
    for (int i = 1; i < numItem + 1; i++) {
        cout << setw(3) << weightItem[i];
        orderID[i] = i;
    }

    cout << "\n#Corresponding value: ";
    for (int i = 1; i < numItem + 1; i++)
        cout << setw(3) << valueItem[i];
}

//按单位价值排序
void UnitValueSort()
{
    int i, j,
        temporder = 0,
        temp = 0;

    for (i = 1; i <= numItem; i++)
        perValue[i] = valueItem[i] / weightItem[i]; //计算单位价值(单位重量的物品价值)
    for (i = 1; i <= numItem - 1; i++) {
        for (j = i + 1; j <= numItem; j++)
            if (perValue[i] < perValue[j]) {    //冒泡排序perp[],order[],sortv[],sortw[]
                temp = perValue[i];             //冒泡对perp[]排序
                perValue[i] = perValue[j];
                perValue[j] = temp;

                temporder = orderID[i];         //冒泡对orderID[]排序
                orderID[i] = orderID[j];
                orderID[j] = temporder;

                temp = valueItem[i];            //冒泡对valueItem[]排序
                valueItem[i] = valueItem[j];
                valueItem[j] = temp;

                temp = weightItem[i];           //冒泡对weightItem[]排序
                weightItem[i] = weightItem[j];
                weightItem[j] = temp;
            }
    }
}

//回溯函数
void BackTracking(int i) {
    //i用来指示到达的层数(第几步,从0开始),同时也指示当前选择玩了几个物品
    double Boundary(int i);
    if (i > numItem) {                  //递归结束的判定条件
        bestValue = curValue;
        return;
    }

    //如若左子节点可行,则直接搜索左子树;
    //对于右子树,先计算上界函数,以判断是否将其减去
    if (curWeight + weightItem[i] <= capacityPack) {//将物品i放入背包,搜索左子树
        curWeight += weightItem[i];     //同步更新当前背包的重量
        curValue += valueItem[i];       //同步更新当前背包的总价值
        judgeArray[i] = 1;
        BackTracking(i + 1);            //深度搜索进入下一层
        curWeight -= weightItem[i];     //回溯复原
        curValue -= valueItem[i];       //回溯复原
    }
    if (Boundary(i + 1) > bestValue)       //如若符合条件则搜索右子树
        BackTracking(i + 1);
}

//计算上界函数,功能为剪枝
double Boundary(int i)
{   //判断当前背包的总价值cp+剩余容量可容纳的最大价值<=当前最优价值
    double leftw = capacityPack - curWeight;//剩余背包容量
    double sumValue = curValue;//记录当前背包的总价值cp,最后求上界
    //以物品单位重量价值递减次序装入物品
    while (i <= numItem && weightItem[i] <= leftw)
    {
        leftw -= weightItem[i];
        sumValue += valueItem[i];
        i++;
    }
    //装满背包
    if (i <= numItem)
        sumValue += valueItem[i] / weightItem[i] * leftw;
    return sumValue;   //返回计算出的上界

}

#endif

3. 效果展示

【0-1背包问题】“回溯法”——《算法设计与分析(第五版)》_第2张图片


三、补充

(1) 时间复杂度
回溯法的运行时间取决于它在搜索过程中生成的结点数。而限界函数可以大大减少所生成的的结点个数,避免无效搜索,加快搜索速度。
约束函数时间复杂度为O(1),限界函数时间复杂度为O(n)。最坏情况下有O(2")个左孩子结点调用约束函数,有O(2n)个右孩子结点需要调用限界函数,故回溯法解决购物车问题的时间复杂度为O(1×2n+n×2n)=O(n×2n)。
(2) 空间复杂度
回溯法的另一个重要特性就是在搜索执行的同时产生解空间。在所搜过程中的任何时刻,仅保留从开始结点到当前扩展结点的路径,从开始结点起最长的路径为n。程序中我们使用bestp[]数组记录该最长路径作为最优解,所以该算法的空间复杂度为O(n)。

文档供本人学习笔记使用,仅供参考。

你可能感兴趣的:(《算法设计与分析(第五版)》,算法,动态规划,贪心算法)