zoukankan      html  css  js  c++  java
  • OBST(Optimal Binary Tree最优二叉搜索树)

    二叉搜索树

    二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。

    一、什么是最优二叉查找树

    最优二叉查找树:

    给定n个互异的关键字组成的序列K=<k1,k2,...,kn>,且关键字有序(k1<k2<...<kn),我们想从这些关键字中构造一棵二叉查找树。对每个关键字ki,一次搜索搜索到的概率为pi。可能有一些搜索的值不在K内,因此还有n+1个“虚拟键”d0,d1,...,dn,他们代表不在K内的值。具体:d0代表所有小于k1的值,dn代表所有大于kn的值。而对于i = 1,2,...,n-1,虚拟键di代表所有位于ki和ki+1之间的值。对于每个虚拟键,一次搜索对应于di的概率为qi。要使得查找一个节点的期望代价(代价可以定义为:比如从根节点到目标节点的路径上节点数目)最小,就需要建立一棵最优二叉查找树。

    图一显示了给定上面的概率分布pi、qi,生成的两个二叉查找树的例子。图二就是在这种情况下一棵最优二叉查找树。

    概率分布:

    i

    0

    1

    2

    3

    4

    5


    pi

     

    0.15

    0.10

    0.05

    0.10

    0.20

    qi

    0.05

    0.10

    0.05

    0.05

    0.05

    0.10

    已知每个关键字以及虚拟键被搜索到的概率,可以计算出一个给定二叉查找树内一次搜索的期望代价。假设一次搜索的实际代价为检查的节点的个数,即所发现的节点的深度加1.计算一次搜索的期望代价等式为:

    建立一棵二叉查找树,如果是的上式最小,那么这棵二叉查找树就是最优二叉查找树

    而且有下式成立:

    二、最优二叉查找树的最优子结构

    最优子结构:

    如果一棵最优二叉查找树T有一棵包含关键字ki,..,kj的子树T',那么这可子树T'对于关键字Ki,...,kj和虚拟键di-1,...dj的子问题也必定是最优的。可以应用剪贴法证明。

    根据最优子结构,寻找最优解:

    给定关键字ki,...,kj,假设kr(i<=r<=j)是包含这些键的一棵最优子树的根。其左子树包含关键字ki,...,kr-1和虚拟键di-1,...,dr-1,右子树包含关键字kr+1,...,kj和虚拟键dr,...dj。我们检查所有的候选根kr,就保证可以找到一棵最优二叉查找树。

    递归解:

    定义e[i,j]为包含关键字ki,...,kj的最优二叉查找树的期望代价,最终要计算的是e[1,n]。

    当j = i - 1时,此时子树中只有虚拟键,期望搜索代价为e[i,i - 1] = qi-1.

    当j >= i时,需要从ki,...,kj中选择一个根kr,然后分别构造其左子树和右子树。下面需要计算以kr为根的树的期望搜索代价。然后选择导致最小期望搜索代价的kr做根。

    现在需要考虑的是,当一棵树成为一个节点的子树时,期望搜索代价怎么变化?子树中每个节点深度都增加1.期望搜索代价增加量为子树中所有概率的总和。

    对一棵关键字ki,...,kj的子树,定义其概率总和为:

    因此,以kr为根的子树的期望搜索代价为:

    因此e[i,j]可以进一步写为:

    这样推导出最终的递归公式为:

     1 #include <iostream>
     2 #include <cstdio>
     3 #define INF 0xFFFFF
     4 using namespace std;
     5 double p[21], q[21];
     6 int root[21][21];//记录最优子树的根节点位置
     7 double w[21][21];//w[i][j]:最优子树概率总和
     8 double e[21][21];//e[i][j]: (最优)子树期望代价
     9 
    10 void optimalBST(int n)
    11 {
    12     for(int i = 1; i<=n+1; i++)
    13     {
    14         w[i][i-1] = q[i-1];
    15         e[i][i-1] = q[i-1];
    16     }
    17 
    18     for(int len = 1; len<=n; len++)
    19     {
    20         for(int i = 1; i<=n-len+1; i++)
    21         {
    22             int j  = i+len-1;
    23             e[i][j] = INF;
    24             w[i][j] = w[i][j-1] + p[j] + q[j];
    25             for(int r = i; r<=j; r++)
    26             {
    27                 double t = e[i][r-1] + e[r+1][j] + w[i][j];
    28                 if(t<e[i][j])
    29                 {
    30                     e[i][j]=t;
    31                     root[i][j] = r;
    32                 }
    33             }
    34         }
    35     }
    36 }
    37 
    38 int main()
    39 {
    40     int n;
    41     while(scanf("%d", &n)!=EOF)
    42     {
    43         getchar();
    44         for(int i = 1; i<=n; i++)
    45             scanf("%lf", &p[i]);
    46         getchar();
    47         for(int i =0; i<=n; i++)
    48             scanf("%lf", &q[i]);
    49         getchar();
    50         optimalBST(n);
    51         printf("%.3lf
    ", e[1][n]);
    52     }
    53 }
    View Code

    参考代码:

      1 //最优二叉查找树
      2 
      3 #include <iostream>
      4 
      5 using namespace std;
      6 
      7 const int MaxVal = 9999;
      8 
      9 const int n = 5;
     10 //搜索到根节点和虚拟键的概率
     11 double p[n + 1] = {-1,0.15,0.1,0.05,0.1,0.2};
     12 double q[n + 1] = {0.05,0.1,0.05,0.05,0.05,0.1};
     13 
     14 int root[n + 1][n + 1];//记录根节点
     15 double w[n + 2][n + 2];//子树概率总和
     16 double e[n + 2][n + 2];//子树期望代价
     17 
     18 void optimalBST(double *p,double *q,int n)
     19 {
     20     //初始化只包括虚拟键的子树
     21     for (int i = 1;i <= n + 1;++i)
     22     {
     23         w[i][i - 1] = q[i - 1];
     24         e[i][i - 1] = q[i - 1];
     25     }
     26 
     27     //由下到上,由左到右逐步计算
     28     for (int len = 1;len <= n;++len)
     29     {
     30         for (int i = 1;i <= n - len + 1;++i)
     31         {
     32             int j = i + len - 1;
     33             e[i][j] = MaxVal;
     34             w[i][j] = w[i][j - 1] + p[j] + q[j];
     35             //求取最小代价的子树的根
     36             for (int k = i;k <= j;++k)
     37             {
     38                 double temp = e[i][k - 1] + e[k + 1][j] + w[i][j];
     39                 if (temp < e[i][j])
     40                 {
     41                     e[i][j] = temp;
     42                     root[i][j] = k;
     43                 }
     44             }
     45         }
     46     }
     47 }
     48 
     49 //输出最优二叉查找树所有子树的根
     50 void printRoot()
     51 {
     52     cout << "各子树的根:" << endl;
     53     for (int i = 1;i <= n;++i)
     54     {
     55         for (int j = 1;j <= n;++j)
     56         {
     57             cout << root[i][j] << " ";
     58         }
     59         cout << endl;
     60     }
     61     cout << endl;
     62 }
     63 
     64 //打印最优二叉查找树的结构
     65 //打印出[i,j]子树,它是根r的左子树和右子树
     66 void printOptimalBST(int i,int j,int r)
     67 {
     68     int rootChild = root[i][j];//子树根节点
     69     if (rootChild == root[1][n])
     70     {
     71         //输出整棵树的根
     72         cout << "k" << rootChild << "是根" << endl;
     73         printOptimalBST(i,rootChild - 1,rootChild);
     74         printOptimalBST(rootChild + 1,j,rootChild);
     75         return;
     76     }
     77 
     78     if (j < i - 1)
     79     {
     80         return;
     81     }
     82     else if (j == i - 1)//遇到虚拟键
     83     {
     84         if (j < r)
     85         {
     86             cout << "d" << j << "" << "k" << r << "的左孩子" << endl;
     87         }
     88         else
     89             cout << "d" << j << "" << "k" << r << "的右孩子" << endl;
     90         return;
     91     }
     92     else//遇到内部结点
     93     {
     94         if (rootChild < r)
     95         {
     96             cout << "k" << rootChild << "" << "k" << r << "的左孩子" << endl;
     97         }
     98         else
     99             cout << "k" << rootChild << "" << "k" << r << "的右孩子" << endl;
    100     }
    101 
    102     printOptimalBST(i,rootChild - 1,rootChild);
    103     printOptimalBST(rootChild + 1,j,rootChild);
    104 }
    105 
    106 int main()
    107 {
    108     optimalBST(p,q,n);
    109     printRoot();
    110     cout << "最优二叉树结构:" << endl;
    111     printOptimalBST(1,n,-1);
    112 }

    我们将表e、w以及root旋转45°,便于查看上述程序的计算过程。上述代码核心在于函数optimalBST,其计算顺序是从下到上、从左到右。首先是依据概率数组pi、qi初始化:给最下面的一行赋值。然后是三个for循环:从下到上计算表中每一行的值,可以充分利用前面计算出来的结果。如果每当计算e[i][j]的时候都从头开始计算w[i][j],那么需要O(j-i)步加法,但是将这些值保存在表w[1...n+1][0...n]中,就避免这些复杂的计算。

    输出结果:

  • 相关阅读:
    JVM内存划分
    AIO
    软件精华收藏-[Windows] Photoshop 八零后的回忆版(只是40M)
    如何在 Ubuntu 中切换多个 PHP 版本
    wamp server 3.2.2.2 (64) 设置局域网访问
    WordPress站点遇到了致命错误解决方法,请查看您的站点的管理电子邮箱来获得指引
    WordPress主题开发:开启文章缩略图功能
    如何修改discuz首页logo
    Discuz怎么设置VIP用户组,dz用户vip组在哪添加
    discuz帖子中的图片和文字如何增加超链接呢?
  • 原文地址:https://www.cnblogs.com/zpfbuaa/p/4953898.html
Copyright © 2011-2022 走看看