算法导论 动态规划钢条切割问题 C语言

动态规划钢条切割问题

动态规划(dynamic programming)与分治法类似。分治策略将问题划分为互不相交的子问题,递归求解子问题,再将子问题进行组合,求解原问题。动态规划应用于子问题重叠的情况,在这种情况下,分治法将会对重叠问题进行多次重复求解,而动态规划对每个子问题只求解一次

动态规划方法常用于求解**最优化问题(optimization problem)**。这类问题可能有多个解,每个解有一个值,我们希望寻找具有最优值的解。

设计动态规划算法步骤:

  1. 刻画一个最优解的特征
  2. 递归定义最优解的值
  3. 计算最优解的值
  4. 利用计算出的信息构造一个最优解

例:钢条切割问题
假设钢条价格表

长度i 1 2 3 4 5 6 7 8 9 10
价格p 1 5 8 9 10 17 17 20 24 30

现在对于一根长度为n的钢条,求它能获得的最大效益r

注意到,每一次切割后将得到两根钢铁,下一步,又将这两根钢条看作两个独立的钢铁切割问题。通过组合子问题的最优解,选取组合收益最大者,构成原问题的最优解。钢条切割问题满足最优子结构性质:问题的最优解由相关子问题的最优解组合而成,而这些子问题都可以独立求解
对钢条问题,我们可以再进行简化。对于每次切割后得到的两段钢条,我们只对第二段进行再次切割,对第一段不再切割。

自顶向下递归实现
int CUD_ROD(int *p, int n) {
	if (n == 0)return 0;
	int q = INT_MIN;
	for (int i = 1; i <= n;i++) {
		if (p[i] + CUD_ROD(p, n - i) > q)q = p[i] + CUD_ROD(p, n - i);
	}
	return q;
}

输出价格数组p以及钢条长度n,返回最大收益r。
当n变大时,程序的运行时间会成倍增长,这是由于程序在递归调用时,对相同参数值进行了反复求解。例如,当n=5时,将会调用CUD_ROD(p, n=0 to 4),求解CUD_ROD(p, n=4)时,又会调用CUD_ROD(p, n=0 to 3)。整个程序中存在大量的重复求解过程。

使用动态规划方法求解最优钢条切割问题

朴素递归算法效率之所以低,是因为他在反复求解子问题。动态规划方法安排求解顺序,对每个子问题只进行一次求解,将其结果保存下来,再次遇到相同子问题时,只需要查询结果,而不需要重新计算。这是典型的时空权衡(time-memory trade-off)

带备忘的自顶向下法

仍按照自然递归的顺序进行编写,但过程中会保存子问题的解于数组r中。过程中首先查询子问题的解是否已经存在,若存在,直接返回解,不存在,则按常规方式进行求解。

int MEMOIZED_CUT_ROD_AUX(int *p, int n, int *r) {
	if (r[n] >= 0) return r[n];
	if (n == 0)return 0;
	int q = INT_MIN;
	for (int i = 1; i <= n; i++)
	{
		if (p[i] + MEMOIZED_CUT_ROD_AUX(p, n - i, r) > q)
			q = p[i] + MEMOIZED_CUT_ROD_AUX(p, n - i, r);
	}
	r[n] = q;
	return q;
}
自底向上法

自底向上法依次求解出r[1...n]

int BOTTOM_UP_CUT_ROD(int *p, int n) {
	int *r1;
	r1 = (int*)malloc(n * sizeof(int));
	r1[0] = 0;
	for (int j = 1; j <= n; j++) {
		int q = INT_MIN;
		for (int i = 1; i <= j; i++)
		{
			if (p[i] + r1[j-i] > q) q = p[i] + r1[j - i];
			
		}

		r1[j] = q;
	}
	return r1[n];
}
完整测试代码
#include 
#include 
#include 
#include 
using namespace std;
int p[100]= { 0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30 };
int CUD_ROD(int *p, int n) {
	if (n == 0)return 0;
	int q = INT_MIN;
	for (int i = 1; i <=n;i++) {
		if (p[i] + CUD_ROD(p, n - i) > q)q = p[i] + CUD_ROD(p, n - i);
	}
	return q;
}
int* MEMOIZED_CUT_ROD(int n) {
	int *r;
	r = (int*)malloc( n * sizeof(int));
	for (int i = 0; i <= n; i++)
	{
		r[i] = INT_MIN;
	}
	return r;
}
int MEMOIZED_CUT_ROD_AUX(int *p, int n, int *r) {
	if (r[n] >= 0) return r[n];
	if (n == 0)return 0;
	int q = INT_MIN;
	for (int i = 1; i <= n; i++)
	{
		if (p[i] + MEMOIZED_CUT_ROD_AUX(p, n - i, r) > q)
			q = p[i] + MEMOIZED_CUT_ROD_AUX(p, n - i, r);
	}
	r[n] = q;
	return q;
}
int BOTTOM_UP_CUT_ROD(int *p, int n) {
	int *r1;
	r1 = (int*)malloc(n * sizeof(int));
	r1[0] = 0;
	for (int j = 1; j <= n; j++) {
		int q = INT_MIN;
		for (int i = 1; i <= j; i++)
		{
			if (p[i] + r1[j-i] > q) q = p[i] + r1[j - i];
			
		}

		r1[j] = q;
	}
	return r1[n];
}
int main() {
	int n = 18;
	cout <<"朴素自顶向下递归法:" <<CUD_ROD(p, n) << endl;;
	int *r=MEMOIZED_CUT_ROD(n);//初始化备忘 r
	cout << "带备忘自顶向下法:" << MEMOIZED_CUT_ROD_AUX(p, n, r) << endl;
	cout << "自底向上法:" << BOTTOM_UP_CUT_ROD(p, n);
	system("pause");
	return 0;
}

你可能感兴趣的:(算法导论)