HNU_算法_实验3(2021级)-经典案例2-回溯法求解0-1背包

一、实验名称

回溯法求解0-1背包:

(1)问题描述:回溯法求解0-1背包问题

(2)给定一个背包,容量为c,对于n件物品,每件物品i的价值v[i],重量w[i],每件物品可以选择放或不放,求在不超过背包容量的情况下能放进背包的物品最大价值

二、实验目的

        通过上机实验,要求掌握回溯法求解 0-1 背包问题的问题描述、算法设计思想、程序设计。

三、实验原理

        利用回溯法求解 0-1 背包问题,并计算出程序运行所需要的时间。

四、实验步骤

        0-1背包问题的解空间可用子集树表示。

        在搜索解空间的时,只要其左儿子节点是一个可行节点,搜索就进去其左子树(约束条件)。

        当右子树中可能包含最优解时才进入右子树搜索(限界函数)。否则就将右子树剪去。

        计算右子树中解的上界的更好方法是将剩余物品依其单位重量价值排序,然后依次装入物品,直至装不下时,再装入物品的一部分而装满背包。由此得到的价值是右子树中解的上界。

        为了便于计算上界,可先将物品按照单位重量价值由大到小排序,此后,只要按照顺序考察各个物品即可。

        在实现时,由Bound计算当前结点处的上界。在解空间树的当前扩展结点处,仅当要进入右子树时才计算右子树的上界Bound,以判断是否将右子树剪去。

     进入左子树时不需要计算上界,因为其上界与其父节点上界相同。

举例:

假设有4个物品,物品的价值分别为p=[9, 10, 7, 4], 重量分别为w=[3, 5, 2, 1], 背包容量C=9,中间被剪掉的结点用×标记。

单位重量价值:[p1/w1, p2/w2, p3/w3, p4/w4] =(3, 2, 3.5, 4)

按照物品单位重量价值由大到小排序:

深度优先搜索解空间树:

左儿子是可行节点,才搜索进入左子树,否则将左子树剪掉;

右子树包含最优解,才搜索右子树,否则将右子树剪掉。

五、关键代码

1.计算up值

int Bound(int i,int n,int c,vector<Object> &O)
{
    int temp_cw = c - cw;
    int temp_cp = cp;
    while(i <= n && temp_cw >= O[i].w)
    {
        temp_cw -= O[i].w;
        temp_cp += O[i].p;
        ++i;
    }
    //装满背包
    if(i <= n)
        temp_cp += O[i].p / O[i].w * temp_cw;
    return temp_cp;

}

2.回溯 

void Backtrack(int i,int n,int c,vector<int> &x,vector<int> &best_x,vector<Object> &O)
{
    if(i > n)
    {
        bestp = cp;
        for(int i = 1; i <= n; ++i)
            best_x[i] = x[i];
        return;
    }
    if(cw + O[i].w <= c)
    {
        x[O[i].id] = 1;
        cw += O[i].w;
        cp += O[i].p;
        Backtrack(i + 1,n,c,x,best_x,O);
        cp -= O[i].p;
        cw -= O[i].w;
        x[O[i].id] = 0;
    }
    //向右求解的约束条件
    if(Bound(i + 1,n,c,O) > bestp)
    {
        x[O[i].id] = 0;
        Backtrack(i + 1,n,c,x,best_x,O);
    }
}

3.生成不同规模样例

//生成规模为n的随机数 
		cout<<"请输入数据规模n:"<<endl; 
		int n,c;
		cin>>n;
		ofstream out("input1.txt");
		out<<n<<'\n';
		srand((unsigned)time(NULL));
		// c (a,b]
		int a=0,b=100;
		out<< (rand() % (b-a))+ a + 1<<'\n';
		// v数组 
		for(int i=0;i<n;i++){
			out<<rand()<<' ';
			if((i+1)%10==0) out<<'\n';		}
		out<<'\n';
		// w数组 (a,b]
		for(int i=0;i<n;i++){
			out<< (rand() % (b-a))+ a + 1<<' ';
			if((i+1)%10==0) out<<'\n';		}
		out.close();

六、测试结果

                

时间复杂度:O(2^n)

回溯法求解 0-1 背包问题的时间复杂度取决于搜索空间的大小。在最坏情况下,回溯法需要考虑所有可能的解,因此其时间复杂度是指数级别的。

对于每个物品,都有两种选择:放入背包或不放入背包。在递归的过程中,会对这些选择进行穷举,导致指数级别的搜索。具体来说,回溯法的时间复杂度可以表示为 O(2^n )。

n从1-20000,每次递增100的测试结果:

自定义输入n的数值的测试结果:

七、实验心得

        通过这次实验,我更为掌握了回溯算法,并且对于0-1背包问题求解的过程和原理有了更加清楚的认识。在自己生成案例并测试运行时间的过程中,熟悉了随机化算法和运行时间的计算。

​        实验可改进的地方:随机化过程的加入可能是的不同规模的测试数据与理论值有偏差,可以通过每个输入规模多次测试来减小误差。

        

八、完整代码

#include <iostream>
#include <fstream>
#include <windows.h>
#include <time.h>
#include <vector>
#include <algorithm> 
using namespace std;

using namespace std;

class Object
{
public:
    int p;   //价值
    int w;   //重量
    int id;  //物品的id
};

//当前价值 当前重量 最优值  总重量 总价值
int cp, cw, bestp, total_w, total_p;
bool cmp(const Object &a, const Object &b)
{
    return a.p / a.w > b.p / b.w;
}


//计算up值
int Bound(int i,int n,int c,vector<Object> &O)
{
    int temp_cw = c - cw;
    int temp_cp = cp;
    while(i <= n && temp_cw >= O[i].w)
    {
        temp_cw -= O[i].w;
        temp_cp += O[i].p;
        ++i;
    }
    //装满背包
    if(i <= n)
        temp_cp += O[i].p / O[i].w * temp_cw;
    return temp_cp;


}

void Backtrack(int i,int n,int c,vector<int> &x,vector<int> &best_x,vector<Object> &O)
{
    if(i > n)
    {
        bestp = cp;
        for(int i = 1; i <= n; ++i)
            best_x[i] = x[i];
        return;
    }
    if(cw + O[i].w <= c)
    {
        x[O[i].id] = 1;
        cw += O[i].w;
        cp += O[i].p;
        Backtrack(i + 1,n,c,x,best_x,O);
        cp -= O[i].p;
        cw -= O[i].w;
        x[O[i].id] = 0;
    }
    //向右求解的约束条件
    if(Bound(i + 1,n,c,O) > bestp)
    {
        x[O[i].id] = 0;
        Backtrack(i + 1,n,c,x,best_x,O);
    }
}

void OutPut(int n,vector<int> best_x)
{
    cout<<"bestp = "<<bestp<<endl;;
    cout<<"选取的物品为 : ";
    for(int i = 1; i <= n; ++i)
    {
        if(best_x[i] == 1)
            cout << i << " ";
    }
}

int main(int argc, char** argv) {
	ofstream out1("output.txt");
	//int n=20000,c;
	while(1){
		//生成规模为n的随机数 
		cout<<"请输入数据规模n:"<<endl; 
		int n,c;		
		cin>>n;
		ofstream out("input1.txt");
		out<<n<<'\n';
		srand((unsigned)time(NULL));
		// c (a,b]
		int a=0,b=100;
		out<< (rand() % (b-a))+ a + 1<<'\n';
		// v数组 
		for(int i=0;i<n;i++){
			out<<rand()<<' ';
			if((i+1)%10==0) out<<'\n';
		}
		out<<'\n';
		// w数组 (a,b]
		for(int i=0;i<n;i++){
			out<< (rand() % (b-a))+ a + 1<<' ';
			if((i+1)%10==0) out<<'\n';
		}
		out.close();
	
		int i,maxi,mini;
		LARGE_INTEGER nFreq,nBegin,nEnd;
		double time; 
		
		ifstream in("input1.txt");
		//物品数量 背包容量
		in>>n>>c;
		vector <Object> o(n+1);
		for(i=1;i<=n;i++){
			in>>o[i].p;
			o[i].id=i;
		}
		for(i=1;i<=n;i++){
			in>>o[i].w;
		}
		
		cp = cw = total_w = total_p = bestp = 0;
		for(int i = 1; i <= n; ++i)
    	{
        	total_p += o[i].p;
        	total_w += o[i].w;
   		}
		vector<int> x(n+1);
		vector<int> best_x(n+1);    //记录暂时选取状态  记录最优选取状态
		QueryPerformanceFrequency(&nFreq);	
		QueryPerformanceCounter(&nBegin);		
	    //依照物品单位重量价值排序
	    sort(o.begin()+1, o.end(), cmp);
		Backtrack(1,n,c,x,best_x,o);
    //	OutPut(n,best_x);		
		QueryPerformanceCounter(&nEnd);
		time=(double)(nEnd.QuadPart-nBegin.QuadPart)/(double)nFreq.QuadPart; 
		
		cout<<"查询时间:"<<time<<endl<<endl;
		out1<<n<<' '<<time<<endl;
		in.close();		
	}
	out1.close();
	return 0;
}

九、绘图代码

import matplotlib.pyplot as plt

# 读取txt文件,假设文件名为data.txt
file_path = 'F:\\3-CourseMaterials\\3-1\\3-算法设计与分析\实验\lab3\\1-code\\2-回溯法求解0-1背包\\output.txt'

# 存储x和y的列表
x_values = []
y_values = []

# 读取文件并提取数据
with open(file_path, 'r') as file:
    for line in file:
        # 假设数据以空格或逗号分隔
        x, y = map(float, line.strip().split())
        x_values.append(x)
        y_values.append(y)

print(x_values)
print(y_values)
# 绘制图形
plt.plot(x_values, y_values)
plt.title('X vs Y Plot')
plt.xlabel('X-axis')
plt.ylabel('Y-axis')
plt.grid(True)
plt.show()
  • 15
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值