《算法导论》第15章动态规划钢条切割问题Java实现

算法导论中讲解动态规划的一个很经典的例子,详细分析了朴素的递归自顶向下动态规划的递归自底向上的非递归动态规划,这三种思路之间的区别,并重构解。

联系第22章:

自顶向下动态规划的递归:就是子问题图的深度优先搜索

自底向上的非递归动态规划:就是子问题图的逆拓扑排序

代码实现:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

//动态规划,钢条切割问题
public class Cut {

	public static void main(String[] args) {
		//长度对应价格表price,下标从0开始,每(i+1)长的钢条价值为price[i],n为给的钢条长度
		int[] price = {1,5,8,9,10,17,17,20,24,30};
		int n=7;
		
		System.out.println("给定钢条长度价格表为:");
		System.out.println(Arrays.toString(price));
		System.out.println("给定钢条长度为:");
		System.out.println(n);
		
		//1.使用朴素的自顶向下递归算法求解最大切割收益q1
		int q1 = Cut_Rod(price, n);
		System.out.println("使用朴素的自顶向下递归算法求解最大切割收益为:"+q1);
		//2.使用带备忘的自顶向下递归的动态规划求解最大切割收益q2
		int q2 = Memoized_Cut_Rod(price, n);
		System.out.println("使用带备忘的自顶向下递归的动态规划求解最大切割收益为:"+q2);
		//3.使用自底向上的非递归动态规划求解最大切割收益q3
		int q3 = Bottom_Up_Cut_Rod(price, n);
		System.out.println("使用自底向上的非递归动态规划求解最大切割收益为:"+q3);
		//4.重构解,获得每个子问题长度的最大切割收益和最大切割收益的切割轨迹
		List<int[]> result = Extended_Bottom_Up_Cut_Rod(price, n);
		int[] r = result.get(0);//最大切割收益记录
		int[] s = result.get(1);//最大切割收益的切割轨迹记录
		System.out.println("最大切割收益记录为"+Arrays.toString(r));
		System.out.println("最大切割收益的切割轨迹记录为"+Arrays.toString(s));
		System.out.print("当前长度最大切割收益为:"+r[n]+",当前长度最大切割收益方案为:");
		Print_Cut_Rod_Solution(price, n); 
		
	}
	
	//1.朴素的自顶向下的递归实现,对于同一个子问题多次展开计算,效率太低
	//数组p为价格数组,下标从0开始,每(i+1)长的钢条价值为p[i],n为给的钢条长度
	public static int Cut_Rod(int[] p,int n) {
		//递归的出口,如果钢条长度为0,那么没有价值
		if(n==0) {
			return 0;
		}
		//q用来保存当前的最大价格收益,初始化为无穷小
		int q = -99999;
		//从1到n依次求从左向右切割i长的钢板能获得的最大收益值
		for(int i=1;i<=n;i++) {
			q=Math.max(q, p[i-1]+Cut_Rod(p, n-i));
		}
		return q;
	}
	
	//2.带备忘的自顶向下的动态规划实现,是递归版的动态规划,用了一个r数组来保存记忆住以往的重叠子问题(长度为i的钢条切割的最大收益)
	//自顶向下在子问题图上就是深度优先搜索
	
	//主过程
	public static int Memoized_Cut_Rod(int[] p,int n) {
		//初始化一个记忆数组r
		int[] r = new int[n+1];
		//给r数组初始化,每个值设为未知的小值
		for(int i=0;i<r.length;i++) {
			r[i]=-99999;
		}
		//调用基本过程
		return Memoized_Cut_Rod_Aux(p, n, r);
	}
	//基本过程
	private static int Memoized_Cut_Rod_Aux(int[] p,int n,int[] r) {
		int q;//用于保存当前最大收益
		if(r[n]>=0) {
			return r[n];//递归,动态规划的出口,即长度为n的钢条最大切割收益找到了(r[n]由负更新为正了)
		}
		//q的初始化,如果钢条长为0,无法切割,收益也为0,否则初始化为一个未知的小值
		if(n==0) {
			q=0;
		}else {
			q=-99999;
		}
		
		for(int i=1;i<=n;i++) {
			q=Math.max(q, p[i-1]+Memoized_Cut_Rod_Aux(p, n-i, r));
		}
		//每轮递归后更新相应长度i的最大切割收益值,即r[i]
		r[n]=q;
		
		return q;
	}
	
	//3.自底向上的动态规划实现,不再是是递归,求解每个子问题的前提是它所依赖的更小子问题求解完毕了
	//自底向上在子问题图上就是逆拓扑排序
	public static int Bottom_Up_Cut_Rod(int[] p,int n) {
		int r[] = new int[n+1];
		//初始化与递归不同,自底向上要从基本的子问题开始,所以基本的子问题要确定,r[0]=0,不再是-99999
		r[0]=0;
		for(int j=1;j<=n;j++) {
			//即j从1到n一个个找,每个j是个和n级别相同的层面问题了,但长度j又分为相同的基本问题i与j-i两段
			int q=-99999;
			for(int i=1;i<=j;i++) {
				q=Math.max(q, p[i-1]+r[j-i]);
			}
			//每次外轮循环后求出长度i的最大切割收益,更新r[i]
			r[j]=q;
		}
		return r[n];
	}
	
	//4.自底向上动态规划的重构解,用来跟踪解的轨迹
	public static List<int[]> Extended_Bottom_Up_Cut_Rod(int[] p,int n) {
		//r来保存长度j的钢条最大切割收益
		int[] r = new int[n+1];
		//s来保存长度j的最优切割方案中的第一段切割下来的钢条长度
		int[] s = new int[n+1];
		r[0]=0;
		for(int j=1;j<=n;j++) {
			int q=-99999;
			for(int i=1;i<=j;i++) {
				if(q<p[i-1]+r[j-i]) {
					q=p[i-1]+r[j-i];
					s[j]=i;//保存长度j的切割方案中第一段钢条的长度,那么剩下长度就是j-s[j]
					r[j]=q;
				}
			}
		}
		
		//由于要返回一个int[]型最大收益记录数组r和一个int[]型切割轨迹数组s,Java中不支持元组,所以用一个List集合保存这两个
		List<int[]> temp = new ArrayList<int[]>();
		temp.add(r);
		temp.add(s);
		return temp;
	}
	//对最大收益和切割方案轨迹打印
	public static void Print_Cut_Rod_Solution(int[] p,int n) {
		List<int[]> result = Extended_Bottom_Up_Cut_Rod(p, n);
		int[] r = result.get(0);
		int[] s = result.get(1);
		while(n>0) {
			System.out.print(s[n]+" ");
			n=n-s[n];
		}
	}
}

程序结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值