0020算法笔记动态规划最优二叉搜索树问题.docx
《0020算法笔记动态规划最优二叉搜索树问题.docx》由会员分享,可在线阅读,更多相关《0020算法笔记动态规划最优二叉搜索树问题.docx(15页珍藏版)》请在冰豆网上搜索。
0020算法笔记动态规划最优二叉搜索树问题
0020算法笔记——【动态规划】最优二叉搜索树问题
1、问题描速:
设S={x1,x2,···,xn}是一个有序集合,且x1,x2,···,xn表示有序集合的二叉搜索树利用二叉树的顶点存储有序集中的元素,而且具有性质:
存储于每个顶点中的元素x大于其左子树中任一个顶点中存储的元素,小于其右子树中任意顶点中存储的元素。
二叉树中的叶顶点是形如(xi,xi+1)的开区间。
在表示S的二叉搜索树中搜索一个元素x,返回的结果有两种情形:
(1)在二叉树的内部顶点处找到:
x=xi
(2)在二叉树的叶顶点中确定:
x∈(xi ,xi+1)
设在情形
(1)中找到元素x=xi的概率为bi;在情形
(2)中确定x∈(xi ,xi+1)的概率为ai。
其中约定x0=-∞,xn+1=+∞,有
集合{a0,b1,a1,……bn,an}称为集合S的存取概率分布。
最优二叉搜索树:
在一个表示S的二叉树T中,设存储元素xi的结点深度为ci;叶结点(xj,xj+1)的结点深度为dj。
注:
在检索过程中,每进行一次比较,就进入下面一层,对于成功的检索,比较的次数就是所在的层数加1。
对于不成功的检索,被检索的关键码属于那个外部结点代表的可能关键码集合,比较次数就等于此外部结点的层数。
对于图的内结点而言,第0层需要比较操作次数为1,第1层需要比较2次,第2层需要3次。
p表示在二叉搜索树T中作一次搜索所需的平均比较次数。
P又称为二叉搜索树T的平均路长,在一般情况下,不同的二叉搜索树的平均路长是不同的。
对于有序集S及其存取概率分布(a0,b1,a1,……bn,an),在所有表示有序集S的二叉搜索树中找出一棵具有最小平均路长的二叉搜索树。
设Pi是对ai检索的概率。
设qi是对满足ai 对于有n个关键码的集合,其关键码有n!
种不同的排列,可构成的不同二叉搜索树有
棵。
(n个结点的不同二叉树,卡塔兰数)。
如何评价这些二叉搜索树,可以用树的搜索效率来衡量。
例如:
标识符集{1,2,3}={do,if,stop}可能的二分检索树为:
若P1=0.5,P2=0.1,P3=0.05,q0=0.15,q1=0.1,q2=0.05,q3=0.05,求每棵树的平均比较次数(成本)。
Pa(n)=1×p1+2×p2+3×p3+1×q0+2×q1+3×(q2+q3) =1×0.5+2×0.1+3×0.05+1×0.05+2×0.1+3×(0.05+0.05) =1.5
Pb(n)=1×p1+2×p3+3×p2+1×q0+2×q3+ 3×(q1+q2) =1×0.5+2×0.05+3×0.1+1×0.15+2×0.05+3×(0.1+0.05) =1.6
Pc(n)=1×p2+2×(p1+ p3)+2×(q0+q1+q2+q3) =1×0.1+2×(0.5+0.05)+2×(0.15+0.1+0.05+0.05) =1.9
Pd(n)=1×p3+2×p1+3×p2+1×q3+2×q0+3×(q1+q2) =1×0.05+2×0.5+3×0.1+1×0.05+2×0.15+3×(0.1+0.05) =2.15
Pe(n)=1×p3+2×p2+3×p1+1×q3+2×q2+3×(q0+q1) =1×0.05+2×0.1+3×0.5+1×0.05+2×0.15+3×(0.15+0.1) =2.85
因此,上例中的最小平均路长为Pa(n)=1.5。
可以得出结论:
结点在二叉搜索树中的层次越深,需要比较的次数就越多,因此要构造一棵最小二叉树,一般尽量把搜索概率较高的结点放在较高的层次。
2、最优子结构性质:
假设选择k为树根,则1,2,…,k-1和a0,a1,…,ak-1 都将位于左子树L上,其余结点(k+1,…,n和ak,ak+1,…,an)位于右子树R上。
设COST(L)和COST(R)分别是二分检索树T的左子树和右子树的成本。
则检索树T的成本是:
P(k)+COST(L)+COST(R)+……。
若T是最优的,则上式及COST(L)和COST(R)必定都取最小值。
证明:
二叉搜索树T的一棵含有顶点xi ,···,xj和叶顶点(xi-1 ,xi ),···,(xj ,xj+1)的子树可以看作是有序集{xi ,···,xj}关于全集为{xi-1 ,xj+1 }的一棵二叉搜索树(T自身可以看作是有序集)。
根据S的存取分布概率,在子树的顶点处被搜索到的概率是:
。
{xi ,···,xj}的存储概率分布为{ai-1,bi,…,bj,aj },其中,ah,bk分别是下面的条件概率:
。
设Tij是有序集{xi ,···,xj}关于存储概率分布为{ai-1,bi,…,bj,aj}的一棵最优二叉搜索树,其平均路长为pij,Tij的根顶点存储的元素xm,其左子树Tl和右子树Tr的平均路长分别为pl和pr。
由于Tl和Tr中顶点深度是它们在Tij中的深度减1,所以得到:
由于Ti是关于集合{xi ,···,xm-1}的一棵二叉搜索树,故Pl>=Pi,m-1。
若Pl>Pi,m-1,则用Ti,m-1替换Tl可得到平均路长比Tij更小的二叉搜索树。
这与Tij是最优二叉搜索树矛盾。
故Tl是一棵最优二叉搜索树。
同理可证Tr也是一棵最优二叉搜索树。
因此最优二叉搜索树问题具有最优子结构性质。
3、递推关系:
根据最优二叉搜索树问题的最优子结构性质可建立计算pij的递归式如下:
初始时:
记wi,j pi,j为m(i,j),则m(1,n)=w1,n p1,n=p1,n为所求的最优值。
计算m(i,j)的递归式为:
4、求解过程:
1)没有内部节点时,构造T[1][0],T[2][1],T[3][2]……,T[n+1][n]
2)构造只有1个内部结点的最优二叉搜索树T[1][1],T[2][2]…,T[n][n],可以求得m[i][i]同时可以用一个数组存做根结点元素为:
s[1][1]=1,s[2][2]=2…s[n][n]=n
3)构造具有2个、3个、……、n个内部结点的最优二叉搜索树。
……
r(起止下标的差)
0 T[1][1],T[2][2] ,…, T[n][n],
1 T[1][2],T[2][3],…,T[n-1][n],
2 T[1][3],T[2][4],…,T[n-2][n],
……
r T[1][r+1],T[2][r+2],…,T[i][i+r],…,T[n-r][n]
……
n-1 T[1][n]
具体代码如下:
[cpp] viewplain copy
1.//3d11-1 最优二叉搜索树 动态规划
2.#include "stdafx.h"
3.#include
4.using namespace std;
5.
6.const int N = 3;
7.
8.void OptimalBinarySearchTree(double a[],double b[],int n,double **m,int **s,double **w);
9.void Traceback(int n,int i,int j,int **s,int f,char ch);
10.
11.int main()
12.{
13. double a[] = {0.15,0.1,0.05,0.05};
14. double b[] = {0.00,0.5,0.1,0.05};
15.
16. cout<<"有序集的概率分布为:
"<17. for(int i=0; i18. {
19. cout<<"a"<
20. }
21.
22. double **m = new double *[N+2];
23. int **s = new int *[N+2];
24. double **w =new double *[N+2];
25.
26. for(int i=0;i27. {
28. m[i] = new double[N+2];
29. s[i] = new int[N+2];
30. w[i] = new double[N+2];
31. }
32.
33. OptimalBinarySearchTree(a,b,N,m,s,w);
34. cout<<"二叉搜索树最小平均路长为:
"<35. cout<<"构造的最优二叉树为:
"<36. Traceback(N,1,N,s,0,'0');
37.
38. for(int i=0;i39. {
40. delete m[i];
41. delete s[i];
42. delete w[i];
43. }
44. delete[] m;
45. delete[] s;
46. delete[] w;
47. return 0;
48.}
49.
50.void OptimalBinarySearchTree(double a[],double b[],int n,double **m,int **s,double **w)
51.{
52. //初始化构造无内部节点的情况
53. for(int i=0; i<=n; i++)
54. {
55. w[i+1][i] = a[i];
56. m[i+1][i] = 0;
57. }
58.
59. for(int r=0; r60. {
61. for(int i=1; i<=n-r; i++)//i为起始元素下标
62. {
63. int j = i+r;//j为终止元素下标
64.
65. //构造T[i][j] 填写w[i][j],m[i][j],s[i][j]
66. //首选i作为根,其左子树为空,右子树为节点
67. w[i][j]=w[i][j-1]+a[j]+b[j];
68. m[i][j]=m[i+1][j];
69. s[i][j]=i;
70.
71. //不选i作为根,设k为其根,则k=i+1,……j
72. //左子树为节点:
i,i+1……k-1,右子树为节点:
k+1,k+2,……j
73. for(int k=i+1; k<=j; k++)
74. {
75. double t = m[i][k-1]+m[k+1][j];
76.
77. if(t78. {
79. m[i][j]=t;
80. s[i][j]=k;//根节点元素
81. }
82. }
83. m[i][j]+=w[i][j];
84. }
85. }
86.}
87.
88.void Traceback(int n,int i,int j,int **s,int f,char ch)
89.{
90. int k=s[i][j];
91. if(k>0)
92. {
93. if(f==0)
94. {
95. //根
96. cout<<"Root:
"<j):
("<
97. }
98. else
99. {
100. //子树
101. cout<"<j):
("<
102. }
103.
104. int t = k-1;
105. if(t>=i && t<=n)
106. {
107. //回溯左子树
108. Traceback(n,i,t,s,k,'L');
109. }
110. t=k+1;
111. if(t<=j)
112. {
113. //回溯右子树
114. Traceback(n,t,j,s,k,'R');
115. }
116. }
117.}
4、构造最优解:
算法OptimalBinarySearchTree中用s[i][j]保存最优子树T(i,j)的根节点中的元素。
当s[i][n]=k时,xk为所求二叉搜索树根节点元素。
其左子树为T(1,k-1)。
因此,i=s[1][k-1]表示T(1,k-1)的根节点元素为xi。
依次类推,容易由s记录的信息在O(n)时间内构造出所求的最优二叉搜索树。
5、复杂度分析与优化:
算法中用到3个数组m,s和w,故所需空间复杂度为O(n^2)。
算法的主要计算量在于计算
。
对于固定的r,它需要的计算时间O(j-i+1)=O(r+1)。
因此算法所耗费的总时间为:
。
事实上,由《动态规划加速原理之四边形不等式》可以得到:
而此状态转移方程的时间复杂度为O(n^2)。
由此,对算法改进后的代码如下:
[cpp] viewplain copy
1.//3d11-1 最优二叉搜索树 动态规划加速原理 四边形不等式
2.#include "stdafx.h"
3.#include
4.using namespace std;
5.
6.const int N = 3;
7.
8.void OptimalBinarySearchTree(double a[],double b[],int n,double **m,int **s,double **w);
9.void Traceback(int n,int i,int j,int **s,int f,char ch);
10.
11.int main()
12.{
13. double a[] = {0.15,0.1,0.05,0.05};
14. double b[] = {0.00,0.5,0.1,0.05};
15.
16. cout<<"有序集的概率分布为:
"<17. for(int i=0; i18. {
19. cout<<"a"<
20. }
21.
22. double **m = new double *[N+2];
23. int **s = new int *[N+2];
24. double **w =new double *[N+2];
25.
26. for(int i=0;i27. {
28. m[i] = new double[N+2];
29. s[i] = new int[N+2];
30. w[i] = new double[N+2];
31. }
32.
33. OptimalBinarySearchTree(a,b,N,m,s,w);
34. cout<<"二叉搜索树最小平均路长为:
"<35. cout<<"构造的最优二叉树为:
"<36. Traceback(N,1,N,s,0,'0');
37.
38. for(int i=0;i39. {
40. delete m[i];
41. delete s[i];
42. delete w[i];
43. }
44. delete[] m;
45. delete[] s;
46. delete[] w;
47. return 0;
48.}
49.
50.void OptimalBinarySearchTree(double a[],double b[],int n,double **m,int **s,double **w)
51.{
52. //初始化构造无内部节点的情况
53. for(int i=0; i<=n; i++)
54. {
55. w[i+1][i] = a[i];
56. m[i+1][i] = 0;
57. s[i+1][i] = 0;
58. }
59.
60. for(int r=0; r61. {
62. for(int i=1; i<=n-r; i++)//i为起始元素下标
63. {
64. int j = i+r;//j为终止元素下标
65. int i1 = s[i][j-1]>i?
s[i][j-1]:
i;
66. int j1 = s[i+1][j]>i?
s[i+1][j]:
j;
67.
68. //构造T[i][j] 填写w[i][j],m[i][j],s[i][j]
69. //首选i作为根,其左子树为空,右子树为节点
70. w[i][j]=w[i][j-1]+a[j]+b[j];
71. m[i][j]=m[i][i1-1]+m[i1+1][j];
72. s[i][j]=i1;
73.
74. //不选i作为根,设k为其根,则k=i+1,……j
75. //左子树为节点:
i,i+1……k-1,右子树为节点:
k+1,k+2,……j
76. for(int k=i1+1; k<=j1; k++)
77. {
78. double t = m[i][k-1]+m[k+1][j];
79.
80. if(t81. {
82. m[i][j]=t;
83. s[i][j]=k;//根节点元素
84. }
85. }
86. m[i][j]+=w[i][j];
87. }
88. }
89.}
90.
91.void Traceback(int n,int i,int j,int **s,int f,char ch)
92.{
93. int k=s[i][j];
94. if(k>0)
95. {
96. if(f==0)
97. {
98. //根
99. co