最优二叉排序树

OBST

最优二叉排序树(OBST):在BST的前提下,每个节点有被搜索的概率,然后要找到一个BST,使得带权搜索路径最小

K [ 1 ⋯ n ] = < k 1 , ⋯   , k n > ( k 1 < k 2 < ⋯ < k n ) K\left[1\cdots n \right]=<k_1,\cdots,k_n>(k_1<k_2<\cdots<k_n) K[1n]=<k1,,kn>(k1<k2<<kn)
k i k_i ki是BST上的节点(key)

D [ 0 ⋯ n ] = < d 0 , ⋯   , d n > D\left[0\cdots n\right]=<d_0,\cdots,d_n> D[0n]=<d0,,dn>
其中
k 1 < d 0 k_1<d_0 k1<d0
d n > k n d_n>k_n dn>kn
k i < d i < k i + 1 ( i = 1 , 2 , ⋯   , n − 1 ) k_i<d_i<k_{i+1}(i=1,2,\cdots,n-1) ki<di<ki+1(i=1,2,,n1)
d i d_i di表示的是搜索失败的节点(虚节点)

p i p_i pi表示 k i k_i ki被搜索到的概率
q i q_i qi表示 d i d_i di被搜索到的概率

∑ i = 1 n p i + ∑ i = 0 n q i = 1 \sum_{i=1}^{n}p_i+\sum_{i=0}^{n}q_i=1 i=1npi+i=0nqi=1
带权路径
E T = ∑ i = 1 n d e p t h T ( k i ) p i + ∑ i = 0 n d e p t h T ( d i ) q i E_{T}=\sum_{i=1}^{n}depth_{T}(k_i)p_i+\sum_{i=0}^{n}depth_{T}(d_i)q_i ET=i=1ndepthT(ki)pi+i=0ndepthT(di)qi
其中根节点的深度为1
我们的目标就是让这个带权路径最小

e [ i , j ] e\left[i,j\right] e[i,j]表示包含 K [ i , j ] K\left[i,j\right] K[i,j]的OBST的带权路径,
w [ i , j ] = ∑ l = i j p l + ∑ l = i − 1 j q l w\left[i,j\right]=\sum_{l=i}^{j}p_l+\sum_{l=i-1}^{j}q_l w[i,j]=l=ijpl+l=i1jql,
T i j T_{i}^{j} Tij表示包含 k i k_i ki k j k_j kj的树
r [ i , j ] r\left[i,j\right] r[i,j]表示 T i j T_i^j Tij的OBST根节点

(1) e [ i , i − 1 ] = q i − 1 e\left[i,i-1\right]=q_{i-1} e[i,i1]=qi1
(2)当 i ≤ j i\le j ij
e [ i , j ] = min ⁡ i ≤ r ≤ j ( ∑ l = i r − 1 ( d e p t h T i r − 1 ( k l ) + 1 ) p l + ∑ l = r + 1 j ( d e p t h T r + 1 j ( k l ) + 1 ) p l + ∑ l = i − 1 r − 1 ( d e p t h T i r − 1 ( d l ) + 1 ) q l + ∑ l = r j ( d e p t h T r + 1 j ( d l ) + 1 ) q l + p r ) = min ⁡ i ≤ r ≤ j ( ∑ l = i r − 1 d e p t h T i r − 1 ( k l ) p l + ∑ l = i − 1 r − 1 d e p t h T i r − 1 ( d l ) q l + ∑ l = r + 1 j d e p t h T r + 1 j ( k l ) p l + ∑ l = r j d e p t h T r + 1 j ( d l ) q l + ∑ l = i j p l + ∑ l = i − 1 j q l ) = min ⁡ i ≤ r ≤ j ( e [ i , r − 1 ] + e [ r + 1 , j ] + w [ i , j ] ) \begin{aligned} e\left[i,j\right]&=\min\limits_{i\le r \le j}(\sum_{l=i}^{r-1}(depth_{T_{i}^{r-1}}(k_l)+1)p_l +\sum_{l=r+1}^{j}(depth_{T_{r+1}^{j}}(k_l)+1)p_l+\\ &\quad\quad \sum_{l=i-1}^{r-1}(depth_{T_{i}^{r-1}}(d_l)+1)q_l +\sum_{l=r}^{j}(depth_{T_{r+1}^{j}}(d_l)+1)q_l+\\ &\quad\quad p_r)\\ &=\min\limits_{i\le r \le j}(\sum_{l=i}^{r-1}depth_{T_{i}^{r-1}}(k_l)p_l +\sum_{l=i-1}^{r-1}depth_{T_{i}^{r-1}}(d_l)q_l+\\ &\quad\quad\sum_{l=r+1}^{j}depth_{T_{r+1}^{j}}(k_l)p_l +\sum_{l=r}^{j}depth_{T_{r+1}^{j}}(d_l)q_l+\\ &\quad\quad \sum_{l=i}^{j}p_l+\sum_{l=i-1}^{j}q_l)\\ &=\min\limits_{i\le r \le j}(e\left[i,r-1\right]+e\left[r+1,j\right]+w\left[i,j\right]) \end{aligned} e[i,j]=irjmin(l=ir1(depthTir1(kl)+1)pl+l=r+1j(depthTr+1j(kl)+1)pl+l=i1r1(depthTir1(dl)+1)ql+l=rj(depthTr+1j(dl)+1)ql+pr)=irjmin(l=ir1depthTir1(kl)pl+l=i1r1depthTir1(dl)ql+l=r+1jdepthTr+1j(kl)pl+l=rjdepthTr+1j(dl)ql+l=ijpl+l=i1jql)=irjmin(e[i,r1]+e[r+1,j]+w[i,j])
所以
e [ i , j ] = { q i − 1 , j = i − 1 min ⁡ i ≤ r ≤ j ( e [ i , r − 1 ] + e [ r + 1 , j ] ) + w [ i , j ] , i ≤ j e\left[i,j\right]=\begin{cases} q_{i-1},&j=i-1\\ \min\limits_{i\le r \le j}(e\left[i,r-1\right]+e\left[r+1,j\right])+w\left[i,j\right],&i\le j \end{cases} e[i,j]={qi1,irjmin(e[i,r1]+e[r+1,j])+w[i,j],j=i1ij
可以看出,如果根节点是固定的,那么左右子树都是OBST,也就是是想找到 i i i j j j的OBST,可以枚举根节点,算左右两边的OBST
于是dp方程就有了

#include <iostream>
#include <cstring>
#include <cfloat>
#include <vector>
using namespace std;
const int N = 1000;
//假设有n个key,那数组要e[n+2][n+2],w[n+2][n+2],r[n+2][n+2]
double e[N][N];
double w[N][N];
int r[N][N];

/**
 * @description: 最优二叉排序树
 * @param {double} *p 找到的key权重
 * @param {double} *q 找不到的key的权重
 * @param {int} n 找到的key的个数
 */
void build_obst(double *p, double *q, int n)
{
    // memset(e,0,sizeof(e));
    // memset(w,0,sizeof(w));
    for (int i = 1; i <= n + 1; ++i)
    {
        e[i][i - 1] = q[i - 1];
        w[i][i - 1] = q[i - 1];
    }
    for (int l = 1; l <= n; ++l)
    {
        for (int i = 1; i <= n - l + 1; ++i)
        {
            int j = i + l - 1;
            double ans = DBL_MAX;
            w[i][j] = w[i][j - 1] + p[j] + q[j];
            for(int cur_root=i;cur_root<=j;++cur_root){
                double t=e[i][cur_root-1]+e[cur_root+1][j];
                if(t<ans){
                    ans=t;
                    r[i][j]=cur_root;
                }
            }
            e[i][j]=ans+w[i][j];
        }
    }
}

int main()
{
    /**
     7
          0.04 0.06 0.08 0.02 0.10 0.12 0.14
     0.06 0.06 0.06 0.06 0.05 0.05 0.05 0.05
     */
    double p[N];
    double q[N];
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
    {
        scanf("%lf", &p[i]);
    }
    for (int i = 0; i <= n; ++i)
    {
        scanf("%lf", &q[i]);
    }
    build_obst(p, q, n);
    for (int i = 0; i <= n + 1; ++i)
    {
        for (int j = 0; j <= n + 1; ++j)
        {
            printf("%lf ", e[i][j]);
        }
        printf("\n");
    }
    printf("\n");
    for (int i = 0; i <= n + 1; ++i)
    {
        for (int j = 0; j <= n + 1; ++j)
        {
            printf("%lf ", w[i][j]);
        }
        printf("\n");
    }
    printf("\n");
    for (int i = 0; i <= n + 1; ++i)
    {
        for (int j = 0; j <= n + 1; ++j)
        {
            printf("%d ", r[i][j]);
        }
        printf("\n");
    }
    return 0;
}

时间复杂度
∑ l = 1 n ∑ i = 1 n − l + 1 ∑ r = i i + l − 1 1 = ∑ l = 1 n ∑ i = 1 n − l + 1 l = ∑ l = 1 n ( n − l + 1 ) l = ∑ l = 1 n ( ( n + 1 ) l − l 2 ) = ( n + 1 ) ( 1 + n ) n 2 − n ( n + 1 ) ( 2 n + 1 ) 6 = n ( n + 1 ) ( n + 2 ) 6 \begin{aligned} &\quad \sum_{l=1}^{n}\sum_{i=1}^{n-l+1}\sum_{r=i}^{i+l-1}1\\ &=\sum_{l=1}^{n}\sum_{i=1}^{n-l+1}l\\ &=\sum_{l=1}^{n}(n-l+1)l\\ &=\sum_{l=1}^{n}((n+1)l-l^2)\\ &=\frac{(n+1)(1+n)n}{2}-\frac{n(n+1)(2n+1)}{6}\\ &=\frac{n(n+1)(n+2)}{6} \end{aligned} l=1ni=1nl+1r=ii+l11=l=1ni=1nl+1l=l=1n(nl+1)l=l=1n((n+1)ll2)=2(n+1)(1+n)n6n(n+1)(2n+1)=6n(n+1)(n+2)
所以是 Θ ( n 3 ) \Theta(n^3) Θ(n3)
空间复杂度 O ( n 2 ) O(n^2) O(n2)

优化

r [ i ] [ j − 1 ] ≤ r [ i ] [ j ] ≤ r [ i + 1 ] [ j ] r[i][j-1]\le r[i][j] \le r[i+1][j] r[i][j1]r[i][j]r[i+1][j]
证明:
再说

#include <iostream>
#include <cstring>
#include <cfloat>
#include <vector>
using namespace std;
const int N = 1000;
//假设有n个key,那数组要e[n+2][n+2],w[n+2][n+2],r[n+2][n+2]
double e[N][N];
double w[N][N];
int r[N][N];

/**
 * @description: 最优二叉排序树
 * @param {double} *p 找到的key权重
 * @param {double} *q 找不到的key的权重
 * @param {int} n 找到的key的个数
 */
void build_obst(double *p, double *q, int n)
{
    // memset(e,0,sizeof(e));
    // memset(w,0,sizeof(w));
    for (int i = 1; i <= n + 1; ++i)
    {
        e[i][i - 1] = q[i - 1];
        w[i][i - 1] = q[i - 1];
    }
    for (int l = 1; l <= n; ++l)
    {
        for (int i = 1; i <= n - l + 1; ++i)
        {
            int j = i + l - 1;
            double ans = DBL_MAX;
            w[i][j] = w[i][j - 1] + p[j] + q[j];
            // for(int cur_root=i;cur_root<=j;++cur_root){
            //     double t=e[i][cur_root-1]+e[cur_root+1][j];
            //     if(t<ans){
            //         ans=t;
            //         r[i][j]=cur_root;
            //     }
            // }
            // e[i][j]=ans+w[i][j];
            if (i < j)
            {
                for (int cur_root = r[i][j - 1]; cur_root <= r[i + 1][j]; ++cur_root)
                {
                    double t = e[i][cur_root - 1] + e[cur_root + 1][j];
                    if (t < ans)
                    {
                        ans = t;
                        r[i][j] = cur_root;
                    }
                }
                e[i][j] = ans + w[i][j];
            }
            else
            { //i==j
                e[i][j] = e[i][j - 1] + e[j + 1][j] + w[i][j];
                r[i][j] = i;
            }
        }
    }
}

int main()
{
    /**
     7
          0.04 0.06 0.08 0.02 0.10 0.12 0.14
     0.06 0.06 0.06 0.06 0.05 0.05 0.05 0.05
     */
    double p[N];
    double q[N];
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
    {
        scanf("%lf", &p[i]);
    }
    for (int i = 0; i <= n; ++i)
    {
        scanf("%lf", &q[i]);
    }
    build_obst(p, q, n);
    for (int i = 0; i <= n + 1; ++i)
    {
        for (int j = 0; j <= n + 1; ++j)
        {
            printf("%lf ", e[i][j]);
        }
        printf("\n");
    }
    printf("\n");
    for (int i = 0; i <= n + 1; ++i)
    {
        for (int j = 0; j <= n + 1; ++j)
        {
            printf("%lf ", w[i][j]);
        }
        printf("\n");
    }
    printf("\n");
    for (int i = 0; i <= n + 1; ++i)
    {
        for (int j = 0; j <= n + 1; ++j)
        {
            printf("%d ", r[i][j]);
        }
        printf("\n");
    }
    return 0;
}

时间复杂度
T = ∑ l = 2 n ∑ i = 1 n − l + 1 ∑ k = r [ i ] [ j − 1 ] r [ i + 1 ] [ j ] 1 + ∑ l = 1 1 ∑ i = 1 n − l + 1 1 = n + ∑ l = 2 n ∑ i = 1 n − l + 1 ( r [ i + 1 ] [ j ] − r [ i ] [ j − 1 ] + 1 ) = n + ∑ l = 2 n ( n − l + 1 ) + ∑ l = 2 n ∑ i = 1 n − l + 1 ( r [ i + 1 ] [ j ] − r [ i ] [ j − 1 ] ) = n + ( n + 1 ) ( n − 1 ) − ( n − 1 ) ( 2 + n ) 2 + ∑ l = 2 n ∑ i = 1 n − l + 1 ( r [ i + 1 ] [ j ] − r [ i ] [ j − 1 ] ) = n ( n + 1 ) 2 + ∑ l = 2 n ∑ i = 1 n − l + 1 ( r [ i + 1 ] [ j ] − r [ i ] [ j − 1 ] ) \begin{aligned} T&=\sum_{l=2}^{n}\sum_{i=1}^{n-l+1}\sum_{k=r[i][j-1]}^{r[i+1][j]}1+\sum_{l=1}^{1}\sum_{i=1}^{n-l+1}1\\ &=n+\sum_{l=2}^{n}\sum_{i=1}^{n-l+1}(r[i+1][j]-r[i][j-1]+1)\\ &=n+\sum_{l=2}^{n}(n-l+1)+\sum_{l=2}^{n}\sum_{i=1}^{n-l+1}(r[i+1][j]-r[i][j-1])\\ &=n+(n+1)(n-1)-\frac{(n-1)(2+n)}{2}+\sum_{l=2}^{n}\sum_{i=1}^{n-l+1}(r[i+1][j]-r[i][j-1])\\ &=\frac{n(n+1)}{2}+\sum_{l=2}^{n}\sum_{i=1}^{n-l+1}(r[i+1][j]-r[i][j-1]) \end{aligned} T=l=2ni=1nl+1k=r[i][j1]r[i+1][j]1+l=11i=1nl+11=n+l=2ni=1nl+1(r[i+1][j]r[i][j1]+1)=n+l=2n(nl+1)+l=2ni=1nl+1(r[i+1][j]r[i][j1])=n+(n+1)(n1)2(n1)(2+n)+l=2ni=1nl+1(r[i+1][j]r[i][j1])=2n(n+1)+l=2ni=1nl+1(r[i+1][j]r[i][j1])
考察 ∑ i = 1 n − l + 1 ( r [ i + 1 ] [ j ] − r [ i ] [ j − 1 ] ) \sum_{i=1}^{n-l+1}(r[i+1][j]-r[i][j-1]) i=1nl+1(r[i+1][j]r[i][j1])的每一项
r [ 2 ] [ l ] − r [ 1 ] [ l − 1 ] + r [ 3 ] [ l + 1 ] − r [ 2 ] [ l ] + r [ 4 ] [ l + 2 ] − r [ 3 ] [ l + 1 ] + ⋮ r [ n − l + 2 ] [ n ] − r [ n − l + 1 ] [ n − 1 ] r[2][l]-r[1][l-1]+\\ r[3][l+1]-r[2][l]+\\ r[4][l+2]-r[3][l+1]+\\ \vdots\\ r[n-l+2][n]-r[n-l+1][n-1] r[2][l]r[1][l1]+r[3][l+1]r[2][l]+r[4][l+2]r[3][l+1]+r[nl+2][n]r[nl+1][n1]
所以 ∑ i = 1 n − l + 1 ( r [ i + 1 ] [ j ] − r [ i ] [ j − 1 ] ) = r [ n − l + 2 ] [ n ] − r [ 1 ] [ l − 1 ] \sum_{i=1}^{n-l+1}(r[i+1][j]-r[i][j-1])=r[n-l+2][n]-r[1][l-1] i=1nl+1(r[i+1][j]r[i][j1])=r[nl+2][n]r[1][l1]
所以
T = n ( n + 1 ) 2 + ∑ l = 2 n ( r [ n − l + 2 ] [ n ] − r [ 1 ] [ l − 1 ] ) T=\frac{n(n+1)}{2}+\sum_{l=2}^{n}(r[n-l+2][n]-r[1][l-1]) T=2n(n+1)+l=2n(r[nl+2][n]r[1][l1])
显然
T ≤ n ( n + 1 ) 2 + ∑ l = 2 n ( n − 1 ) = 3 n 2 − 3 n + 2 2 T\le \frac{n(n+1)}{2}+\sum_{l=2}^{n}(n-1)=\frac{3n^2-3n+2}{2} T2n(n+1)+l=2n(n1)=23n23n+2
根据 r [ i ] [ j − 1 ] ≤ r [ i ] [ j ] ≤ r [ i + 1 ] [ j ] r[i][j-1]\le r[i][j] \le r[i+1][j] r[i][j1]r[i][j]r[i+1][j]
r [ i + 1 ] [ j ] − r [ i ] [ j − 1 ] ≥ 0 r[i+1][j]-r[i][j-1] \ge 0 r[i+1][j]r[i][j1]0
T = n ( n + 1 ) 2 + ∑ l = 2 n ∑ i = 1 n − l + 1 ( r [ i + 1 ] [ j ] − r [ i ] [ j − 1 ] ) ≥ n ( n + 1 ) 2 + 0 T=\frac{n(n+1)}{2}+\sum_{l=2}^{n}\sum_{i=1}^{n-l+1}(r[i+1][j]-r[i][j-1])\ge \frac{n(n+1)}{2}+0 T=2n(n+1)+l=2ni=1nl+1(r[i+1][j]r[i][j1])2n(n+1)+0
所以时间复杂度 Θ ( n 2 ) \Theta(n^2) Θ(n2)
空间复杂度 O ( n 2 ) O(n^2) O(n2)

参考

https://zhuanlan.zhihu.com/p/152636524

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
最优二叉搜索树问题是一个经典的动态规划问题,旨在找到一棵二叉搜索树,使得在给定的概率分布下,查找任意元素的期望代价最小化。 解决这个问题的动态规划算法可以分为以下几个步骤: 1. 首先,我们需要将给定的元素按照升序排序,并计算出每个元素的概率。 2. 接下来,我们构建一个二维数组dp,其中dp[i][j]表示从第i个元素到第j个元素构成的子数组的最优二叉搜索树的期望代价。 3. 然后,我们可以通过填充dp数组的方式来逐步计算最优解。具体地,我们从长度为1的子数组开始,然后逐步增加子数组的长度,直到整个数组。对于每个子数组,我们通过枚举根节点的位置来计算出最小的期望代价。 4. 在计算dp[i][j]时,我们可以考虑将每个元素k作为根节点,然后将子数组划分为左子树和右子树。左子树由元素i到k-1构成,右子树由元素k+1到j构成。然后,我们可以通过dp[i][k-1]和dp[k+1][j]来计算左子树和右子树的期望代价,并将它们与当前根节点的代价相加。 5. 最后,我们可以通过遍历不同的根节点位置,选择最小的代价作为dp[i][j]的值。 总结起来,最优二叉搜索树问题的动态规划算法主要通过填充二维数组dp来计算最优解。具体的步骤包括排序元素、初始化dp数组、计算dp数组的值,并通过选择最小代价来确定最优解。通过这种方法,我们可以找到在给定概率分布下查找元素的最优二叉搜索树。 参考文献: <<引用>> <<引用>> <<引用>>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Nightmare004

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值