题目
第一个想法:递归
这个问题满足递归的结构,本质上相当于深度优先遍历一个二叉树,没什么难度,直接给出代码,时间复杂度为 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;
}
空间复杂度的进一步优化
这里不再赘述,视频里有讲解,算法本身并无变化
动态规划的一般思路
整个程序的时间复杂度等于状态空间的元素数与求解每个状态的时间之积
什么样的问题可以使用动态规划
- 具有最优子结构性
如果问题的最优解所包含的子问题的解也是最优的 - 无后效性(路径无关)
当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态值有关,和之前是采取那种手段或经过哪条路径演变到当前的这若干个状态,没有关系
题外话
假设,数字三角形里的数字
x
∈
[
a
,
b
]
,
a
n
d
x
∈
Z
x\in [a,b],and\ x\in Z
x∈[a,b],and x∈Z,服从均匀分布,数字三角形的阶数为
N
N
N,那么上述问题的期望是多少
沿用上述递推的思路
事件空间
在递推的各层中,同层内的最大和的事件空间是相同的:
最低层的事件空间显然是
{
x
∈
[
a
,
b
]
∣
x
∈
Z
}
\{x\in [a,b]\ |\ x\in Z\}
{x∈[a,b] ∣ x∈Z},而且
∀
x
,
P
(
x
)
=
1
b
−
a
+
1
\forall x,P(x) = \frac{1}{b-a+1}
∀x,P(x)=b−a+11
上一层的事件空间就变成了
{
x
∈
[
2
a
,
2
b
]
∣
x
∈
Z
}
\{x\in [2a,2b]\ |\ x\in Z\}
{x∈[2a,2b] ∣ x∈Z},因为会将下面一层的最大和矩阵元素与本层数字三角形对应元素相加。
依次类推
选择和相加
要想计算出期望,就要知道最上层的概率分布,而问题是递推型的,可以求通式
假设已经知道某层的概率分布和事件空间
{
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=1∑kP(Y=i)+P(Y=k)i=1∑kP(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+(B−C)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}
b−a+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=⎣⎢⎢⎡PmaxPmax⋱Pmax⎦⎥⎥⎤
这是一个长方形矩阵,每列代表着从
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=b−a+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
由于多次迭代,会使得事件空间中个事件的概率变得很小,所以即便原理正确,依然会有精度丢失误差