算法设计与分析实验课-动态规划-最优二分检索树

最优二分检索树


定义

二分类找最优阈值python 最优二分检索树例题_子树

数据集S是升序集,对于最优二分检索树本身和它的所有子树,都满足左孩子<根<右孩子


比较次数

假设在该二叉树中寻找数据x,先与根结点进行比较,比根结点小进入左子树寻找,比根结点大进入右子树寻找,如果与该结点相等则寻找结束(对于每一个结点都把它看成根结点,然后进行同样的操作)。
假设寻找的数据x是数据结点,那么比较次数应该为数据结点的深度+1(深度=层数-1,即根结点深度为0)。
假设寻找的数据x是空隙结点,那么比较次数应该为空隙结点的深度,例如寻找x=1.7,寻找到1的时候,1.7大于1,那么进入右子树,但是右子树只有空隙结点L1,x不属于S,比较次数就为L1的深度。
也即x属于S,比较次数为x在树中的深度加1,x不属于S,比较次数为x在树中的深度


存取概率

二分类找最优阈值python 最优二分检索树例题_子树_02

对于一个二分检索树T,平均比较次数m(T)等于所有结点对应的比较次数乘以其存取概率。

最优二分检索树就是在给定存取概率分布下平均比较次数最少的二分检索树。

二分类找最优阈值python 最优二分检索树例题_检索树_03


子问题分析

二分类找最优阈值python 最优二分检索树例题_二分类找最优阈值python_04

如果T是一棵最优二分检索树,那么它对应的左子树和右子树(根结点确定)都是最优二分检索树,假设左子树有更优的情况,那么在i到j这个范围内也会有更优的二分检索树,这与T是最优二分检索树矛盾,所以不成立。

而对于k=i和k=j这种情况,前者左子树为空,只有空隙结点,后者右子树为空,只有空隙结点,如果某个数据在这个空隙结点范围内,那么它在最初与根结点比较的时候就已经确定了是否属于S(所有结点都首先要和根结点比较),所以在该子树的比较次数中就不用计算比较次数了(已经算到根结点的比较次数中去了,后面会说二分检索树T的平均比较次数等于左子树的加上右子树的加上根结点的),所以比较次数为0。

(这里PPT部分下标写错了,自己明白就行)


递推方程(还是动态规划)

二分类找最优阈值python 最优二分检索树例题_检索树_05

关于为什么要引入w[i][j],其等于i到j所有存取概率之和,因为分别计算了左子树和右子树的平均比较次数,但是把左子树和右子树合并入二分检索树T后,左右子树中所有结点的深度加1,那么新的平均比较次数应该加上1乘以所有结点的存取概率,也即w[i][j],所以引入了w[i][j]。

二分类找最优阈值python 最优二分检索树例题_结点_06

根据这个公式,最少平均比较次数 = min(左子树最少平均比较次数 + 右子树最少平均比较次数 + w[i][j])。

从这里就可以看出来为什么上面说k=i和k=j时对应区间数据比较次数为0了,因为这些比较次数都算到根xk上了。


注意

二分类找最优阈值python 最优二分检索树例题_结点_07


例题

二分类找最优阈值python 最优二分检索树例题_结点_08

#include<bits/stdc++.h>
#define maxsize 1000+5
#define INF 0x3f3f3f3f
int SS[maxsize][maxsize];
double PA[maxsize], PB[maxsize];
double w[maxsize][maxsize];
double m[maxsize][maxsize];
void BST(double PA[maxsize], double PB[maxsize], int n)
{
    //初始化,只有空隙结点的子树的比较次数m为0,只有空隙结点的w的所有结点存取概率和为空隙结点的存取概率。
    for(int i=1; i<=n+1; i++)
    {
        m[i][i-1]=0;
        w[i][i-1]=PA[i-1];
    }
    //区间i到j的长度
    for(int len=1; len<=n; len++)
    {
        //左边界
        for(int l=1; l+len-1<=n; l++)
        {
            //有边界
            int r=l+len-1;
            m[l][r]=INF;
            w[l][r]=w[l][r-1]+PA[r]+PB[r];
            //遍历根结点
            for(int k=l; k<=r; k++)
            {
                double tmp=m[l][k-1]+m[k+1][r]+w[l][r];
                if(tmp<m[l][r])
                {
                    m[l][r]=tmp;
                    //记录这个区间的根结点
                    SS[l][r]=k;
                }
            }
        }
    }
    return ;
}
//题目有错误,应该是先序遍历,先输出根结点,再递归左子树、右子树
//SS保存l到r的根结点,对于每一个结点,把它看成它所在区间内的根结点,保存下来。
void printTree(int l, int r)
{
    if(l>r) return ;
    int mid=SS[l][r];
    std::cout<<mid<<" ";
    printTree(l,mid-1);
    printTree(mid+1,r);
    return ;
}
int main()
{
    int n;
    std::cin>>n;
    std::cin>>PA[0];
    for(int i=1; i<=n; i++)
    {
        std::cin>>PB[i];
        std::cin>>PA[i];
    }
    BST(PA, PB, n);
    printTree(1,n);
    return 0;
}