首页 > 编程语言 >最优二叉搜索树问题(Java)

最优二叉搜索树问题(Java)

时间:2023-04-15 10:39:39浏览次数:40  
标签:结点 Java 二叉 关键字 搜索 最优 TODO

最优二叉搜索树问题(Java)


1、前置介绍

S={x1, x2, … , xn} 是有序集, 且x1 < x2 < … < xn, 表示有序集S的二叉搜索树利用二叉树的结点存储有序集中的元素。

它具有下述性质:存储于每个结点中的元素xi大于其左子树中任一结点所存储的元素,小于其右子树中任一结点所存储的元素。 二叉搜索树的叶结点是形如(xi,xi+1)的开区间。在表示S的二叉搜索树中搜索元素x, 返回的结果有以下两种情形:

  • 在二叉搜索树的内结点中找到 x=xi;
  • 在二叉搜索树的叶结点中确定x属于 (xi, xi+1

最优二叉搜索树的形式化定义如下:给定一个n个不同关键字的已排序的序列K={k1, k2, ..., kn}(因此k1 < k2 < ... < kn),通过这些关键字来构建最优二叉搜索树。对于每个关键字ki作为「实节点」,都有一个概率 pi 与之对应(n个),表示其搜素频率「概率p下标从1开始对于每个关键字」。

但是有些要搜索的值可能不在K序列中, 因此我们还需要使用n+1个「伪关键字」d0, d1, d2, … , dn. 表示不在序列K中的值。d0表示所有小于k1如的值,dn表示所有大于kn的值,对i=l, 2, … , n—1, 伪关键字di 表示所有在ki和ki+1之间的值。对每个伪关键字di, 也都有一个概率 qi 表示对应的搜索频率「概率q下标从0开始对于每个伪关键字」。

下图中,显示了对一个n=5个关键字的集合构造的两棵二叉搜索树。每个关键字ki是一个内部结点, 而每个伪关键字di是一个叶结点。每次搜索要么成功(找到某个关键字ki)要么失败(找到某个伪关键字di),因此有以下公式:

最优二叉搜索树问题(Java)_Java

对一个n=5的关键字集合及如下的搜索概率,构造的两棵二叉搜索树:

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.105

0.10

搜索代价如下公式②:

最优二叉搜索树问题(Java)_二叉搜索树_02

2、算法设计思路

2.1 最优二叉搜索树的结构

最优二叉搜索树问题(Java)_二叉搜索树_03

2.2 一个递归算法

求解包含关键字ki, …,kj的最优二叉搜索树, 其中 i <= 1j <=n 且 (当 j = i - 1 时, 子树不包含实际关键字,只包 含伪关键字di-1)。定义e[i,j] 为在包含关键字ki, …,kj的最优二叉搜索树中进行一次搜索的期望代价。最终,我们希望计算出e[1, n]

以下分为两种情况;

  • j=i-1时最简单,因为子树只包含伪关键字di-1,此时的期望搜索代价为e[i, i—1]=qi-1
  • j>=i 时,我们需要从从ki, … ,kj中选择一个根结点kr, 然后构造一棵包含关键字ki, …, kr-1的最优二叉搜索树作为其左子树,以及一棵包含关键字kr+1, …, kj的二叉搜索树作为其右子树。

当一棵子树成为一个结点的子树时,由于每个结点的深度都增加了1, 根据公式②, 这棵子树的期望搜索代价的增加值应为所有概率之和。对于包含关键字ki, … , kj的子树, 所有概率之和为以下公式③:

因此,若ki, … , kj 的最优二叉搜索树的根结点,我们有如下公式④:

因此e[i,j]可重写为:如下公式⑤

递归公式⑤假定我们知道哪个结点k应该作为根结点。如果选取期望搜索代价最低者 作为根结点,可得最终递归公式⑥:

e[i,j]的值给出了最优二叉搜索树的期望搜索代价。

最优二叉搜索树问题(Java)_算法_04

2.3 计算最优二叉搜索树的期望搜索代价

最优二叉搜索树问题(Java)_算法_05

伪代码如下:

最优二叉搜索树问题(Java)_数据结构_06

根据前文的描述,以及与算法MATRIX-CHAIN-ORDER的相似性,很容易理解此算法。第2-4行的for循环初始化e[i, i —1]和w[i, i —1]的值。第5-14行的for循环利用 递归式⑥和递归式(15. 15)来对所有计算e[i, j]和w [i, j]。

在第一个循环步中,l= l, 循环对所有i = 1, 2, …, n计算e[i, j]和w[i, i]。第二个循环步中, l = 2, 对所有i = 1, 2, …, n — 1 计算e[i, i+l]和w[i, i+l], 依此类推。第10-14行的内层for循环,逐个尝试下标r, 确定哪个关键字k, 作为根结点可以得到包含关键字ki, …,kj的最优二叉搜索树。这个for循环在找到更好的关键字作为根结点时,会将其下标r保存在root[i, j]中。

下图给出了OPTIMAL-EST输入图1中的关键字分布后计算出的表e[i, j]、w[i, j]和 写root[i, j]。本图中的表进行了旋转,对角线旋转到了水平方向。OPTIMAL-EST按自底向上的顺序逐行计算,在每行中由左至右计算每个表项。

最优二叉搜索树问题(Java)_数据结构与算法_07

3、代码实现

动态规划解决最优二叉搜索树

/**
 * TODO 实现最优二叉树算法
 */
public class t4 {

    public static void main(String[] args) {
        // TODO p[1, 5]是 5个实节点的概率 p[0]不存在,对应每一个关键字的搜索频率
        double[] p = {0, 0.15, 0.1, 0.05, 0.1, 0.2};
        // TODO q[0, 5]是 6个伪节点的概率
        double[] q = {0.05, 0.1, 0.05, 0.05, 0.05, 0.1};
        // TODO 一共5个关键字(可以搜索得到的,即实节点)
        int n = p.length - 1;

        // TODO root[i][j]记录的是最终得出的[i,j]这个子段里的根节点
        int[][] root = optimalBinarySearchTree(p, q, n);
        // TODO root的 length 是 n+2, i 和 j 只需要循环到 n
        int temp = root.length - 1;
        // TODO 输出一下这个root矩阵,直接根据这个矩阵也可以画出来最优二叉搜索树
        //  root矩阵将对角线旋转到水平方向更直观地看到最优二叉搜索树
        for (int i = 1; i < temp; i++) {
            for (int j = 1; j < temp; j++) {
                System.out.print(root[i][j] + "-");
            }
            System.out.println();
        }
    }

    private static int[][] optimalBinarySearchTree(double[] p, double[] q, int n) {
        // TODO e[i][j]表示 i 到 j 这段的代价
        //  e[1][0]为左边只包含伪节点d0的代价...e[n+1][n]为左边只包含伪节点dn的代价
        double[][] e = new double[n + 2][n + 2];
        // TODO i 到 j 这一段的总概率,在加一层根节点时需要用到
        //  为避免每次计算e[i, j]都重新计算w(i,j)的值,直接将这些值存放在w数组中
        double[][] w = new double[n + 2][n + 2];
        // TODO root[i][j]记录的是最终得出的[i,j]这个子段里的根节点
        int[][] root = new int[n + 2][n + 2];

        // TODO 初始化
        for(int i = 1; i < n + 1; i++) {
            e[i][i - 1] = q[i - 1];
            w[i][i - 1] = q[i - 1];     // 1 <= i <= n + 1的情况
        }
        // TODO :l是 [i, j]区间的长度,相当于一个固定l长度的小节从最左边循环到最右边。
        //  然后再把l逐渐加大,重复从左到右。
        //  这样就使得小长度的都先计算过,为大长度的片段分解为左右两个字片段计算时提供了已经计算好的值,这也是动态规划的精髓之一。
        //  这样做是为了从小到大积累动态规划的能量
        for (int l = 1; l <= n; l++) {
            for (int i = 1; i <= n - l + 1; i++) {
                int j = i + l - 1;
                // TODO 初始化为最大值,最后才能找出真正最小的,从而找到最优解
                e[i][j] = Double.MAX_VALUE;
                // TODO j >= i的情况
                w[i][j] = w[i][j-1] + p[j] + q[j];
                // TODO 从 i 到 j 找到最优的根节点(选择以 r 为根节点)
                for(int r = i; r <= j; r++) {
                    // TODO 加了一层,相当于下面的左子树右子树都加了一布,其实就是最终比没加一层根节点比多了一个w[i][j]
                    double t = e[i][r - 1] + e[r + 1][j] + w[i][j];
                    // TODO 不断的使e[i][j]保持最小,同时记录下使代价最小的位置为最终的最优的根节点
                    if(t < e[i][j]) {
                        e[i][j] = t;
                        root[i][j] = r;
                    }
                }
            }

        }
        System.out.println("当前最小代价:" + e[1][n]);
        return root;
    }
}

4、复杂度分析

与MATRIX-CHAIN-ORDER 一样,OPTIMAL-BST的时间复杂度也是O(n3 )。由于它包含三重for循环,而每层循环的下标最多取n个值, 因此很容易得出其运行时间为O(n3)。

OPTIMAL-EST的循环下标的范围与MATRIX-CHAIN-ORDER不完全一样,但每个方向最多相 差1。因此,与MATRIX-CHAIN -ORDER 一样,OPTIMAL-EST的运行时间为O(n3) (从而得出运行时间为O(n3))。

5、参考资料

  • 算法分析与设计(第四版)
  • 算法导论第三版

全文结束!

标签:结点,Java,二叉,关键字,搜索,最优,TODO
From: https://blog.51cto.com/shadowlim/6192212

相关文章

  • Java面向对象编程基础
    类与对象类和对象的区别和联系类是抽象的,概念的,代表一类事物,比如人类,猫类..,即它是数据类型.对象是具体的,实际的,代表一个具体事物,即是实例.类是对象的模板,对象是类的一个个体,对应一个实例对象在内存中存在形式!字符串本质上是一个引用类型,按照jvm的规则会把字符串放在方法区的......
  • JavaSE07面向对象
    1.类和对象1.1类和对象的理解客观存在的事物皆为对象,所以我们也常常说万物皆对象。类类的理解类是对现实生活中一类具有共同属性和行为的事物的抽象类是对象的数据类型,类是具有相同属性和行为的一组对象的集合简单理解:类就是对现实事物的一种描述类的组成属性:指......
  • 环形链表_相交链表_多数元素(java语言)
    环形链表力扣141题问题:思路:创建hashset,把链表的每个节点放到集合中,在放入的过程中检查这个节点是否已经存在,存在则证明存在环。代码实现:publicclassSolution{publicbooleanhasCycle(ListNodehead){Set<ListNode>set=newHashSet<>();whi......
  • (之前的项目复习)我的Java项目实战--校园餐饮商户外卖系统06
    开发笔记六导入用户地址簿相关功能代码需求分析地址簿,指的是移动端消费者用户的地址信息,用户登录成功后可以维护自己的地址信息。同一个用户可以有多个地址信息,但是只能有一个默认地址。数据模型用户的地址信息会存储在address_book表,即地址簿表中。具体表结构如下:导入功......
  • Java的访问修饰符
    为了实现面向对象程序设计(OOP)的封装这个特性,需要程序设计语言提供一定的语法机制来支持。这个语法机制就是访问权限控制(访问修饰符:public、protected、private、default)。在Java中,封装就意味着所有的实例域都带有private访问修饰符(私有的实例域),并提供带有public访问修饰符......
  • LeetCode 538.把二叉搜索树转换成累加树
    1.题目:给出二叉搜索树的根节点,该树的节点值各不相同,请你将其转换为累加树(GreaterSumTree),使每个节点node 的新值等于原树中大于或等于 node.val 的值之和。提醒一下,二叉搜索树满足下列约束条件:节点的左子树仅包含键小于节点键的节点。节点的右子树仅包含键大于节点键的......
  • 树的遍历-二叉树
    给定一棵二叉树的后序遍历和中序遍历,请你输出其层序遍历的序列。这里假设键值都是互不相等的正整数。输入格式:输入第一行给出一个正整数N(≤30),是二叉树中结点的个数。第二行给出其后序遍历序列。第三行给出其中序遍历序列。数字间以空格分隔。输出格式:在一行中输出该树的层序遍......
  • Java + Jpcap实现监控 IP包流量
    Java+Jpcap实现监控IP包流量说明:本设计是计算机网络课程的课设,因为代码是提前实现的,本博客于后期补上,又因为代码没写注释自己也看不懂了,所以,仅供参考,就当提供一种实现方式。文中提供的资料来源于网络,本文仅用于学习交流,如有侵权,可联系我进行删除。效果图:1)课程设计要求1......
  • JAVAWEB-项目-实现登录功能步骤-2023-04-14
    第一步:编写前端页面第二步:设置首页第三步:编写dao层用户dao接口第四步:编写Servic层用户Service接口实现类第五步:编写Servic层用户Service接口第六步:编写Servic层用户Service接口实现类(用@Test测试一下是否可行)第七步:编写LoginServlet类第八步:web.xml注册LoginServlet第九......
  • 二叉树遍历(102.144.94.145)
    102.二叉树的层序遍历BPS/***Definitionforabinarytreenode.*structTreeNode{*intval;*TreeNode*left;*TreeNode*right;*TreeNode():val(0),left(nullptr),right(nullptr){}*TreeNode(intx):val(x),left(nullptr)......