第十五章 动态规划(最优二叉搜索树)

第15章动态规划(最优二叉搜索树)


说在前面的话:

为什么单独拿出来发?
1.由于排版篇幅问题,放一起太长没人愿意看吧。
2.单纯想增加投稿数量。(毕竟今天都没怎么发过文)

15.5 最优二叉搜索树

最优二叉搜索树的问题的形式可以定义如下:给定一个n个不同关键字的已排序的序列K=<K1,K2,…,Kn>(因此k1<k2…<kn),我们希望用这些关键字构造一棵二叉树搜索树。对每个关键字ki,都有一个概率pi表示其搜索频率。**有些搜索值可能不在K中,因此我们还要有n+1个“伪关键字”d0,d1,d2,…,dn表示不在K中的值。**对每个伪关键字di,也都有一个概率qi表示对应的搜索频率。
Alt

搜索概率如下表:

i012345
pi/0.150.100.050.100.20
qi0.050.100.050.050.050.10

如图显示了对于n=5个关键字的集合构造的两颗二叉搜索树。每个关键字ki是一个内部结点,而每个伪关键字di是一个叶结点。每次搜索要么成功(找到关键字ki)要么失败(找到伪关键字di),因此有如下公式:
∑ i = 1 n \displaystyle\sum_{i=1}^{n} i=1n pi + ∑ i = 0 n \displaystyle\sum_{i=0}^{n} i=0n qi = 1

由于我们知道每个关键字和伪关键字的搜索概率,因而可以确定在一棵给定的二叉搜索树T中进行一次搜索的期望代价。假定一次搜索的代价等于访问的结点数,即此次搜索找到的结点在T中的深度再加1.那么在T中进行亿次搜索的期望代价为:
E[T中搜索代价] = ∑ i = 1 n \displaystyle\sum_{i=1}^{n} i=1n (depthT(ki) + 1) * pi + ∑ i = 0 n \displaystyle\sum_{i=0}^{n} i=0n(depthT(di) + 1) * qi
= 1 + ∑ i = 1 n \displaystyle\sum_{i=1}^{n} i=1n (depthT(ki) ) * pi + ∑ i = 0 n \displaystyle\sum_{i=0}^{n} i=0n(depthT(di) ) * qi
其中depthT表示一个结点在树T中的深度。

对于一个给定的概率集合,我们希望构造一课期望搜索代价最小的二叉搜索树,我们成为最优二叉搜索树上图右边所示的二叉搜索树就是给定概率集合的最优二叉搜索树,其期望代价为2.75。

代码如下:

   public class Program
    {
        public static void Main(string[] args)
        {
            var handle = new OPTIMALBST_Handle();
            handle.optimal_bst();
        }
    }

    public class OPTIMALBST_Handle
    {
        public decimal[] p { get; set; }

        public decimal[] q { get; set; }

        public int amount { get; set; }

        public decimal[,] w { get; set; }

        public decimal[,] e { get; set; }

        public int[,] root { get; set; }

        public OPTIMALBST_Handle()
        {
            Console.WriteLine("请输入结点数:\r");
            amount = Convert.ToInt32(Console.ReadLine());
            p = new decimal[amount + 1];
            q = new decimal[amount + 1];
            w = new decimal[amount + 2, amount + 1];
            e = new decimal[amount + 2, amount + 1];
            root = new int[amount + 1, amount + 1];
            for (var i = 1; i <= amount; i++)
            {
                Console.WriteLine("关键字k" + i + "的概率为:\r");
                p[i] = Convert.ToDecimal(Console.ReadLine());
            }
            for (var i = 0; i <= amount; i++)
            {
                Console.WriteLine("伪关键字d" + i + "的概率为:\r");
                q[i] = Convert.ToDecimal(Console.ReadLine());
            }
        }

        public void optimal_bst()
        {
            for (var i = 1; i <= amount + 1; i++)
            {
                w[i, i - 1] = q[i - 1];
                e[i, i - 1] = q[i - 1];
            }
            for (var l = 1; l <= amount; l++)
            {
                for (var i = 1; i <= amount - l + 1; i++)
                {
                    var j = i + l - 1;
                    w[i, j] = w[i, j - 1] + p[j] + q[j];
                    e[i, j] = decimal.MaxValue;
                    for (var r = i; r <= j; r++)
                    {
                        var temp = e[i, r - 1] + w[i, j] + e[r + 1, j];
                        if (temp < e[i, j])
                        {
                            e[i, j] = temp;
                            root[i, j] = r;
                        }
                    }
                }
            }
        }
    }

15.5 练习

15.5-1

设计伪代码CONSTRUCT-OPTIMAL-BST(root),输入为表root,输出是最优二叉搜索树的结构。例如,对图上图中的root表,应输出
k2为根
k1为k2的左孩子
d0为k1的左孩子
d1为k1的右孩子
k5为k2的右孩子
k4为k5的左孩子
k3为k4的左孩子
d2为k3的左孩子
d3为k3的右孩子
d4为k4的右孩子
d5为k5的右孩子
与图(b)中的最优二叉搜索树对应

直接上代码和输出结果,用的是不递归前序遍历二叉树的思路。
结果和上面是一样的,这里就不贴输出内容了,有兴趣可以自己复制代码试一下。当然这里用递归前序遍历二叉树肯定简单明了多了,用不递归去写纯粹是一时兴起。

        public void Construct_optimal_bst()
        {
            var s_left = new Stack();
            var s_right = new Stack();
            var l = 1;
            var r = amount;
            var parent = -1;
            while (s_left.Count != 0
                || r >= l)
            {
                while (r >= l)
                {
                    if (parent == -1)
                    {
                        Console.WriteLine("k" + root[l, r] + "为根");
                    }
                    else if (root[l, r] >= parent)
                    {
                        Console.WriteLine("k" + root[l, r] + "为k" + parent + "的右孩子");
                    }
                    else
                    {
                        Console.WriteLine("k" + root[l, r] + "为k" + parent + "的左孩子");
                    }
                    s_left.Push(l);
                    s_right.Push(r);
                    parent = root[l, r];
                    r = parent - 1;
                    if (r == l - 1)
                    {
                        Console.WriteLine("d" + r + "为k" + parent + "的左孩子");
                    }
                }
                if (s_left.Count != 0 && s_right.Count != 0)
                {
                    l = (int)s_left.Pop();
                    r = (int)s_right.Pop();
                    parent = root[l, r];
                    l = parent + 1;
                    if (r == l - 1)
                    {
                        Console.WriteLine("d" + r + "为k" + parent + "的右孩子");
                    }
                }
            }
        }

15.5-2

若7个关键字的概率如下图所示,求其最优二叉搜索树的结构和代价。

i01234567
pi/0.040.060.080.020.100.120.14
qi0.060.060.060.060.050.050.050.05

输入题目里面的元素,直接运行代码就能出结果了。
在这里插入图片描述

15.5-3

假设OPTIMAL-BST不维护表w[i,j],而是在第9行利用公式直接计算w(i,j),然后在第11行使用此值。如此改动会对渐近时间复杂性有何影响?
公式为w(i,j) = ∑ i = 1 n \displaystyle\sum_{i=1}^{n} i=1n pi + ∑ i = 0 n \displaystyle\sum_{i=0}^{n} i=0n qi
两个总和项都要花费O(n)的时间,因此时间复杂度上升到O(n^3)。

15.5-4

Knuth已经证明,对所有1<=i<=j<=n,存在最优二叉搜索树,其根满足root[i , j-1] <= root[i , j] <= root[i + 1, j]。利用这一特性修改算法OPTIMAL-BST,使得运行时间减少为Θ(n^2)。

非常强的证明,直接简化了我们的递归公式。
后续会直接贴上修改后的代码。

  • 5
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值