回溯法加剪枝解决01背包问题(C++)

01 背包问题: knapsack 是解决如何将一个背包的价值最大划的问题 

输入:  c 背包最大容量,w[] 物品的重量 ,v[] 物品的价值 

输出:bestv 最大的可放置在背包内的物品价值总和,bestX[] 对应bestp的物品取法,即最优值和最优解

例如:输入:c = 30, w[] = {20,15,15} v[] = {40,25,25}, 

           则输出:bestv = 50, bestX[] = {0,1,1}

不难看出,这个问题属于NPC问题,不存在多项式复杂度的解法,通常我们会用动态规划(Dynamic Programming)解,此处我们用回溯法。回溯法本质上是深度优先搜索(DFS)。我们可以把所有的解决离散问题的算法都看做在答案的一个子空间进行搜索的过程。而在knapsack问题中,这个子空间既是高度为n(n为物品数量)的二叉树,因为对于每个物品我们有两种选择,选或者不选。 

剪枝:剪枝的意思是在这个搜索树中,我们可以在进入某一个节点之前大致估计一下可能的最好结果,如果最好结果没有当前最好结果好,我们可以不进入这个节点,同时将该节点的子树减掉。

运行结果:

剪枝

baoergutes-MacBook-Pro:Desktop baoergute$ ./knapsack
List:
value 40 weight: 20 value per weight:2
value 25 weight: 15 value per weight:1.66667
value 25 weight: 15 value per weight:1.66667

After sorting:
value 40 weight: 20 value per weight:2
value 25 weight: 15 value per weight:1.66667
value 25 weight: 15 value per weight:1.66667

b:56.6667
b:40
b:50
b:25
b:25
Best Value is: 50
Node Count is: 7
item with value: 25 weight: 15
item with value: 25 weight: 15

未剪枝

baoergutes-MacBook-Pro:Desktop baoergute$ ./knapsack_org
List:
value 40 weight: 20 value per weight:2
value 25 weight: 15 value per weight:1.66667
value 25 weight: 15 value per weight:1.66667

Best Value is: 50
Node Count is: 11
item with value: 25 weight: 15
item with value: 25 weight: 15

值得一提的是,剪枝并不改变算法复杂度,理论上来讲,在最坏情况下剪枝也不能提升实际的运行速度

// 未剪枝版本
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

int c = 30;    //背包容量
const int n = 3;     //对象数目
int w[] = {20, 15, 15};  //对象重量数组
int v[] = {40, 25, 25};   //对象收益数组
int cw = 0;     //当前背包重量
int cv = 0;     //当前背包价值
double bestv = 0;  //迄今最大的收益
int node_count = 0; //当前遍历过的节点数
int bestX[n];  //迄今最佳选择
int X[n];      //当前选择

void getBest (int i, vector<pair<double, int> > l) {
    node_count += 1;
    if (i >= n) {
        if (cv >= bestv) {
            bestv = cv;
	    for (int k = 0; k < n; k++) {
		bestX[k] = X[k];
	    }
	}
	return;
    }
    // left child
    if (cw + (l[i]).second <= c) {
        X[i] = 1;
        cw += (l[i]).second;
        cv += (l[i]).first;
        getBest(i + 1, l);
        // 此处要减去
        cw -= (l[i]).second;
        cv -= (l[i]).first;
    }
     // right child
     X[i] = 0;
     getBest(i + 1, l);    
}

int main() {
    // store the items into a vector of pairs
    // 此处课本定义了一个类来存每个物品 而此处我们用vector of pairs
    vector <pair<double, int> > value_weight_list;
    for (int i = 0; i < n; i++) {
	pair<double, int> value_weight_pair(v[i], w[i]);
	value_weight_list.push_back(value_weight_pair);
    }
    cout << "List:" << endl;
    for (vector<pair<double, int> >::iterator itr = value_weight_list.begin(); itr != value_weight_list.end(); itr++) {
	cout << "value " << (*itr).first << " weight: " << (*itr).second << " value per weight:" 
	    << 1.0 * (*itr).first / (*itr).second << endl;
    }
    cout << endl;
    
    getBest(0, value_weight_list);
    cout << "Best Value is: " << bestv << endl;
    cout << "Node Count is: " << node_count << endl;
    for (int i = 0; i < n; i++) {
	if (bestX[i] == 1) {
	    cout << "item with value: " << (value_weight_list[i]).first 
		<< " weight: " << (value_weight_list[i]).second << endl;
	}
    }
    cout << endl;    

    return 0;
}
// 剪枝版本

double Bound(int i, vector<pair<double, int> > l) {
    double cleft = c - cw;
    double b = cv;
    // 此处课本为 i <= n
    while (i < n && (l[i]).second <= cleft) {
	cleft -= (l[i]).second;
        b += (l[i]).first;
        i++;
    }
    // 拆分
    if (i < n) {
        double tmp = 1.0 * (l[i]).first / (l[i]).second;
        b += 1.0 * tmp * cleft;
    }
    cout << "b:" << b << endl;
    return b;
}    

void getBest (int i, vector<pair<double, int> > l) {
    node_count += 1;
    if (i >= n) {
        if (cv >= bestv) {
            bestv = cv;
	    for (int k = 0; k < n; k++) {
		bestX[k] = X[k];
	    }
	}
	return;
    }
    // left child
    if (cw + (l[i]).second <= c) {
        X[i] = 1;
        cw += (l[i]).second;
        cv += (l[i]).first;
        getBest(i + 1, l);
        cw -= (l[i]).second;
        cv -= (l[i]).first;
    }
    // 判断剪枝
    if (Bound(i+1, l) > bestv) {
        // right child
        X[i] = 0;
        getBest(i + 1, l);
    }
}

// 自定义比较运算符
bool MyComparator_dsc (pair<double, int> a, pair<double, int> b) {
    return (1.0 * a.first / a.second >= 1.0 * b.first / b.second);
}

int main() {
    // store the items into a vector of pairs
    vector <pair<double, int> > value_weight_list;
    for (int i = 0; i < n; i++) {
	pair<double, int> value_weight_pair(v[i], w[i]);
	value_weight_list.push_back(value_weight_pair);
    }
    cout << "List:" << endl;
    for (vector<pair<double, int> >::iterator itr = value_weight_list.begin(); itr != value_weight_list.end(); itr++) {
	cout << "value " << (*itr).first << " weight: " << (*itr).second << " value per weight:" 
	    << 1.0 * (*itr).first / (*itr).second << endl;
    }
    cout << endl;
    // sort the list
    // 此处我们用了自定义的运算符,按照性价比降序排列
    sort(value_weight_list.begin(), value_weight_list.end(), MyComparator_dsc);
    // print the sorted list
    cout << "After sorting:" << endl;
    for (vector<pair<double, int> >::iterator itr = value_weight_list.begin(); itr != value_weight_list.end(); itr++) {
	cout << "value " << (*itr).first << " weight: " << (*itr).second << " value per weight:" 
	    << 1.0 * (*itr).first / (*itr).second << endl;
    }
    cout << endl;

    getBest(0, value_weight_list);
    cout << "Best Value is: " << bestv << endl;
    cout << "Node Count is: " << node_count << endl;
    for (int i = 0; i < n; i++) {
	if (bestX[i] == 1) {
	    cout << "item with value: " << (value_weight_list[i]).first 
		<< " weight: " << (value_weight_list[i]).second << endl;
	}
    }
    cout << endl;    

    return 0;
}

    原文作者:回溯法
    原文地址: https://blog.csdn.net/weixin_40184101/article/details/78970586
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞