算法分析及实例解析(二)——贪心算法、动态规划

贪心算法(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;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值