动态规划——最优二叉查找树问题(C代码实现)

引入

假设你要把一本英文书翻译成中文,翻译的过程中,你需要在词汇库中寻找每一个单词。假设每个词都已经按照字母序排列好,用二分法寻找单词,这个过程可以被抽象成一棵二叉查找树。
但是我们可能会遇到这种情况:一个单词出现频率特别高但它离根节点特别远。另一个词出现频率特别低,但它离根节点特别近,这样会导致搜索过程地比较次数大幅增加。除此之外,有一些生僻词可能并没有出现在你的词汇库中,每次遇到这些词的时候你都需要经过最差的比较次数。
为了减少搜索过程中的比较,我们可以把词汇出现的频率列入二叉树建立的考察中。
下面对这个问题进行形式化定义:
给定一个 n n n个不同关键字的已排序序列 K = < k 1 , k 2 , … , k n > K=<k_1,k_2,…,k_n> K=<k1,k2,,kn>,(因此 k 1 < k 2 < … < k n k_1<k_2<…<k_n k1<k2<<kn)。对每一个关键字 k i k_i ki,都有一个概率 p i p_i pi表示其搜索概率。有些要搜索的值可能不在 K K K中,因此我们还有 n + 1 n+1 n+1个“伪关键字” d 0 , d 1 , d 2 , … , d n d_0,d_1,d_2,…,d_n d0,d1,d2,,dn表示不在 K K K中的值。 d 0 d_0 d0表示所有小于 k 1 k_1 k1的值, d n d_n dn表示所有大于 k n k_n kn的值,对 i = 1 , 2 , … , n − 1 i=1,2,…,n-1 i=1,2,,n1,伪关键字 d i d_i di表示所有在 k i k_i ki k i + 1 k_{i+1} ki+1之间的值。对每一个伪关键字 d i d_i di,都有一个概率 q i q_i qi表示其搜索概率。每次搜索要么成功(找到某个关键字 k i k_i ki)要么失败(找到某个伪关键字 d i d_i di),因此有如下公式:
∑ i = 1 n p i + ∑ i = 0 n q i = 1 \sum_{i=1}^{n}p_i+\sum_{i=0}^{n}q_i=1 i=1npi+i=0nqi=1
由于我们知道每个关键字和伪关键字的搜索概率,因而可以确定在一棵给定的二叉搜索树 T T T中进行一次搜索的期望代价为:
E [ T 中 搜 索 代 价 ] = ∑ i = 1 n ( d e p t h T ( k i ) + 1 ) × p i + ∑ i = 0 n ( d e p t h T ( d i ) + 1 ) × q i = 1 + ∑ i = 1 n d e p t h T ( k i ) × p i + ∑ i = 0 n d e p t h T ( d i ) × q i E[T中搜索代价]=\sum_{i=1}^{n}(depth_T(k_i)+1)×p_i+\sum_{i=0}^{n}(depth_T(d_i)+1)×q_i=1+\sum_{i=1}^{n}depth_T(k_i)×p_i+\sum_{i=0}^{n}depth_T(d_i)×q_i E[T]=i=1n(depthT(ki)+1)×pi+i=0n(depthT(di)+1)×qi=1+i=1ndepthT(ki)×pi+i=0ndepthT(di)×qi
d e p t h T 表 示 某 个 结 点 在 树 T 中 的 深 度 depth_T表示某个结点在树T中的深度 depthTT
最优二叉搜索树不一定是高度最矮的,频率最高的关键字也未必是一个最优二叉搜索树的根节点。如果用暴力枚举法来计算, n n n个结点能产生的二叉树的数量为 Ω ( 4 n / n 3 / 2 ) \Omega(4^n/n^{3/2}) Ω(4n/n3/2),因此我们采用动态规划法来解决这个问题。

用动态规划法解决最优二叉查找树问题

优化子结构

如果一颗最优二叉树 T T T有一颗包含关键字 k i , … , k j k_i,…,k_j ki,,kj的子树 T ′ T' T,那 T ′ T' T必然是包含关键字 k i , … , k j k_i,…,k_j ki,,kj和伪关键字 d i − 1 , … , d j d_{i-1},…,d_j di1,,dj的子问题的最优解。

证明: 如果存在一颗子树 T ′ ′ T'' T包含和 T ′ T' T相同的元素且期望搜索代价更低,那么我们可以把 T ′ T' T的位置用 T ′ ′ T'' T替换掉,从而使 T T T的期望搜索代价更低。

下面我们假设在由 k i , … , k j k_i,…,k_j ki,,kj这串序列构成的树中,选取 k r k_r kr作为根结点能使之成为最优二叉查找树。那么 k r k_r kr的左子树中就包括 k i , … , k r − 1 k_i,…,k_{r-1} ki,,kr1及伪关键字 d i − 1 , … , d r − 1 d_{i-1},…,d_{r-1} di1,,dr1,它的右子树中就包括 k r + 1 , … , k j k_{r+1},…,k_j kr+1,,kj及伪关键字 d r , … , d j d_{r},…,d_{j} dr,,dj。只要我们检查所有可能的根节点 k r ( i ≤ r ≤ j ) k_r(i≤r≤j) kr(irj),并对每种情况分别求解包含 k i , … , k r − 1 k_i,…,k_{r-1} ki,,kr1及包含 k r + 1 , … , k j k_{r+1},…,k_j kr+1,,kj的最优二叉查找树,即可保证找到原问题的最优解。

递归求解方案

我们先定义两个二维数组, e [ i , j ] e[i,j] e[i,j]为由从 i i i j j j的结点构成的二叉搜索树进行一次搜索的期望代价, w [ i , j ] w[i,j] w[i,j]为从 i i i j j j的结点的概率之和。最后我们希望求得的是 e [ 1 , n ] e[1,n] e[1,n]

注意: w [ i , j ] = ∑ l = i j p l + ∑ l = i − 1 j q l w[i,j]=\sum_{l=i}^{j}p_l+\sum_{l=i-1}^{j}q_l w[i,j]=l=ijpl+l=i1jql

先来考虑递归到底的情况,递归到最底的时候肯定只有一个关键字。但值得注意的是在这个问题中还有伪关键字,因此我们需要考虑在左右范围的下标( i , j i,j ij)是什么情况下时构造出来的子树只有一个伪关键字的结点。
前面提到在由 k i , … , k j k_i,…,k_j ki,,kj这串序列构成的树中,选取 k r k_r kr作为根结点能使之成为最优二叉查找树。那么 k r k_r kr的左子树中就包括 k i , … , k r − 1 k_i,…,k_{r-1} ki,,kr1及伪关键字 d i − 1 , … , d r − 1 d_{i-1},…,d_{r-1} di1,,dr1,它的右子树中就包括 k r + 1 , … , k j k_{r+1},…,k_j kr+1,,kj及伪关键字 d r , … , d j d_{r},…,d_{j} dr,,dj。那么当这个左子树只有一个伪关键字的时候显然 r = i r=i r=i,此时它的左子树就应该是 k i , … , k i − 1 k_i,…,k_{i-1} ki,,ki1,即并没有关键字结点。则再对这个左子树寻找最优子树时, ( n e w ) i = ( o l d ) i , j = ( o l d ) i − 1 (new)i=(old)i,j=(old)i-1 (new)i=(old)i,j=(old)i1。由此我们就知道了递归到底的情况是 j = i − 1 j=i-1 j=i1。即 j = i − 1 j=i-1 j=i1 e [ i , j ] = q i − 1 e[i,j]=q_{i-1} e[i,j]=qi1(其实是 q i − 1 ∗ 1 q_{i-1}*1 qi11,只有结点的时候只用比较一次,这里把 ∗ 1 *1 1省略掉了)。
那么对于 j ≥ i j≥i ji的情况呢?
首先想到的是 e [ i , j ] e[i,j] e[i,j]等于它的两个子树的期望简单相加之后再加上树根的概率,但其实当我们在两棵子树上新找到一个结点作为它们的根的时候,这两个子树的每一个节点的深度都加了 1 1 1,也就是说去搜索子树里的节点的比较次数全多了一次,即还得再加上一次所有结点的概率(和刚才 q i − 1 ∗ 1 q_{i-1}*1 qi11省略了 ∗ 1 *1 1一样,这里多比较了一次,体现在公式上就是再补加上全体结点的概率)。因此 j ≥ i j≥i ji
e [ i , j ] = e [ i , r − 1 ] + e [ r + 1 , j ] + p r + w [ i , r − 1 ] + w [ r + 1 , j ] e[i,j]=e[i,r-1]+e[r+1,j]+p_r+w[i,r-1]+w[r+1,j] e[i,j]=e[i,r1]+e[r+1,j]+pr+w[i,r1]+w[r+1,j]

e [ i , r − 1 ] + e [ r + 1 , j ] + p r e[i,r-1]+e[r+1,j]+p_r e[i,r1]+e[r+1,j]+pr:它的两个子树的期望简单相加之后再加上树根的概率
w [ i , r − 1 ] + w [ r + 1 , j ] w[i,r-1]+w[r+1,j] w[i,r1]+w[r+1,j]:因为深度加深了1,再补加上了一次所有结点的概率

而我们很容易发现:
w [ i , j ] = w [ i , r − 1 ] + p r + w [ r + 1 , j ] w[i,j]=w[i,r-1]+p_r+w[r+1,j] w[i,j]=w[i,r1]+pr+w[r+1,j]
因此原式可以变为:
e [ i , j ] = e [ i , r − 1 ] + e [ r + 1 , j ] + w [ i , j ] e[i,j]=e[i,r-1]+e[r+1,j]+w[i,j] e[i,j]=e[i,r1]+e[r+1,j]+w[i,j]
据此我们终于可以写出递归公式:
e [ i , j ] = { q i − 1 , if  j = i − 1   e [ i , j ] = e [ i , r − 1 ] + e [ r + 1 , j ] + w [ i , j ] , if  j ≥ i e[i,j]= \begin{cases} q_{i-1}, & \text {if $j=i-1$ } \\e[i,j]=e[i,r-1]+e[r+1,j]+w[i,j], & \text{if $j≥i$} \end{cases} e[i,j]={qi1,e[i,j]=e[i,r1]+e[r+1,j]+w[i,j],if j=iif ji

C代码填表

void OPTIMAL_BST(int p[],int q[],int n,int **e,int **w,int **root)
{
	int i,j,l,r,t;
	for(i=1;i<=n+1;i++)
	{
		e[i][i-1]=q[i-1];
		w[i][i-1]=q[i-1];
	}
	for(l=1;l<=n;l++)
	{
		for(i=1;i<=n-l+1;i++)
		{
			j=i+l-1;
			e[i][j]=INT_MAX;
			w[i][j]=w[i][j-1]+p[j]+q[j];
			for(r=i;r<=j;r++)
			{
				t=e[i][r-1]+e[r+1][j]+w[i][j];
				if(t<e[i][j])
				{
					e[i][j]=t;
					root[i][j]=r;
				}
			}
		}
	}
	return;
}
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值