01背包各種算法代碼實現總結(窮舉,貪心,動態,遞歸,回溯,分支限界)


2020-05-22

所有背包問題實現的例子都是下面這張圖

01背包實現之——窮舉法:

1.我的難點:

(1)在用窮舉法實現代碼的時候,我自己做的時候認為最難的就是怎么將那么多種情況表示出來,一開開始想用for循環進行多次嵌套,但是太麻煩,而且還需要不斷的進行各種標記。我現在的水平實在太菜,然后就在一篇博文中看到一個特別巧妙的枚舉算法,如下所示:

int fun(int x[n])
{
	int i;
	for(i=0;i<n;i++) 
	   if(x[i]!=1)  {x[i]=1; return;}
//從遇到的第一位開始,若是0,將其變成1,然后結束for循環,得到一種解法 
	   else x[i]=0;
	   return;
//從第一位開始,若是1,將其變成0,然后繼續循環,若再循環的時候遇到0,則將其變為1,結束循環。得到另一種解法。 
}

  雖然我現在也不知道為什么會這樣,但是確實是個很好的規律,找到這個規律后,就可以很輕松的自己寫出各種排列情況,以后遇到排列的問題,就用這個方法。語言不好描述,上圖片演示(是歪的,湊活看吧。。。):

(2)算法思想:

x[i]的值為0/1,即選或者不選

w[i]的值表示商品i的重量

v[i]的值表示商品的價值

所以這個算法最核心的公式就是

tw=x[1]*w[1]+x[2]*w[2]+.......+x[n]*w[n]

tv=x[1]*w[1]+x[2]*v[2]+......+x[n]*v[n]

tv1:用於存儲當前最優解

limit:背包容量

如果 tw<limit&&tv>tv1 則可以找到最優解

2.代碼實現(借鑒博文

#include<stdio.h>
#include<iostream>
using namespace std;
#define n 4
void possible_solution(int x[n]){
	int i;
	for(i=0;i<4;i++)  //n=4,有2^4-1種解法 
	  if(x[i]!=1) 
	  { 
	  x[i]=1;
	  return; //從遇到的第一位開始,若是0,將其變成1,然后結束循環,得到一種解法 
	  }
	  else
	  x[i]=0;
	  return;//從第一位開始,若是1,將其變成0,然后繼續循環,若再循環的時候遇到0,則將其變為1,結束循環。得到另一種解法。 	
}
int main(){
	int count=0; 
	int w[n]={2,3,4,5},v[n]={3,4,5,6};
	int x[n]={0,0,0,0},y[n]={0,0,0,0};
	int tw,tv,tv1=0,limit=8;
	int j;
	for(j=1;j<=15;j++){ 
		possible_solution(x); 
		count++;
		for(int i=0;i<4;i++){
	  	cout<<x[i]<<" ";
	  } 
	  cout<<endl;
		tw=x[0]*w[0]+x[1]*w[1]+x[2]*w[2]+x[3]*w[3];
		tv=x[0]*v[0]+x[1]*v[1]+x[2]*v[2]+x[3]*v[3];
		if(tw<=limit&&tv>tv1){
			tv1=tv; y[0]=x[0];y[1]=x[1];y[2]=x[2],y[3]=x[3];
		}
	}
	cout<<"共有"<<count<<"種解法."<<endl; 
	printf("其中0-1背包問題的最優解為: y=(%d,%d,%d,%d)\n",y[0],y[1],y[2],y[3]);
	printf("總價值為:%d",tv1);
}

3.運行結果:

4.復雜度分析

n個物品的話,就有2^n-1種解,所以其時間復雜度為O(2^n)

 

 

01背包問題之——貪心算法:

 1.算法思路:

取單位價值量最大的那個物品先裝入背包。所以還算好實現,得到每一個物品的價值量之后,查找最大的價值量的坐標,判斷這個坐標額物品體積是否小於背包的容量,若小於,則裝入背包。否則,繼續循環。

2.代碼實現 法一:

將得到的每個物品的價值量進行排序,得到一個遞減序列。

 #include<iostream>
 #include <iomanip>
 #define n 4   //物品數列 
 #define c 8  //背包容量 
 using namespace std;
int w[4]={2.0,3.0,4.0,5.0};
float v[4]={3.0,4.0,5.0,6.0};
float sortBest[4];  //v[i]/w[i]  
int C(){
for(int i=0;i<4;i++){
 	sortBest[i]=v[i]/w[i];
 	//cout<<sortBest[i]<<" ";
}
cout<<endl;
}
int Sort(){
for(int i=0;i<4;i++)
{
	int temp;
	int wtemp;
	int vtemp;
	if(sortBest[i+1]>sortBest[i])
	{
	temp=sortBest[i];
	sortBest[i]=sortBest[i+1];
	sortBest[i+1]=temp;
	wtemp=w[i];
	w[i]=w[i+1];
	w[i+1]=wtemp;
	vtemp=v[i];
	v[i]=v[i+1];
	v[i+1]=vtemp;
    }
    //用來查看排序是否正確 
	cout<<"w["<<i<<"]="<<w[i]<<" ";
	cout<<endl;
	cout<<"v["<<i<<"]="<<v[i]<<" "; 
	cout<<endl;
	cout<<"sortBest["<<i<<"]="<<sortBest[i]<<endl;
}
cout<<endl;
}
int F(){
	int c1=c;
	int result=0;
	for(int i=0;i<4;i++){
		if(w[i]<=c1)
		  result=result+v[i];
		  c1=c1-w[i];
	}
	cout<<"最優值是:"<<result;
}
 int main()
 {
C();
cout<<"背包重量是:"<<c<<endl; 
Sort();
F();
return 0;
 }

 

代碼實現 法二:

沒有對每個商品的價值量進行排序,直接查找當前價值量的最大值,判斷其是否能夠裝入背包,若能,直接裝入,令當前價值量為0,繼續尋找第二大價值量,不斷循環即可。代碼如下:

#include<iostream>
#include <iomanip>
#define n 4   //物品數列 
#define c 8  //背包容量 
 using namespace std;
float w[4]={2.0,3.0,4.0,5.0};
float v[4]={3.0,4.0,5.0,6.0};
float sortBest[4];  //價值量  v[i]/w[i]
int C(){
for(int i=0;i<4;i++){
 	sortBest[i]=v[i]/w[i];
 	//cout<<sortBest[i]<<" ";
}
}
int F()
{ 
   float temp=0; 
   float result=0;
   float c1=8; //用於改變c的值 
for(int i=0;i<4;i++)
{
	//for循環用來得到最大sortBest 
   for(i=0;i<4;i++)
   {
   	if(temp<sortBest[i]) 
	   temp=sortBest[i];
   }
   //cout<<"max(sortBest)="<<temp<<endl;
   for(i=0;i<4;i++)
   {
   	if (temp==sortBest[i])
   	//cout<<"最大sortBest的下標是:"<<i<<endl;
   	sortBest[i]=0;  
   	if (w[i]<=c1)
	result=result+v[i];
   	c1=c1-w[i];  
   }
}
cout<<"結果為:"<<result<<endl; 
}
int main()
{
	cout<<"********貪心算法解決01背包問題,誰的sortBest=v[i]/w[i] 大,就先拿誰***********"<<endl; 
	cout<<"背包的總重量是:"<<c<<endl;
	cout<<"可挑選的物品共4件"<<endl;
	cout<<endl; 
	for(int i=0;i<4;i++)
	{
	cout<<"w["<<i<<"]="<<w[i]<<"  ";
	cout<<"v["<<i<<"]="<<v[i]<<"  "; 
	cout<<"sortBest["<<i<<"]="<<v[i]/w[i]<<"  "; 
	cout<<endl;
	} 
	C();
	F(); 
	return 0;
}

3.遇到的困難

就是,當得到的價值量的包含小數時,而且剛好就靠小數部分區分大小時(比如1.5 ,1.33,)。c++正常輸出的結果都是整數。

解決辦法就是,將每個物品的價值量(3.0,4.0)和背包重量(2.0,3.0)都變float類型,注意定義的時候,也需要定義為float類型

4.復雜度:

時間復雜度:O(n)

01背包問題之——動態規划

 

 1.算法思想

最重要的就是尋找遞推關系式:

定義V[i,j]:當背包容量為j時,前i個物品最佳組合對應的值。

遞推關系:

(1)當背包的容量不允許裝入第i件物品時,和前一個物品裝入背包一樣。即 :V[i][j]=V[i-1][j]

(2)當背包的容積可以裝入第i件物品時,分兩種情況,A裝入第i件物品不是最優,還不如不裝。B裝入第i件物品是最優。即:V[i][j]=max(V[i-1][j],V[i][j-w[i]]+v[i])

2.代碼實現:

#include<iostream>
 using namespace std;
 int w[5]={0,2,3,4,5};
 int v[5]={0,3,4,5,6};
 int V[5][9]; 
 int c=8;
 int B()
 {
 	int i,j;
 	for(i=0;i<5;i++)
	{
 		V[i][0]=0;
 		for(j=0;j<c+1;j++)
		 {
 			V[0][j]=0;
 			if(j<w[i])
 				V[i][j]=V[i-1][j];
			else
			 	V[i][j]=max(V[i-1][j],V[i-1][j-w[i]]+v[i]);
		 }
	}
 }
int main(){
B();
//顯示填好的表格 
for (int i=0;i<5;i++)
{
	for(int j=0;j<9;j++)
	{
		cout<<V[i][j]<<"  ";
	}
	cout<<endl;
}
cout<<"最優結果是:"<<V[4][8];
 	return 0;
 }

 

  

 

 下面是帶上回溯找出解的組成的代碼:

 

 #include<iostream>
 using namespace std;
 int w[5]={0,2,3,4,5};
 int v[5]={0,3,4,5,6};
 int V[5][9]; 
 int c=8;
 int item[4];
 int B()
 {
 	int i,j;
 	for(i=0;i<5;i++)
	{
 		V[i][0]=0;
 		for(j=0;j<c+1;j++)
		 {
 			V[0][j]=0;
 			if(j<w[i])
 				V[i][j]=V[i-1][j];
			else
			 	V[i][j]=max(V[i-1][j],V[i-1][j-w[i]]+v[i]);
		 }
	}
 }
void FindWhat(int i,int j)//尋找解的組成方式
{
    if(i>=0)
    {
        if(V[i][j]==V[i-1][j])//相等說明沒裝
        {
            item[i]=0;//全局變量,標記未被選中
            FindWhat(i-1,j);
        }
        else if( j-w[i]>=0 && V[i][j]==V[i-1][j-w[i]]+v[i] )
        {
            item[i]=1;//標記已被選中
            FindWhat(i-1,j-w[i]);//回到裝包之前的位置
        }
    }
}

int main(){
B();
//顯示填好的表格 
cout<<"得到的表格如下圖所示:"<<endl;
for (int i=0;i<5;i++)
{
	for(int j=0;j<9;j++)
	{
		cout<<V[i][j]<<"  ";
	}
	cout<<endl;
}
cout<<"最優結果是:"<<V[4][8]<<endl;
FindWhat(4,8);
cout<<endl;
cout<<"回溯得到的解是:"<<endl;
for(int i=1;i<5;i++){
if(item[i]==1) 
cout<<"背包里面有第"<<i<<"號物品"<<endl; 
//cout<<item[i]<<" ";
}
 	return 0;
 }

 

 3.復雜度

時間復雜度:

O(物體個數*背包容積)=O(number*capacity)

空間復雜度:

用二維表實現的,所以和時間復雜度一樣。

 O(物體個數*背包容積)=O(number*capacity)

 01背包之——遞歸

1.

遞歸法思路很單一,也是在遞歸方程的基礎上,將其改造為可以遞歸的方式

2.代碼演示

#include<iostream>
using namespace std;
int n=4;
int w[4]={2,3,4,5};
int v[4]={3,4,5,6};
int y[4]={0,0,0,0};
int c=8;
int f(int n,int c)
{
	int temp1;
	int temp2;
	if(n==0||c==0) {
	return 0;}
	else
	{
		for(int i=n-1;i>=0;i--)
		{
			if(w[i]>c)
			{
			  return f(n-1,c);
		    }
			else
		{
			   temp1=f(n-1,c);
			   temp2=f(n-1,c-w[i])+v[i];
			   if(temp1>temp2)
			   {
			    return temp1;
			   }
			   else if(temp1<temp2)
			   {
			    return temp2;
		       }
		}
		}
	}
}
int main()
{
	cout<<"最優值為:"<<f(4,8)<<endl;
	return 0;
}

 

 3.我的難點:

因為是遞歸,所以其最大的缺點就是重復計算,所以如果我想查找他的解是什么,不容易查找。因為如果你進行標記的話,因為會重復計算,所以標記的話,標記也是不停的會變。所以我也不知道怎么解決。

4.復雜度:

O(2^n)

01背包之——回溯

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM