[笔记][中国大学mooc][程序设计与算法(二) 算法基础][动态规划] 数字三角形

题目

在这里插入图片描述

第一个想法:递归

这个问题满足递归的结构,本质上相当于深度优先遍历一个二叉树,没什么难度,直接给出代码,时间复杂度为 O ( n ! ) O(n!) O(n!)

代码

#include<iostream>
#define MAXAMOUNTOFROWS 100
using namespace std;
int numberTriangle[MAXAMOUNTOFROWS][MAXAMOUNTOFROWS];
int maxSum(int currentPosition_row, int currentPosition_column, int stairNumber){
	if(stairNumber == 1)
		return numberTriangle[currentPosition_row][currentPosition_column];
	else{
		int temp1 = maxSum(currentPosition_row + 1, currentPosition_column, stairNumber-1);
		int temp2 = maxSum(currentPosition_row + 1, currentPosition_column+1, stairNumber-1);
		return numberTriangle[currentPosition_row][currentPosition_column] + (temp1 > temp2 ? temp1 : temp2);
	}
}
int main(){
	int n;
	cin >> n;
	for(int cnt1=0; cnt1<n; cnt1++)
		for(int cnt2=0; cnt2<=cnt1; cnt2++)
			scanf("%d", &numberTriangle[cnt1][cnt2]);
	cout << maxSum(0, 0, n);
	return 0;
} 

存在大量重复计算

这个二叉树存在相当多的相同树枝,比如当两个路径都遍历到同一位置的时候,如果继续遍历,则走过的是完全一样的路径
在这里插入图片描述
使用matlab生成了一个35阶的数字三角,算了足足两分钟,更别想100阶的了
为了消除冗余变量,可以引入记忆矩阵,记录已经遍历过的相同的树枝的值。
每个节点只需要计算一次,所以时间复杂度应该是 O ( n 2 ) O(n^2) O(n2)

记忆递归型代码

#include<iostream>
#include<fstream>
#define MAXAMOUNTOFROWS 100
using namespace std;
int numberTriangle[MAXAMOUNTOFROWS][MAXAMOUNTOFROWS];
int memoryMatrix[MAXAMOUNTOFROWS][MAXAMOUNTOFROWS];
int maxSum(int currentPosition_row, int currentPosition_column, int stairNumber){
	if(memoryMatrix[currentPosition_row][currentPosition_column] == -1){
		if(stairNumber == 1){
			memoryMatrix[currentPosition_row][currentPosition_column] = numberTriangle[currentPosition_row][currentPosition_column];
			return memoryMatrix[currentPosition_row][currentPosition_column];
		}
		else{
			int temp1 = maxSum(currentPosition_row + 1, currentPosition_column, stairNumber-1);
			int temp2 = maxSum(currentPosition_row + 1, currentPosition_column+1, stairNumber-1);
			memoryMatrix[currentPosition_row][currentPosition_column] = numberTriangle[currentPosition_row][currentPosition_column] + (temp1 > temp2 ? temp1 : temp2);
			return memoryMatrix[currentPosition_row][currentPosition_column];
		}
	}
	else
		return memoryMatrix[currentPosition_row][currentPosition_column];
}
int main(){
	int n;
	freopen("number_triangle.txt","r",stdin);
	cin >> n;
	for (int cnt1 = 0; cnt1 < MAXAMOUNTOFROWS; cnt1++)
		for(int cnt2 = 0; cnt2 < MAXAMOUNTOFROWS; cnt2++)
			memoryMatrix[cnt1][cnt2] = -1;
	for(int cnt1=0; cnt1<n; cnt1++)
		for(int cnt2=0; cnt2<=cnt1; cnt2++)
			scanf("%d", &numberTriangle[cnt1][cnt2]);
	cout << maxSum(0, 0, n);
	return 0;
} 

剪枝结果

在这里插入图片描述
用时不到0.1s,和刚刚的不在一个数量级

动态规划

其实上面的记忆性递推,已经算动态规划。接下来将这一概念进一步提炼

从递归到递推

在这里插入图片描述
递推基出发,计算上面的单元格,递推公式
m a t r i x ( i , j ) = n u m b e r _ t r i a n g l e ( i , j ) + m a x { m a t r i x ( i + 1 , j ) , m a t r i x ( i + 1 , j + 1 ) } matrix(i,j)=number\_triangle(i,j)+max\{matrix(i+1,j),matrix(i+1,j+1)\} matrix(i,j)=number_triangle(i,j)+max{matrix(i+1,j),matrix(i+1,j+1)}
递归基不再赘述

代码

#include<iostream>
#include<fstream>
#define MAXAMOUNTOFROWS 100
using namespace std;
int numberTriangle[MAXAMOUNTOFROWS][MAXAMOUNTOFROWS];
int memoryMatrix[MAXAMOUNTOFROWS][MAXAMOUNTOFROWS];
int main(){
	int n;
	freopen("number_triangle.txt","r",stdin);
	cin >> n;
	for(int cnt1=0; cnt1<n; cnt1++)
		for(int cnt2=0; cnt2<=cnt1; cnt2++)
			scanf("%d", &numberTriangle[cnt1][cnt2]);
	for(int cnt=0; cnt<n; cnt++)
		memoryMatrix[n-1][cnt] = numberTriangle[n-1][cnt];
	for(int cnt1 = n-2; cnt1 >= 0; cnt1--)
		for(int cnt2 = 0; cnt2 <= cnt1; cnt2++)
			memoryMatrix[cnt1][cnt2] = numberTriangle[cnt1][cnt2] + (memoryMatrix[cnt1+1][cnt2] > memoryMatrix[cnt1+1][cnt2+1] ? memoryMatrix[cnt1+1][cnt2] : memoryMatrix[cnt1+1][cnt2+1]);
	cout << memoryMatrix[0][0];
	return 0;
} 

空间复杂度的进一步优化

这里不再赘述,视频里有讲解,算法本身并无变化

动态规划的一般思路

Created with Raphaël 2.2.0 开始 递归式的思考:将原问题分解为子问题 设置保存子问题的解的机制 明确状态空间 确定初始状态 确定状态转移方程 结束

整个程序的时间复杂度等于状态空间的元素数与求解每个状态的时间之积

什么样的问题可以使用动态规划

  1. 具有最优子结构性
    如果问题的最优解所包含的子问题的解也是最优的
  2. 无后效性(路径无关)
    当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态值有关,和之前是采取那种手段或经过哪条路径演变到当前的这若干个状态,没有关系

题外话

假设,数字三角形里的数字 x ∈ [ a , b ] , a n d   x ∈ Z x\in [a,b],and\ x\in Z x[a,b],and xZ,服从均匀分布,数字三角形的阶数为 N N N,那么上述问题的期望是多少
沿用上述递推的思路

事件空间

在递推的各层中,同层内的最大和的事件空间是相同的:
最低层的事件空间显然是 { x ∈ [ a , b ]   ∣   x ∈ Z } \{x\in [a,b]\ |\ x\in Z\} {x[a,b]  xZ},而且 ∀ x , P ( x ) = 1 b − a + 1 \forall x,P(x) = \frac{1}{b-a+1} x,P(x)=ba+11
上一层的事件空间就变成了 { x ∈ [ 2 a , 2 b ]   ∣   x ∈ Z } \{x\in [2a,2b]\ |\ x\in Z\} {x[2a,2b]  xZ},因为会将下面一层的最大和矩阵元素与本层数字三角形对应元素相加。
依次类推

选择和相加

要想计算出期望,就要知道最上层的概率分布,而问题是递推型的,可以求通式
假设已经知道某层的概率分布和事件空间 { a 1   a 2 ⋯   a N } \{a_1\ a_2\cdots \ a_N\} {a1 a2 aN}
求上一层的概率分布(事件空间已知)
首先是选择出元素正下方元素的最大的那个
P ( m a x { X , Y } = k ) = P ( X = k ) ∑ i = 1 k P ( Y = i ) + P ( Y = k ) ∑ i = 1 k P ( X = i ) − P ( X = k ) P ( Y = k ) P(max\{X,Y\}=k)=P(X=k)\sum\limits_{i=1}^{k}P(Y=i)+P(Y=k)\sum\limits_{i=1}^{k}P(X=i)-P(X=k)P(Y=k) P(max{X,Y}=k)=P(X=k)i=1kP(Y=i)+P(Y=k)i=1kP(X=i)P(X=k)P(Y=k)
矩阵形式
定义向量
P m a x = [ P ( m a x { X , Y } = a 1 )   P ( m a x { X , Y } = a 2 ) ⋯ P ( m a x { X , Y } = a N ) ] T P_{max}=[P(max\{X,Y\}=a_1)\ P(max\{X,Y\}=a_2)\cdots P(max\{X,Y\}=a_N)]^T Pmax=[P(max{X,Y}=a1) P(max{X,Y}=a2)P(max{X,Y}=aN)]T
P x = [ P ( X = a 1 )   P ( X = a 2 ) ⋯ P ( X = a N ) ] T P_x=[P(X=a_1)\ P(X=a_2)\cdots P(X=a_N)]^T Px=[P(X=a1) P(X=a2)P(X=aN)]T
P y = [ P ( Y = a 1 )   P ( Y = a 2 ) ⋯ P ( Y = a N ) ] T P_y=[P(Y=a_1)\ P(Y=a_2)\cdots P(Y=a_N)]^T Py=[P(Y=a1) P(Y=a2)P(Y=aN)]T
定义矩阵
A = [ P ( X = a 1 ) P ( X = a 2 ) P ( X = a 2 ) ⋮ ⋱ P ( X = a N ) ⋯ P ( X = a N ) ] A= \left[ \begin{matrix} P(X=a_1)&&&\\ P(X=a_2)&P(X=a_2)&&\\ \vdots&&\ddots&\\ P(X=a_N)&\cdots&&P(X=a_N)\\ \end{matrix} \right] A=P(X=a1)P(X=a2)P(X=aN)P(X=a2)P(X=aN)
B = [ P ( Y = a 1 ) P ( Y = a 2 ) P ( Y = a 2 ) ⋮ ⋱ P ( Y = a N ) ⋯ P ( Y = a N ) ] B= \left[ \begin{matrix} P(Y=a_1)&&&\\ P(Y=a_2)&P(Y=a_2)&&\\ \vdots&&\ddots&\\ P(Y=a_N)&\cdots&&P(Y=a_N)\\ \end{matrix} \right] B=P(Y=a1)P(Y=a2)P(Y=aN)P(Y=a2)P(Y=aN)
C = [ P ( Y = a 1 ) P ( Y = a 2 ) ⋱ P ( Y = a N ) ] C= \left[ \begin{matrix} P(Y=a_1)&&&\\ &P(Y=a_2)&&\\ &&\ddots&\\ &&&P(Y=a_N)\\ \end{matrix} \right] C=P(Y=a1)P(Y=a2)P(Y=aN)

P m a x = A P y + ( B − C ) P x P_{max}=AP_y+(B-C)P_x Pmax=APy+(BC)Px
然后计算相加的概率分布
上面讨论过,可以知道和的事件空间扩大了
随机变量 S 1 S_1 S1就是上面的 m a x { X , Y } max\{X,Y\} max{X,Y}
另一个 S 2 S_2 S2最底层 X X X Y Y Y的事件空间和概率分布完全相同
所以 S 2 S_2 S2内事件各元素概率相等,等于 1 b − a + 1 \frac{1}{b-a+1} ba+11
通过构造矩阵
D = [ P m a x P m a x ⋱ P m a x ] D= \left[ \begin{matrix} P_{max}&&&\\ &P_{max}&&\\ &&\ddots&\\ &&&P_{max}\\ \end{matrix} \right] D=PmaxPmaxPmax
这是一个长方形矩阵,每列代表着从 S 2 S_2 S2中任意挑选出一个元素,和的概率分布
所以同一行的元素之和就是对应元素的概率值
P s u m = 1 b − a + 1 D   M a l l   o n e P_{sum}=\frac{1}{b-a+1}D\ M_{all\ one} Psum=ba+11D Mall one
M a l l   o n e M_{all\ one} Mall one是全一列向量,可使对行求和
P s u m P_{sum} Psum又是新的分布,继续迭代至最高层得到 P t o p P_{top} Ptop
和最高层事件空间向量 E t o p = [ N a   ( N a + 1 )   ⋯ N b ] T E_{top}=[Na\ (Na+1)\ \cdots Nb]^T Etop=[Na (Na+1) Nb]T
期望是 E t o p T P t o p E_{top}^TP_{top} EtopTPtop

matlab程序


N = 10;
a = 1;
b = 5;

P=ones(b-a+1,N)/(b-a+1);
for cnt1 = 1:N-1
    sizeOfMatrix = size(P);
    temp2 = zeros(sizeOfMatrix(1)+b-a, N-cnt1);
    for cnt2 = 1:N-cnt1
        px = P(:,cnt2);
        py = P(:,cnt2+1);
        PX = tril(ones(length(px))) .* repmat(px, 1, length(px));
        PY = (tril(ones(length(px))) - eye(length(px))) .* repmat(px, 1, length(px));
        possibilityOfMax = PX * py + PY * px;
        temp1 = zeros(length(possibilityOfMax)+b-a, b-a+1);
        for cnt3 = 1:b-a+1
            temp1(cnt3:cnt3+length(possibilityOfMax)-1, cnt3) = possibilityOfMax;
        end
        temp2(:,cnt2) = temp1*ones(b-a+1, 1)./(b-a+1);
    end
    P = temp2;
end
eventSpace = [N*a:1:N*b];
eventSpace * P

由于多次迭代,会使得事件空间中个事件的概率变得很小,所以即便原理正确,依然会有精度丢失误差

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值