最优二叉查找树(动态规划)


1. 描述

给定一个有序序列K={k1


2. 分析

(1)最优子结构

一个最优二叉树的子树必定包含连续范围的关键字ki~kj,1 <= i <= j<= n,同时也必须含有连续的虚叶子节点di-1~dj。

如果一棵最优二叉查找树T有一棵含有关键字ki~kj的子树T',那么,T'也是一棵最优查找树,这通过剪贴思想可以证明。

构造最优子结构:在ki~kj中,选定一个r,i <= r <= j,使以kr为根,ki~k(r-1)和k(r+1)~kj为左右孩子的最优二叉树。注意r=i或者r=j的情况,表示左子树或右子树只有虚叶子节点。

(2)递推公式

定义e[i,j]为一棵包含关键字ki~kj的最优二叉树的期望代价。当j=i-1时没有真实的关键在,只有虚叶子节点d(i-1)。

则:

e[i,i-1] = q(i-1)          j = i-1时,

当j >= i时,需要选择合适的kr作为根节点,然后其余节点ki~K(r-1)和k(r+1)~kj构造左右孩子。这时要考虑左右孩子这些节点成为一个节点的子树后,它的搜索代价的变化:它们的期望代价增加了“子树中所有概率的总和”w。

w[i,j] = (pi + ... + pj) + (qi-1 + ... + qj)

于是当 j>= i时,e[i,j]=pr + (e[i,r-1]+w[i,r-1])+(e[r+1,j]+w[r+1,j]) = e[i,r-1] +e[r+1,j]+w[i,j];

(3)计算最优二叉树的期望代价

e[i,j]=

         q(i-1)                                                    j = i-1时

         min{e[i,r-1] +e[r+1,j]+w[i,j]}                  i <= j,其中i <= r <= j

w[i,j] =

          q(i-1)                                                   j = i-1时

           w[i,j]=w[i,j-1]+pj+qj                            i <= j时

3. 算法

算法如下:

void optimalBST(int n, float p[], float q[]) // the length of p and q is (n + 1)
{
    float w[n + 2][n + 1], e[n + 2][n + 1], temp;
    int root[n + 1][n + 1];
    int i, j, l, r;
    for (i = 1;i <= n + 1; i++)
        w[i][i - 1] = e[i][i - 1] = q[i - 1];
    for (l = 1; l <= n; l++)
        for (i = 1, j = l; i <= n - l + 1; i++, j++)
        {
            e[i][j] = INT_MAX;
            w[i][j] = w[i][j - 1] + p[j] + q[j];
            for (r = i; r <= j; r++) 
            {
                temp = e[i][r - 1] + e[r + 1][j] + w[i][j];
                if (e[i][j] > temp)
                {
                    e[i][j] = temp;
                    root[i][j] = r;
                }
            }// for (r = i; )
        }// for
    printf("The optimal search cost is %.2f\n", e[1][n]);
    constructOptimalBST(n, root);
}


root是记录构造过程中选择的根节点,以便构造最优解。

constructOptimalBST函数如下:

void constructOptimalBST(int n, int root[][n + 1])
{
    printf("k%d is the root\n", root[1][n]);
    recursiveConstruct(n, root, root[1][n], 1, n);
}


其中recursiveConstruct函数如下:

void recursiveConstruct(int n, int root[][n + 1], int r, int i, int j)
{
    if (j == i - 1)
    {
        if (i == r)
            printf("d%d is the left child of k%d\n", r - 1, r);
        else 
            printf("d%d is the right child of k%d\n", r, r);
    } else {
        if (i != r) {
            printf("k%d is the left child of k%d\n", root[i][r - 1], r);
            recursiveConstruct(n, root, root[i][r - 1], i, r - 1);
        } else{
            printf("d%d is the left child of k%d\n", r - 1, r);

        if (j != r) {
            printf("k%d is the right child of k%d\n", root[r + 1][j], r);
            recursiveConstruct(n, root, root[r + 1][j], r + 1, j);
        } else
            printf("d%d is the right child of k%d\n", r, r);
    }
} 


测试如下:


#include                                                                                                            
#include 

void optimalBST(int n, float p[], float q[]);
void constructOptimalBST(int n, int root[][n + 1]);
void recursiveConstruct(int n, int root[][n + 1], int r, int i, int j);
int main(void)
{
    float p[] = {-1, 0.15, 0.10, 0.05, 0.10, 0.20};
    float q[] = {0.05, 0.10, 0.05, 0.05, 0.05, 0.10};
    int n = 5;
    optimalBST(n, p, q);
    return 0;
}


输出:



你可能感兴趣的:(算法之美,动态规划,程序设计)