动态规划——扔鸡蛋问题的递归算法与非递归算法

动态规划——扔鸡蛋问题的递归算法与非递归算法

基础版
有一幢高100层的楼,鸡蛋从 x x x层投下时刚好会碎。现持有2个完全相同的鸡蛋,试设计一个最优方法来找出 x x x,使以此方法投下鸡蛋时,最坏情况下所投掷的总次数 N ( 2 , 100 ) N(2,100) N(2,100)最少。

进阶版
有一幢高 f f f层的楼,鸡蛋从 x x x层投下时刚好会碎。现持有 e e e个完全相同的鸡蛋,试设计一个最优方法来找出 x x x,使以此方法投下鸡蛋时,最坏情况下所投掷的总次数 N ( e , f ) N(e,f) N(e,f)最少。

e e e为鸡蛋数, f f f为楼层高度, N ( e , f ) N(e,f) N(e,f)为所求最优算法的投掷总次数, φ \varphi φ为某一算法, ψ \psi ψ为全量算法集合, x x x为鸡蛋破碎楼层, n e , f ( x ∣ φ ) n_{e,f}(x|\varphi) ne,f(xφ)为确定 ( e , f , φ , x ) (e,f,\varphi,x) (e,f,φ,x)情况下所需投掷的次数。
我们可以得出 N ( e , f ) N(e,f) N(e,f)的表达式如下
N ( e , f ) = min ⁡ φ ∈ ψ max ⁡ 0 ≤ x ≤ f n e , f ( x ∣ φ ) N(e,f)=\min\limits_{\varphi \in \psi}\max\limits_{0\leq x\leq f}n_{e,f}(x|\varphi) N(e,f)=φψmin0xfmaxne,f(xφ)
带着表达式,我们来思考这个问题。

相信凡是有过编程经验的人,都听说过二分法。
介于 < , ≤ , = , ≠ , ≥ , > <,\leq ,=,\neq ,\geq ,> <,,=,=,,>均是二元关系运算符,二分法成为了最高效的查找算法。其身影不仅出现在查找过程中,在二叉树、归并排序等等算法中均得以一窥。
然而本题并非二分法的领域,以基础版问题为例,若以二分法投鸡蛋,则首次在第50层投掷(记为 T 1 = 50 T_1=50 T1=50)后,若鸡蛋破碎,即 0 < x ≤ 50 0<x\leq 50 0<x50时,第二次必须从第1层开始投掷,否则若从第2层开始投掷且鸡蛋破碎,则无法确定 x = 1 x=1 x=1还是 x = 2 x=2 x=2。若首次在第50层投掷,最坏情况即 x = 50 x=50 x=50,此时投掷次数 N = 50 N=50 N=50,这显然并非最优方法。
但是二分法仍然可以为我们带来启示。根据上述推断,我们可以得出两条规律:

  • N ( e , f ) ≥ ⌊ l o g 2 f ⌋ w h e r e   e ≥ ⌊ l o g 2 f ⌋ N(e,f)\geq \lfloor log_2 f\rfloor\quad{\rm where}\ e\geq \lfloor log_2 f\rfloor N(e,f)log2fwhere elog2f
  • N ( 1 , f ) = f N(1,f)=f N(1,f)=f

第一条为二分法所规定的投掷次数下限,即鸡蛋数量充足时至少要投掷的次数,不过事实上这一条的参考意义有限。
第二条为仅有一个鸡蛋时的投掷次数,这一条则是重要的边界条件之一。
依然由 T 1 = 50 T_1=50 T1=50,我们考虑 T 1 = 49 T_1=49 T1=49,最坏情况仍是 x = 49 x=49 x=49,此时的有 N = 49 < 50 N=49<50 N=49<50。可以发现 T 1 T_1 T1减少时, N N N同步在减少。那是否 T 1 T_1 T1越小越好呢?并非如此。
我们在 T 1 T_1 T1从50减少到49的过程中,实际默认了“对 x > T 1 x>T_1 x>T1的情况下,使用2个鸡蛋总能在 N − 1 N-1 N1次内找出 x x x”。随着 T 1 T_1 T1越来越小,这一条件的实现越来越困难。但好在对于 x ≤ T 1 x\leq T_1 xT1的情况,我们容易确认最坏情况下需要 N = T 1 N=T_1 N=T1次来找出 x x x。那么当首次投掷鸡蛋不破碎时,我们问题变更为在 T 1 + 1 T_1+1 T1+1至100间找出 T 2 T_2 T2。从而 T 1 + 1 T_1+1 T1+1至100间至多投掷 N − 1 N-1 N1次。由 T 1 = N T_1=N T1=N,容易得出 T 2 = N − 1 T_2=N-1 T2=N1。从而有
T 1 + T 2 + … + T N = N + N − 1 + … + 1 = N ( N − 1 ) / 2 ≥ 100 T_1+T_2+…+T_N=N+N-1+…+1=N(N-1)/2\geq 100 T1+T2++TN=N+N1++1=N(N1)/2100
从而 N ≥ 14 N\geq 14 N14,即最优时 N = 14 N=14 N=14。此时的投掷方法为:

  • 按14,27,39,50,60,69,77,84,90,95,99,100(103)的次序投掷,任一次破碎时,从上一个未破碎节点开始逐个投掷。

\frac{}{}

当问题升级为进阶版时,就不能简单地用 T 1 = N T_1=N T1=N来判断了,但我们仍然可以使用基础版的思想。
以下事实显然:

  • N ( 1 , f ) = f N(1,f)=f N(1,f)=f
  • N ( e , 0 ) = 0 N(e,0)=0 N(e,0)=0

对任意 e , f e,f e,f T T T N ( e , f ) T N(e,f)_T N(e,f)T的取值分两种情况,① T T T投掷使鸡蛋破碎,则后续所需投掷次数为 N ( e − 1 , T ) N(e-1,T) N(e1,T);② T T T投掷未使鸡蛋破碎,则后续所需投掷次数为 N ( e , f − T − 1 ) N(e,f-T-1) N(e,fT1)
从而我们得到
N ( e , f ) T = 1 + max ⁡ ( N ( e − 1 , T ) , N ( e , f − T − 1 ) N(e,f)_T=1+\max(N(e-1,T),N(e,f-T-1) N(e,f)T=1+max(N(e1,T),N(e,fT1)
从而有动态规划的边界与最优子结构如下

  • N ( 1 , f ) = f N(1,f)=f N(1,f)=f
  • N ( e , Z \ N ∗ ) = 0 N(e,Z\backslash N^*)=0 N(e,Z\N)=0
  • N ( e , f ) = 1 + min ⁡ 0 ≤ i ≤ f − 1 max ⁡ ( N ( e − 1 , i ) , N ( e , f − i − 1 ) ) N(e,f)=1+\min\limits_{0\leq i\leq f-1}\max(N(e-1,i),N(e,f-i-1)) N(e,f)=1+0if1minmax(N(e1,i),N(e,fi1))

对此可给出代码实现

	public static int dropEgg(int egg, int floor) {
		if (egg == 1)
			return floor;
		if (floor <= 0)
			return 0;
		int min = floor;
		for (int i = 0; i < floor; i++)
			min = Math.min(min, 1 + Math.max(dropEgg(egg - 1, i), dropEgg(egg, floor - i - 1)));
		return min;
	}

这一算法的递归嵌套极为恐怖,甚至不能解决 N ( 2 , 100 ) N(2,100) N(2,100)问题。引入Map来存储已计算的 N ( e , f ) N(e,f) N(e,f)可尽可能规避递归栈溢出与大幅减少运算时间。

	private static Map<String, Integer> dropMap = new HashMap<String, Integer>();

	public static int dropEgg(int egg, int floor) {
		String s = egg + " " + floor;
		if (dropMap.containsKey(s))
			return dropMap.get(s);
		if (egg == 1)
			return floor;
		if (floor <= 0)
			return 0;
		int min = floor;
		for (int i = 0; i < floor; i++)
			min = Math.min(min, 1 + Math.max(dropEgg(egg - 1, i), dropEgg(egg, floor - i - 1)));
		dropMap.put(s, min);
		return min;
	}

递归的算法到此为止,接下来是非递归的算法。
为了得到非递归算法,我们需要进一步的分析问题。除了上文所提及的两个边界与最优子结构,我们还需引入两个显然的关系式:

  • N ( e , f ) ≥ N ( e + 1 , f ) N(e,f)\geq N(e+1,f) N(e,f)N(e+1,f)
  • N ( e , f ) ≤ N ( e , f + 1 ) N(e,f)\leq N(e,f+1) N(e,f)N(e,f+1)

从而对 i ≤ ⌊ ( f − 1 ) / 2 ⌋ i\leq \lfloor (f-1)/2\rfloor i(f1)/2,我们有

  • N ( e − 1 , i ) ≤ N ( e − 1 , f − i − 1 ) N(e-1,i)\leq N(e-1,f-i-1) N(e1,i)N(e1,fi1)
  • N ( e , f − i − 1 ) ≤ N ( e − 1 , f − i − 1 ) N(e,f-i-1)\leq N(e-1,f-i-1) N(e,fi1)N(e1,fi1)

从而 max ⁡ ( N ( e − 1 , i ) , N ( e , f − i − 1 ) ) ≤ max ⁡ ( N ( e , i ) , N ( e − 1 , f − i − 1 ) ) \max(N(e-1,i),N(e,f-i-1))\leq \max(N(e,i),N(e-1,f-i-1)) max(N(e1,i),N(e,fi1))max(N(e,i),N(e1,fi1))
故最优子结构可收束如下:
N ( e , f ) = 1 + min ⁡ 0 ≤ i ≤ ⌊ ( f − 1 ) / 2 ⌋ max ⁡ ( N ( e − 1 , i ) , N ( e , f − i − 1 ) ) N(e,f)=1+\min\limits_{0\leq i\leq \lfloor (f-1)/2\rfloor}\max(N(e-1,i),N(e,f-i-1)) N(e,f)=1+0i(f1)/2minmax(N(e1,i),N(e,fi1))
(这一收束同样可优化递归算法,代码参见后附全代码)
i ≤ ⌊ ( f − 1 ) / 2 ⌋ i\leq \lfloor (f-1)/2\rfloor i(f1)/2的基础下,考察满足以下两种情况的 i i i

  • N ( e − 1 , i ) = N ( e , f − i − 1 ) N(e-1,i)=N(e,f-i-1) N(e1,i)=N(e,fi1)
  • { N ( e − 1 , i ) ≠ N ( e , f − i − 1 ) N ( e − 1 , i ) = N ( e , f − i − 2 ) N ( e − 1 , i + 1 ) = N ( e , f − i − 1 ) \left\{\begin{array}{l} N(e-1,i)\neq N(e,f-i-1)\\ N(e-1,i)=N(e,f-i-2)\\ N(e-1,i+1)=N(e,f-i-1) \end{array}\right. N(e1,i)=N(e,fi1)N(e1,i)=N(e,fi2)N(e1,i+1)=N(e,fi1)

对第一种情况,有
{ max ⁡ ( N ( e − 1 , i + δ ) , N ( e , f − i − 1 − δ ) ) ≥ N ( e − 1 , i + δ ) ≥ N ( e − 1 , i ) = max ⁡ ( N ( e − 1 , i ) , N ( e , f − i − 1 ) ) max ⁡ ( N ( e − 1 , i − δ ) , N ( e , f − i − 1 + δ ) ) ≥ N ( e , f − i − 1 + δ ) ≥ N ( e , f − i − 1 ) = max ⁡ ( N ( e − 1 , i ) , N ( e , f − i − 1 ) ) \left\{\begin{array}{l} \max(N(e-1,i+\delta),N(e,f-i-1-\delta))\geq N(e-1,i+\delta)\\ \qquad\geq N(e-1,i)= \max(N(e-1,i),N(e,f-i-1))\\ \max(N(e-1,i-\delta),N(e,f-i-1+\delta))\geq N(e,f-i-1+\delta)\\ \qquad\geq N(e,f-i-1)= \max(N(e-1,i),N(e,f-i-1)) \end{array}\right. max(N(e1,i+δ),N(e,fi1δ))N(e1,i+δ)N(e1,i)=max(N(e1,i),N(e,fi1))max(N(e1,iδ),N(e,fi1+δ))N(e,fi1+δ)N(e,fi1)=max(N(e1,i),N(e,fi1))
从而 N ( e , f ) = 1 + N ( e − 1 , i ) N(e,f)=1+N(e-1,i) N(e,f)=1+N(e1,i)

对第二种情况,由于 N ( e , f ) N(e,f) N(e,f) N ∗ N^* N上是连续变换的,从而第二种情况与第一种情况互补,故对任意 N ( e , f ) N(e,f) N(e,f),总存在 i i i符合两种情况其一。
又有
{ max ⁡ ( N ( e − 1 , i + 1 + δ ) , N ( e , f − i − 2 − δ ) ) ≥ N ( e − 1 , i + 1 + δ ) ≥ N ( e − 1 , i + 1 ) = max ⁡ ( N ( e − 1 , i + 1 ) , N ( e , f − i − 2 ) ) max ⁡ ( N ( e − 1 , i − δ ) , N ( e , f − i − 1 + δ ) ) ≥ N ( e , f − i − 1 + δ ) ≥ N ( e , f − i − 1 ) = max ⁡ ( N ( e − 1 , i ) , N ( e , f − i − 1 ) ) \left\{\begin{array}{l} \max(N(e-1,i+1+\delta),N(e,f-i-2-\delta))\geq N(e-1,i+1+\delta)\\ \qquad\geq N(e-1,i+1)= \max(N(e-1,i+1),N(e,f-i-2))\\ \max(N(e-1,i-\delta),N(e,f-i-1+\delta))\geq N(e,f-i-1+\delta)\\ \qquad\geq N(e,f-i-1)= \max(N(e-1,i),N(e,f-i-1)) \end{array}\right. max(N(e1,i+1+δ),N(e,fi2δ))N(e1,i+1+δ)N(e1,i+1)=max(N(e1,i+1),N(e,fi2))max(N(e1,iδ),N(e,fi1+δ))N(e,fi1+δ)N(e,fi1)=max(N(e1,i),N(e,fi1))
从而
N ( e , f ) = 1 + max ⁡ ( N ( e − 1 , i ) , N ( e , f − i − 1 ) ) N(e,f)=1+\max(N(e-1,i),N(e,f-i-1)) N(e,f)=1+max(N(e1,i),N(e,fi1))

故对满足以下条件的 i ≤ ⌊ ( f − 1 ) / 2 ⌋ i\leq \lfloor (f-1)/2\rfloor i(f1)/2

  • N ( e − 1 , i ) = N ( e , f − i − 1 ) N(e-1,i)=N(e,f-i-1) N(e1,i)=N(e,fi1)
  • { N ( e − 1 , i ) ≠ N ( e , f − i − 1 ) N ( e − 1 , i ) = N ( e , f − i − 2 ) N ( e − 1 , i + 1 ) = N ( e , f − i − 1 ) \left\{\begin{array}{l} N(e-1,i)\neq N(e,f-i-1)\\ N(e-1,i)=N(e,f-i-2)\\ N(e-1,i+1)=N(e,f-i-1) \end{array}\right. N(e1,i)=N(e,fi1)N(e1,i)=N(e,fi2)N(e1,i+1)=N(e,fi1)

N ( e , f ) = 1 + max ⁡ ( N ( e − 1 , i ) , N ( e , f − i − 1 ) ) N(e,f)=1+\max(N(e-1,i),N(e,f-i-1)) N(e,f)=1+max(N(e1,i),N(e,fi1))

由此我们得到:

  • N ( 1 , f ) = f N(1,f)=f N(1,f)=f
  • N ( e , Z \ N ∗ ) = 0 N(e,Z\backslash N^*)=0 N(e,Z\N)=0
  • N ( e , f ) = 1 + max ⁡ ( N ( e − 1 , i ) , N ( e , f − i − 1 ) ) N(e,f)=1+\max(N(e-1,i),N(e,f-i-1)) N(e,f)=1+max(N(e1,i),N(e,fi1))
    此处 i ≤ ⌊ ( f − 1 ) / 2 ⌋ i\leq \lfloor (f-1)/2\rfloor i(f1)/2满足
    N ( e − 1 , i ) = N ( e , f − i − 1 ) N(e-1,i)=N(e,f-i-1) N(e1,i)=N(e,fi1)
    { N ( e − 1 , i ) ≠ N ( e , f − i − 1 ) N ( e − 1 , i ) = N ( e , f − i − 2 ) N ( e − 1 , i + 1 ) = N ( e , f − i − 1 ) \left\{\begin{array}{l} N(e-1,i)\neq N(e,f-i-1)\\ N(e-1,i)=N(e,f-i-2)\\ N(e-1,i+1)=N(e,f-i-1) \end{array}\right. N(e1,i)=N(e,fi1)N(e1,i)=N(e,fi2)N(e1,i+1)=N(e,fi1)

由此可得代码实现

	public static int[][] dropEggArray(int egg, int floor) {
		int[][] eggFloorArray = new int[eggNum][floorNum + 1];
		for (int j = 0; j <= floorNum; j++)
			eggFloorArray[0][j] = j;
		for (int i = 0; i < eggNum; i++)
			eggFloorArray[i][0] = 0;
		for (int i = 1; i < eggNum; i++)
			for (int j = 1; j <= floorNum; j++)
				for (int k = j - 1 >> 1; k >= 0; k--)
					if (eggFloorArray[i - 1][k] == eggFloorArray[i][j - k - 1]
							|| (eggFloorArray[i - 1][k] == eggFloorArray[i][j - k - 2]
									&& eggFloorArray[i - 1][k + 1] == eggFloorArray[i][j - k - 1])) {
						eggFloorArray[i][j] = 1 + Math.max(eggFloorArray[i - 1][k], eggFloorArray[i][j - k - 1]);
						break;
					}
		return eggFloorArray;
	}

全量代码如下

import java.util.HashMap;
import java.util.Map;

public class Egg {
	private static final int eggNum = 5;
	private static final int floorNum = 2500;

	public static void main(String[] args) {
		long time = System.currentTimeMillis();
		int[][] dropEggArray = dropEggArray(eggNum, floorNum);
		System.out.println("N=" + dropEggArray[eggNum - 1][floorNum]);
		System.out.println("非递归算法耗时" + (-time + (time = System.currentTimeMillis())) + "ms");
		System.out.println("N=" + dropEgg(eggNum, floorNum));
		System.out.println("递归算法耗时" + (-time + (time = System.currentTimeMillis())) + "ms");

	}

	private static Map<String, Integer> dropMap = new HashMap<String, Integer>();

	public static int dropEgg(int egg, int floor) {
		String s = egg + " " + floor;
		if (dropMap.containsKey(s))
			return dropMap.get(s);
		if (egg == 1)
			return floor;
		if (floor <= 0)
			return 0;
		int min = floor;
		for (int i = 0; i <= floor >> 1; i++)
			min = Math.min(min, 1 + Math.max(dropEgg(egg - 1, i), dropEgg(egg, floor - i - 1)));
		dropMap.put(s, min);
		return min;
	}

	public static int[][] dropEggArray(int egg, int floor) {
		int[][] eggFloorArray = new int[eggNum][floorNum + 1];
		for (int j = 0; j <= floorNum; j++)
			eggFloorArray[0][j] = j;
		for (int i = 0; i < eggNum; i++)
			eggFloorArray[i][0] = 0;
		for (int i = 1; i < eggNum; i++)
			for (int j = 1; j <= floorNum; j++)
				for (int k = j - 1 >> 1; k >= 0; k--)
					if (eggFloorArray[i - 1][k] == eggFloorArray[i][j - k - 1]
							|| (eggFloorArray[i - 1][k] == eggFloorArray[i][j - k - 2]
									&& eggFloorArray[i - 1][k + 1] == eggFloorArray[i][j - k - 1])) {
						eggFloorArray[i][j] = 1 + Math.max(eggFloorArray[i - 1][k], eggFloorArray[i][j - k - 1]);
						break;
					}
		return eggFloorArray;
	}
}

对测试数据 e = 5 , f = 2500 e=5,f=2500 e=5,f=2500的运行结果如下

运行结果

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值