Fork me on GitHub

算法导论读书笔记(19)

最优二叉搜索树

最优二叉搜索树 (optimal binary search tree)问题的形式化定义如下:给定一个由 n 个互异的关键字组成的序列 K = < k1 , k2 , … , kn >,且关键字有序(有 k1 < k2 < … < kn ),我们要从这些关键字中构造一棵二叉查找树。对每个关键字 ki ,一次搜索为 ki 的概率是 pi 。某些搜索的值可能不在 K 内,因此还有 n + 1 个“虚拟键” d0 , d1 , … , dn 代表不在 K 内的值。其中, d0 代表所有小于 k1 的值, dn 代表所有大于 kn 的值,而对于 i = 1, 2, …, n - 1 ,虚拟键 di 代表所有位于 kiki+1 之间的值。对每个虚拟键 di ,一次搜索对应于 di 的概率是 qi 。下图是 n = 5个关键字的集合上的两棵二叉查找树。

其中各结点的概率如下表所示:

每个关键字 ki 是一个内部结点,每个虚拟键 di 是一个叶子。每次搜索要么成功(找到某个关键字 ki ),要么失败(找到某个虚拟键 di ),因此有如下公式:

因为已知了每个关键字和每个虚拟键被搜索的概率,因而可以确定在一棵给定的二叉查找树 T 内一次搜索的期望代价。假设一次搜索的实际代价为检查的结点个数,即在 T 内搜索所发现的结点的深度加1.所有在 T 内一次搜索的期望代价为

其中 depthT 代表树 T 内一个结点的深度。

对给定的一组概率,我们的目标是构造一个期望搜索代价最小的二叉查找树。把这种树称为最优二叉查找树。下面将使用动态规划方法来解决这个问题。

步骤1:一棵最优二叉查找树的结构

为描述一棵最优二叉查找树的最优子结构,首先要看它的子树。一棵二叉查找树的任意一棵子树必定包含在连续范围内的关键字 ki ,…, kj ,有 1 <= i <= j <= n 。另外,一棵含有关键字 ki ,…, kj 的子树必定也含有虚拟键 di-1 ,…, dj 作为叶子。

现在我们可以描述最优子结构:如果一棵最优二叉查找树 T 有一棵包含关键字 ki ,…, kj 的子树 T’ ,那么这棵子树 T‘ 对于关键字 ki ,…, kj 和虚拟键 di-1 ,…, dj 的子问题也必定是最优的。

使用最优子结构来说明可以根据子问题的最优解,来构造原问题的一个最优解。给定关键字 ki ,…, kj ,假设 kri <= r <= j ),将是包含这些键的一棵最优子树的根。根 kr 的左子树包含关键字 ki ,…, kr-1 (和虚拟键 di-1 ,…, dr-1 ),右子树包含关键字 kr+1 ,…, kj (和虚拟键 dr ,…, dj )。我们只要检查所有的候选根 kr ,并且确定所有包含关键字 ki ,…, kr-1kr+1 ,…, kj 的最优二叉查找树,就可以保证找到一棵最优的二叉查找树。

步骤2:一个递归解

现在可以递归地定义一个最优解的值了。选取子问题域为找一个包含关键字 ki ,…, kj 的最优二叉查找树,其中 i >= 1 , j <= nj >= i - 1。(当 j >= i - 1时没有真是的关键字,只有虚拟键 di-1 )。定义 e [ i , j ]为搜索一棵包含关键字 ki ,…, kj 的最优二叉查找树的期望代价。最终,我们要计算的是 e [ 1 , n ]。

j = i - 1时出现简单情况。此时只有虚拟键 di-1 。期望的搜索代价为 e [ i , i - 1 ] = qi - 1。

j >= i 时,需要从 ki ,…, kj 中选择一个根 kr ,然后用关键字 ki ,…, kr-1 来构造一棵最优二叉查找树作为其左子树,并用关键字 kr+1 ,…, kj 来构造一棵最优二叉查找树作为其右子树。当一棵树成为一个结点的子树时,子树中每个结点的深度增加1。这个子树的期望搜索代价增加为子树中所有概率的总和。对一棵有关键字 ki ,…, kj 的子树,定义概率的总和为

因此,如果 kr 是一棵包含关键字 ki ,…, kj 的最优子树的根,则有

e [ i , j ] = pr + ( e [ i , r - 1 ] + w ( i , r - 1 )) + ( e [ r + 1 , j ] + w ( r + 1 , j ))

注意

w ( i , j ) = w ( i , r - 1 ) + pr + w ( r + 1 , j )

可以将 e [ i , j ]重写为

e [ i , j ] = e [ i , r - 1 ] + e [ r + 1 , j ] + w ( i , j )

假设我们知道该采用哪一个结点 kr 作为根。我们选择有最低期望搜索代价的结点作为根,从而得到最终的递归式:


步骤3:计算一棵最优二叉查找树的期望搜索代价

下面的伪码以概率 p1 ,…, pnq1 ,…, qn 以及规模为 n 为输入,返回表 eroot

OPTIMAL-BST(p, q, n)
1  let e[1 .. n + 1, 0 .. n], w[1 .. n + 1, 0 .. n] and root[1 .. n, 1 .. n] be new tables
2  for i = 1 to n + 1
3      e[i, i - 1] = q_i - 1
4      w[i, i - 1] = q_i - 1
5  for l = 1 to n
6      for i = 1 to n - l + 1
7          j = i + l - 1
8          e[i, j] = ∞
9          w[i, j] = w[i, j - 1] + p_j + q_j
10         for r = i to j
11             t = e[i, r - 1] + e[r + 1, j] + w[i, j]
12             if t < e[i, j]
13                 e[i, j] = t
14                 root[i, j] = r
15 return e and root

此过程的操作比较直观。第1~4行初始化 e [ i , j - 1 ]和 w [ i , j - 1 ]的值。第5~14行的 for 循环利用上面两个递归式来计算 e [ i , j ]和 w [ i , j ],在第10~14行,尝试每个下标 r 以确定使用哪个关键字 kr 来作为包含关键字 ki ,…, kj 的最优二叉查找树的根。无论何时发现一个更好的关键字来作为根,这个 for 循环在 root [ i , j ]中保存下标 r 的当前值。

下图是根据上面二叉查找树的关键字分布,程序 OPTIMAL-BST 计算出的表 e [ i , j ]和 w [ i , j ]和 root [ i , j ]。

OPTIMAL-BST 过程需要 Θ ( n3 )。

posted on 2014-06-22 14:49  sungoshawk  阅读(969)  评论(0编辑  收藏  举报