算法分析及實例解析(二)——貪心算法、動態規劃

貪心算法(greedy algorithm)

貪心算法,顧名思義。其算法描述爲:在分步決策問題中,每一步選擇當前最優。由於貪心算法僅考慮局部最優性,所以不能保證整體最優。因此,對於貪心算法,必須進一步證明該算法的每一步做出的選擇都必然導致問題的一個整體最優解。

一般來說適用於貪心算法的問題具有兩個特性:最優度量標準和最優子結構。

貪心算法每一步做出的選擇可以依賴於以前做出的選擇,但決不依賴將來的選擇,也不依賴於子問題的解,分步做出最優選擇的過程是一個自頂向下的過程,這是該算法相對其它算法的獨特之處。

由於該算法比較簡單,我們僅用一個一般的揹包問題說明它。

一般揹包問題

問題描述:n個物品,第i個價值爲vi,重量爲wi,物品可分,揹包可承受的重量W,如何選入裝入揹包的物品,使裝入揹包的物品總價值最大。

由於物品可分,所以這是一個典型的貪心算法,只需要每次選擇單位重量價值最大的物品,直到把揹包裝滿即可。
這個問題的處理過程:首先按單位重量價值對物品進行排序,然後從大到小,依次加入揹包,直至揹包加滿。代碼如下:
#include <iostream>
#include <algorithm>
/*
問題表示:
共5間物品:
w:3	4	7	8	9 
v:4	5	10	11	13
W:17 
*/
const int W=17;
const int w[5]={3,4,7,8,9};
const int v[5]={4,5,10,11,13};

int ave_v_index[5]={0,1,2,3,4};//記錄平均重量價值的由大到小物品索引 
bool cmp(int a,int b)
{
	return ((double)v[a])/((double)w[a])>((double)v[b])/((double)w[b]);
}
double greedy_01()
{
	std::sort(ave_v_index,ave_v_index+5,cmp);
	int tem_w=0;
	double sum_value=0;
	int i=0;
	for(;i<5;++i)
	{
		if(w[i]+tem_w<=W)
		{
			tem_w+=w[i];
			sum_value+=v[i];
		}else break;
	} 
	if(i<5) sum_value+=((double)(W-tem_w))/((double)w[i])*((double)v[i]);
	return sum_value;
}

int main(int argc, char** argv) {
	std::cout<<greedy_01()<<std::endl;
	return 0;
}

動態規劃(dynamic programming)

與貪心算法類似,動態規劃是一種求解最優問題的算法設計策略。動態規劃每一步的決策依賴於子問題的解,決策的過程自底向上,並保持子問題的解,從而可以避免重複子問題的計算。

此算法的主要特點是:通過採用表格技術,用多項式算法代替指數算法複雜度。

該算法的經典實例包括:優化的斐波拉契數列數、0-1揹包問題、最長公共子序列問題

斐波拉契數列

斐波拉契數列數(0,1,2,3,5,8,11,...)問題就不再描述了。

考慮用自頂向下的動態規劃降低複雜度,代碼如下:
#include <iostream>
#include <algorithm>
#include <iterator>

int fbnq[1000];//用於保存計算的數 

int get_fbnq(int num)
{
	if(num==0) fbnq[0]=0;
	else if(num==1) fbnq[1]=1;
	else
		fbnq[num]=get_fbnq(num-1)+get_fbnq(num-2);
	return fbnq[num];
	
} 

void print_seq(int *data,int num)
{
	std::copy(data,data+num+1,std::ostream_iterator<int>(std::cout,"  "));
}

int main(int argc, char** argv) {
	int num=10;
	get_fbnq(num);
	print_seq(fbnq,num);
	return 0;
}

0-1揹包問題

問題描述:n個物品,第i個價值爲vi,重量爲wi,物品不可分,揹包可承受的重量W,如何選入裝入揹包的物品,使裝入揹包的物品總價值最大。

自底向上,依次求解裝入i個物品揹包可承載j時的最大價值數組sum_v[i][j],後面的計算用到了前面得出的數據,因此,相對於遞歸,可以減少重複子問題的計算,將算法複雜度由指數降爲多項式。
#include <iostream>
/*
問題表示:
共5間物品:
w:3	4	7	8	9 
v:4	5	10	11	13
W:17 
*/
const int W=17;
const int w[5]={3,4,7,8,9};
const int v[5]={4,5,10,11,13};

int sum_v[6][W+1]; 
int dynamic_01()
{
	for(int i=0;i<=W;++i)
		sum_v[0][i]=0;
	for(int i=1;i<=5;++i)
		sum_v[i][0]=0;
	for(int i=1,k=0;i<=5;++k,++i)//自底向上 ,注意k是物品的編號,與i(放幾個物品)相差1 
	{
			for(int j=1;j<=W;++j)
			{
				if((w[k]<=j)&&((v[k]+sum_v[i-1][j-w[k]]>sum_v[i-1][j])))
					sum_v[i][j]=sum_v[i-1][j-w[k]]+v[k];
				else sum_v[i][j]=sum_v[i-1][j];
				std::cout<<"sum_value["<<i<<","<<j<<"]="<<sum_v[i][j]<<std::endl;
			}
	}
	return 	sum_v[5][W];
}

int main(int argc, char** argv) {
	std::cout<<dynamic_01()<<std::endl;
	return 0;
}

最長公共子序列 LCS

問題描述:對兩個給定序列X={x1,x2,x3...xm}和Y={y1,y2,y3...yn},求它們的最長公共子序列。

如果採用窮舉法,複雜度非常大,達到2的冪次方級,因而採用動態規劃。
首先我們對問題進行分析:
如果Z={z1,z2,z3,...zk}是它們的最長公共子序列,則:
(1)若xm=yn,那麼xm=yn=zk,且Zk-1是Xm-1和Xn-1的一條最長公共子序列。
(2)若xm!=yn且xm!=zk,那麼Z是Xm-1和Y的一條最長公共子序列。
(3)若yn!=xm且yn!=zk,那麼Z是X和Yn-1的一條最長公共子序列。
具體代碼如下:
#include <iostream>

char X[8]={'0','a','b','c','b','d','a','b'},
	 Y[7]={'0','b','d','c','a','b','a'};
int lcs[9][8];

int LCS()
{
	for(int i=0;i<9;i++) lcs[i][0]=0;
	for(int j=0;j<8;j++) lcs[0][j]=0;
	for(int i=1;i<=8;++i)//自底向上 
		for(int j=1;j<=7;++j)
		{
			if(X[i]==Y[j]) //若xm=yn,那麼xm=yn=zk,且Zk-1是Xm-1和Xn-1的一條最長公共子序列。
				lcs[i][j]=lcs[i-1][j-1]+1;
			else//若 xm!=yn,那麼Z是Xm-1和Y或X和Yn-1的一條最長公共子序列。
				lcs[i][j]=std::max(lcs[i-1][j],lcs[i][j-1]);
		}
}


int main(int argc, char** argv) {
	LCS();
	std::cout<<lcs[8][7]<<std::endl; 
	return 0;
}

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