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

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

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



a53fa7633514475fa766316fab7a2e3e.jpeg



1、前置介绍


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


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


  • 在二叉搜索树的内结点中找到 x=x1 ;
  • 在二叉搜索树的叶结点中确定x属于 (xixi+1)。
\sum_{i=1}^{n}p_i + \sum_{i=0}^{n}q_i = 1

Snipaste_2023-01-04_13-30-16.jpeg

最优二叉搜索树的形式化定义如下:给定一个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),因此有以下公式:

1.jpeg


对一个 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


搜索代价如下公式②:

E[T中搜索代价] = \sum_{i=1}^{n}(depth_T(k_i) + 1)• P_i + \sum_{i=0}^{n}(depth_T(d_i) + 1)• q_i\\   
= 1 + \sum_{i=1}^{n}depth_T(k_i)• p_i + \sum_{i=1}^{n}depth_T(d_i)• q_i)

1.jpeg

2.jpeg


2、算法设计思路


2.1 最优二叉搜索树的结构

3.jpeg


2.2 一个递归算法

求解包含关键字ki, …,kj的最优二叉搜索树, 其中 i <= 1  , j <=n   且 j >= i-1  (当 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的子树, 所有概率之和为以下公式③:

w(i,j) = \sum_{l=i}^{j}p_i + \sum_{l=i-1}^{j}q_i

Snipaste_2023-01-04_13-30-16.jpeg

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


w(i,j) = w(i,r-1) + p_r + w(r+1,j)

1.jpeg

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

e[i,j] = e[i,r-1] + e[r+1,j] + w(i,j)

2.jpeg

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

作为根结点,可得最终递归公式⑥:

e[i,j] = \begin{cases} q_i-1& \text{j = i - 1}\\ min_{i<=1<=j}\{e[i, r-1] + e[r+1,j]+w(i,j)\}& \text{i<=j} \end{cases}

Snipaste_2023-01-04_13-30-16.jpeg


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


4.jpeg


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

5.jpeg


伪代码如下:

6.jpeg


根据前文的描述,以及与算法MATRIX-CHAIN-ORDER的相似性,很容易理解此算法。第2-4行的for循环初始化e[i, i —1]和w[i, i —1]的值。第5-14行的for循环利用

递归式⑥和递归式(15. 15)来对所有 1 <= i <= j <= n 计算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按自底向上的顺序逐行计算,在每行中由左至右计算每个表项。

Snipaste_2023-01-04_11-42-37.jpeg


3、代码实现


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

/*** TODO 实现最优二叉树算法*/publicclasst4 {
publicstaticvoidmain(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个关键字(可以搜索得到的,即实节点)intn=p.length-1;
// TODO root[i][j]记录的是最终得出的[i,j]这个子段里的根节点int[][] root=optimalBinarySearchTree(p, q, n);
// TODO root的 length 是 n+2, i 和 j 只需要循环到 ninttemp=root.length-1;
// TODO 输出一下这个root矩阵,直接根据这个矩阵也可以画出来最优二叉搜索树//  root矩阵将对角线旋转到水平方向更直观地看到最优二叉搜索树for (inti=1; i<temp; i++) {
for (intj=1; j<temp; j++) {
System.out.print(root[i][j] +"-");
            }
System.out.println();
        }
    }
privatestaticint[][] optimalBinarySearchTree(double[] p, double[] q, intn) {
// TODO e[i][j]表示 i 到 j 这段的代价//  e[1][0]为左边只包含伪节点d0的代价...e[n+1][n]为左边只包含伪节点dn的代价double[][] e=newdouble[n+2][n+2];
// TODO i 到 j 这一段的总概率,在加一层根节点时需要用到//  为避免每次计算e[i, j]都重新计算w(i,j)的值,直接将这些值存放在w数组中double[][] w=newdouble[n+2][n+2];
// TODO root[i][j]记录的是最终得出的[i,j]这个子段里的根节点int[][] root=newint[n+2][n+2];
// TODO 初始化for(inti=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 (intl=1; l<=n; l++) {
for (inti=1; i<=n-l+1; i++) {
intj=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(intr=i; r<=j; r++) {
// TODO 加了一层,相当于下面的左子树右子树都加了一布,其实就是最终比没加一层根节点比多了一个w[i][j]doublet=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]);
returnroot;
    }
}


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、参考资料

  • 算法分析与设计(第四版)
  • 算法导论第三版
目录
相关文章
|
6月前
|
存储 Java
1038. 从二叉搜索树到更大和树 --力扣 --JAVA
给定一个二叉搜索树 root (BST),请将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。 提醒一下, 二叉搜索树 满足下列约束条件: 节点的左子树仅包含键 小于 节点键的节点。 节点的右子树仅包含键 大于 节点键的节点。 左右子树也必须是二叉搜索树。
59 1
|
6月前
|
Java
java如何实现一个高效的二叉搜索树(BST)?请给出时间复杂度分析。
这段内容介绍了Java中使用AVL树实现高效二叉搜索树的方法。AVL树是一种自平衡树,节点子树高度差不超过1,确保操作时间复杂度为O(log n)。代码包括了`Node`类和`AVLTree`类,实现了节点、插入、删除、查找和平衡旋转等方法。通过旋转操作,维持树的平衡,保证了搜索效率。
43 6
|
6月前
|
算法 C++ Java
Java每日一练(20230504) 位1的个数、移除元素、验证二叉搜索树
Java每日一练(20230504) 位1的个数、移除元素、验证二叉搜索树
34 0
Java每日一练(20230504) 位1的个数、移除元素、验证二叉搜索树
|
6月前
|
存储 Java Serverless
二叉搜索树 和 哈希表 (JAVA)
【1月更文挑战第1天】阿里云初体验 二叉搜索树 二叉搜索树的插入 二叉搜索树的查找 二叉搜索树的删除 哈希表 哈希冲突 闭散列 线性探测法 二次探测法 开散列 二叉搜索树又称二叉排序树,它具有以下性质的二叉树或空树: 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值 它的每颗子树也分别为二叉搜索树 而哈希表是一种插入/删除/查找时间复杂度都是O(1)的数据结构,它的查询之所以也可以如此之快的的原因就是它在查找时不需要经过任何比较,直接一次从表中得到要搜索的元素。
53 0
二叉搜索树 和 哈希表 (JAVA)
|
6月前
|
Java
LeetCode题解-二叉搜索树中第K小的元素-Java
LeetCode题解-二叉搜索树中第K小的元素-Java
31 0
|
6月前
|
存储 算法 Java
230. 二叉搜索树中第K小的元素 --力扣 --JAVA
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。
139 3
|
6月前
|
Java
98. 验证二叉搜索树 --力扣 --JAVA
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下: 节点的左子树只包含 小于 当前节点的数。 节点的右子树只包含 大于 当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。 -2^31 <= Node.val <= 2^31 - 1
51 0
98. 验证二叉搜索树 --力扣 --JAVA
|
11月前
|
Java
108. 将有序数组转换为二叉搜索树 --力扣 --JAVA
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。 高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
48 0
二叉搜索树(二叉排序树)—Java(下)
二叉搜索树(二叉排序树)—Java(下)
二叉搜索树(二叉排序树)—Java(上)
二叉搜索树(二叉排序树)—Java(上)