最优二叉查找树—动态规划C++

一、问题描述

1、问题描述:
基于统计先验知识,我们可统计出一个数表(集合)中各元素的查找概率,理解为集合各元素的出现频率。比如中文输入法字库中各词条(单字、词组等)的先验概率,针对用户习惯可以自动调整词频——所谓动态调频、高频先现原则,以减少用户翻查次数。这就是最优二叉查找树问题:查找过程中键值比较次数最少,或者说希望用最少的键值比较次数找到每个关键码(键值)。为解决这样的问题,显然需要对集合的每个元素赋予一个特殊属性——查找概率。这样我们就需要构造一颗最优二叉查找树。
  n个键{a1,a2,a3…an},其相应的查找概率为{p1,p2,p3…pn}。构成最优BST,表示为T1n ,求这棵树的平均查找次数C[1, n](耗费最低)。换言之,如何构造这棵最优BST,使得C[1, n] 最小。

二、动态规划算法解题思路

动态规划法策略是将问题分成多个阶段,逐段推进计算,后继实例解由其直接前趋实例解计算得到。对于最优BST问题,利用减一技术和最优性原则,如果前n-1个节点构成最优BST,加入一个节点 an 后要求构成规模n的最优BST。按 n-1, n-2 , … , 2, 1 递归,问题可解。自底向上计算:C[1, 2]→C[1, 3] →… →C[1, n]。为不失一般性用C[i, j] 表示由{a1,a2,a3…an}构成的BST的耗费。其中1≤i ≤j ≤n。这棵树表示为Tij。从中选择一个键ak作根节点,它的左子树为Tik-1,右子树为Tk+1j。要求选择的k 使得整棵树的平均查找次数C[i, j]最小。左右子树递归执行此过程。(根的生成过程)
引用书上求解递归式:

以输入节点概率 :0.10 0.15 0.20 0.25 0.30为例
在计算C[ i ] [ j ]是需要从上往下按对角线计算,:
在这里插入图片描述

下面我们来计算C [ 1 ] [ 2 ] :
1<=i<=j<=2;
I<=k<=j;
在这里插入图片描述
在这里插入图片描述

三、解题思路图形化

下面介绍一种更为直观的三角形计算方法,其实本质没有发生改变,只是把数学公式图形化,方便填表。
求C[i][j]时,设以C[i][i-1],C[j+1][j],C[i][j]三个点形成的直角三角形的次斜边上的值累加和为S(斜),直角边上对应两点的和为S(直),显然S[直]有j-i+1个,则C[i][j]=S[斜]+min{S[直]}。更方便的理解为将最优树划分为左右子树,寻求其最小和值(构建最优二叉树),同时也能由k值确认最优的根节点值。
下面举个例子求C[1][3]的值,如图所示:
在这里插入图片描述
S(斜)=0.1+0.2+0.4=0.7,S(直)=min{S1(直),S2(直),S3(直)}=min{0+0.8,0.1+0.4,0.4+0}=0.4,则C[1][3]=0.7+0.4=1.1。同理,求C[[2]][[4]],如下图所示,C[[2]][[4]]=1.4:
在这里插入图片描述

四、思考:为什么输入概率相同(无序),输出结果不一样

总结一句话就是:输入数据是有序的,12345分别代表其根值大小,即第一个输入概率对应的值是1,第二个是2,查找的时候并不是根据概率查找,而是根据根值来构造最优二叉查找树。下面详细分析:

  1. 输出树不一致
    遇到的问题是输入同样的数据,就是各个概率顺序不一样,为什么得到的最优二叉查找树不一样。
    例如:每个节点对应的查找概率值:0.10 0.15 0.20 0.25 0.30

在这里插入图片描述

那么最优二叉树画出来就是:
在这里插入图片描述
但是当输入概率是:0.25 0.10 0.15 0.20 0.30
在这里插入图片描述
那么最优二叉树画出来就是:
在这里插入图片描述

  1. 从这两个图可以看出来,它们的最优平均查找次数不一样,连树画出来也不一样。 刚开始我是考虑怎样最优二叉树的查找,如果按照我刚开始的理解就是按照概率查找,其实是错的。那么按照概率查找,第二种二叉树的画法就出错了,因为从根节点0.15 看,它的左右子节点都比它大,下一步就不知道该查找那边的节点了。

  2. 那么我想了一种解决方法,就是像图一的二叉树一样,将二叉树的概率进行升序排序,那么它的查找树就会符合最优二叉树的查找方法,它的左边节点全部小于根节点,右边的左根节点都大于根节点。如果一定要排序的话那么就说明并不具有通用性,这个动态规划的查找最优二叉树的算法就出错了。

  3. 那么我就开始考虑是否是我错了,算法算出的每一种结果都是对的,然后我就将所有概率换成了根节点。经过多次的结果验证,发现每个图都符合最优二叉树查找的规律,经过仔细的思考我终于理解了:因为查找的键值是根据根节点,而不是根据概率,而且每个键值都是升序。就像跟几点是1,2 ,3 ,4 ,5,那么它的键值大小排序就是1<2<3<4<5,这也就解释了求概率矩阵的时候为什么c[ I ] [ j ]是i到j时连续的,而不是跳变的。所以这个问题隐藏了一个条件,就是输入的概率对应的键值是升序,那么第二个最优二叉树应该是:
    在这里插入图片描述

流程图

详细流程图
在这里插入图片描述

实例

输入输入概率:0.25 0.10 0.15 0.20 0.30
在这里插入图片描述
对应最优二叉查找树:
在这里插入图片描述

示例代码

C++写的代码:

//最优二叉查找树
#include<bits/stdc++.h>

using namespace std;

//const int maxval = 9999;

double BST(int n,double p[],double c[][100],int r[][100])
{
    for(int i=1;i<=n;i++)
    {
        c[i][i-1]=0;   //Ci矩阵初始化
        c[i][i]=p[i];
        r[i][i]=i;      //R根矩阵初始化

    }

    c[n+1][n]=0;
    for(int d=1;d<n;d++)        //安对角线计算,从第二条对角线开始
    {
        for(int i=1;i<=n-d;i++)     //行的取值范围
        {

            int j=i+d;          //j求出在对角线上的i对应的
            double minval=9999;
            int mink=i;         //最小值对应根点
            double sum=0;
            for(int k=i;k<=j;k++)
            {
                sum=sum+p[k];
                if(c[i][k-1]+c[k+1][j]<minval)   //三角形比较法,选最小值
                {
                    minval=c[i][k-1]+c[k+1][j];
                    mink=k;
                }
            }
            c[i][j]=minval+sum;//得到了最小值
            r[i][j]=mink;//记录取得最小值时的根节点
        }
    }

    return c[1][n];
}






int main()
{
    while(1)
    {
        cout<<"input operand :  1  enter   ,2   exit system"<<endl;
        int ch;
        cin>>ch;
        if(ch==2)
            return 0;
        else if(ch==1)
        {
            system("cls");
            int n;
            cout << "input the point number" << endl;
            cin>>n;  //节点个数
            double p[n];  // 概率数组
            memset(p,0,sizeof(p));
            // 将概率数组排序,保证正确
            cout<<"input each point probability" <<endl;
            for(int i=1;i<=n;i++)
            {
                cin>>p[i];
            }
            double c[n+2][100];  //动态ci矩阵
            int r[n+2][100];    //根矩阵
            memset(r,0,sizeof(r));
            memset(c,0,sizeof(c));
            double s=BST(n,p,c,r);

            cout << "the minimum compare times is  " << s<<endl;
            cout << "the minimum probability  matrix is" << endl;
            for(int i=1;i<=n+1;i++)
            {
                for(int j=0;j<=n;j++)
                {
                    cout<<std::setw(3);
                    cout << c[i][j] << "    ";
                }
                cout << endl;
             }

            cout << "the point matrix " << endl;
            for(int i=1;i<=n+1;i++)
            {
                for(int j=0;j<=n;j++)
                {
                    cout << r[i][j] << "   ";
                }
                cout << endl;
             }
        }

    }
}

  • 14
    点赞
  • 146
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
最优二叉搜索树是一种特殊的二叉搜索树,其中查找每个关键字的期望代价最小。这里我们使用动态规划算法来解决这个问题。 假设我们有关键字 $k_1, k_2, \cdots, k_n$,它们被存储在一个有序数组中,且对于每个关键字 $k_i$,它的概率为 $p_i$。我们也有一些虚拟关键字 $d_0, d_1, \cdots, d_n$,其中 $d_0$ 代表小于 $k_1$ 的所有值,$d_n$ 代表大于 $k_n$ 的所有值。我们定义 $w_{i,j}$ 表示 $k_i, k_{i+1}, \cdots, k_j$ 以及 $d_{i-1}, d_i, \cdots, d_j$ 的概率和。我们还定义 $c_{i,j}$ 表示最优二叉搜索树中 $k_i, k_{i+1}, \cdots, k_j$ 的期望代价。 为了找到最优二叉搜索树,我们可以使用以下递推式: $$ c_{i,j} = \begin{cases} 0, & \text{if } i=j+1 \\ \min\limits_{i\leq k\leq j} \{c_{i,k-1} + c_{k+1,j}\} + w_{i,j}, & \text{if } i\leq j \end{cases} $$ 递推式的含义是,我们将可能的根节点 $k$ 从 $i$ 到 $j$ 进行枚举,对于每个 $k$,计算左子树 $i$ 到 $k-1$ 的期望代价 $c_{i,k-1}$ 和右子树 $k+1$ 到 $j$ 的期望代价 $c_{k+1,j}$,然后加上 $k$ 作为根节点的期望代价 $w_{i,j}$。最后取所有可能的 $k$ 中,期望代价最小的那个值作为 $c_{i,j}$ 的值。 最终,我们可以通过计算 $c_{1,n}$ 来获得最优二叉搜索树的期望代价。 下面是一个使用动态规划算法计算最优二叉搜索树的示例程序,其中输入的关键字概率存储在数组 $p$ 中,虚拟关键字的概率为 $q$。程序输出最优二叉搜索树的期望代价。 ```c #include <stdio.h> double opt_bst(int n, double p[], double q[]) { double w[n+1][n+1]; double c[n+1][n+1]; for (int i = 0; i <= n; i++) { w[i][i] = q[i]; c[i][i] = 0; } for (int len = 1; len <= n; len++) { for (int i = 0; i <= n-len; i++) { int j = i + len; w[i][j] = w[i][j-1] + p[j] + q[j]; c[i][j] = __DBL_MAX__; for (int k = i; k <= j; k++) { double cost = c[i][k-1] + c[k+1][j] + w[i][j]; if (cost < c[i][j]) { c[i][j] = cost; } } } } return c[0][n]; } int main() { double p[] = {0.15, 0.10, 0.05, 0.10, 0.20}; double q[] = {0.05, 0.10, 0.05, 0.05, 0.05, 0.10}; int n = sizeof(p) / sizeof(p[0]); double cost = opt_bst(n-1, p, q); printf("The expected cost of the optimal binary search tree is %f\n", cost); return 0; } ```
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值