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

news2024/11/26 2:32:15

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

文章目录

  • 最优二叉搜索树问题(Java)
    • 1、前置介绍
    • 2、算法设计思路
      • 2.1 最优二叉搜索树的结构
      • 2.2 一个递归算法
      • 2.3 计算最优二叉搜索树的期望搜索代价
    • 3、代码实现
    • 4、复杂度分析
    • 5、参考资料


在这里插入图片描述


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),因此有以下公式:

∑ 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

在这里插入图片描述

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

i012345
pi0.150.100.050.100.20
qi0.050.100.050.050.1050.10

搜索代价如下公式②:

E [ T 中 搜 索 代 价 ] = ∑ i = 1 n ( d e p t h T ( k i ) + 1 ) • P i + ∑ i = 0 n ( d e p t h T ( d i ) + 1 ) • q i = 1 + ∑ i = 1 n d e p t h T ( k i ) • p i + ∑ i = 1 n d e p t h T ( d i ) • q i ) 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) E[T]=i=1n(depthT(ki)+1)Pi+i=0n(depthT(di)+1)qi=1+i=1ndepthT(ki)pi+i=1ndepthT(di)qi)

在这里插入图片描述

2、算法设计思路

2.1 最优二叉搜索树的结构

在这里插入图片描述

2.2 一个递归算法

求解包含关键字ki, …,kj的最优二叉搜索树, 其中 i <= 1j <=n j ≥ i 一 1 j \geq i一1 ji1 (当 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 ) = ∑ l = i j p i + ∑ l = i − 1 j q i w(i,j) = \sum_{l=i}^{j}p_i + \sum_{l=i-1}^{j}q_i w(i,j)=l=ijpi+l=i1jqi

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

w ( i , j ) = w ( i , r − 1 ) + p r + w ( r + 1 , j ) w(i,j) = w(i,r-1) + p_r + w(r+1,j) w(i,j)=w(i,r1)+pr+w(r+1,j)

因此e[i,j]可重写为:如下公式⑤
e [ i , j ] = e [ i , r − 1 ] + e [ r + 1 , j ] + w ( i , j ) e[i,j] = e[i,r-1] + e[r+1,j] + w(i,j) e[i,j]=e[i,r1]+e[r+1,j]+w(i,j)

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

e [ i , j ] = { q i − 1 j = i - 1 m i n i < = 1 < = j { e [ i , r − 1 ] + e [ r + 1 , j ] + w ( i , j ) } i<=j 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} e[i,j]={qi1mini<=1<=j{e[i,r1]+e[r+1,j]+w(i,j)}j = i - 1i<=j

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

在这里插入图片描述

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

在这里插入图片描述

伪代码如下:

在这里插入图片描述

根据前文的描述,以及与算法MATRIX-CHAIN-ORDER的相似性,很容易理解此算法。第2-4行的for循环初始化e[i, i —1]和w[i, i —1]的值。第5-14行的for循环利用
递归式⑥和递归式(15. 15)来对所有 1 ≤ i ≤ j ≤ n 1 \leq i \leq j \leq n 1ijn计算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按自底向上的顺序逐行计算,在每行中由左至右计算每个表项。

在这里插入图片描述

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/27684.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

R语言探索 BRFSS 数据和预测

加载包 library(ggplot2) library(dplyr) library(Hmisc) library(corrplot) 加载数据 load("brfss2013.RData") 第1部分&#xff1a;关于数据 行为风险因素监测系统&#xff08;BRFSS&#xff09;是美国的年度电话调查。BRFSS旨在识别成年人口的风险因素并报告…

docker启动出现Error response from daemon: Cannot restart container的报错

1、发现问题 突然发现启动(重启)容器的时候报这个错 Error response from daemon: Cannot restart container 容器id: driver failed programming external connectivity on endpoint 容器名 (容器id): (iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --…

图像超分辨率:优化最近邻插值Super-Resolution by Predicting Offsets

文章目录3. Super-Resolution by Predicting Offsets3.1. 这篇论文用于处理栅格化图像的超分&#xff0c;不知道这样翻译对不对&#xff0c;3.2. 作者认为栅格图像的边缘比较规则&#xff0c;可以训练一个offset map移动栅格图像的 边缘点&#xff08;背景和前景像素 移动 和交…

能率携手梦想改造家,打造适老化住宅新典范

家装改造类节目《梦想改造家》第九季温情回归&#xff0c;日本一级建筑设计师本间贵史携手知名燃热品牌能率&#xff0c;与节目组一起关注民生&#xff0c;走进由一家五口组成的“足不出户的家”&#xff0c;共启老宅改造计划&#xff0c;倾情助力普通家庭拥抱生活与梦想&#…

(Matlab实现)蚂蚁狮子优化算法在电力系统中的应用

目录 1 知识一网打尽 2 蚂蚁狮子优化算法在电力系统经济调度中的应用 3 运行结果 4 Matlab代码实现 1 知识一网打尽 这里总结一位博主的电力系统经济调度目录 蚂蚁狮子优化算法&#xff08;完整Matlab代码实现&#xff09; 多目标蚂蚁狮子优化算法&#xff08;Matlab代码…

拒绝灵感焦虑,藏在UI设计师书签里的宝藏网站!

都在说UI设计“越来越吃香”&#xff0c;导致其他门类的设计师一心想转行。 上次和入行8年的UI大佬聊天&#xff0c;她告诉小摹3条UI设计师必备能力&#xff1a; 审美能力和眼界&#xff1a;一个界面好不好看最后都是UI来定&#xff0c;为了不背锅&#xff0c;UI一定要有国际流…

dolphinscheduler2.0.5性能手动测试

目录&#x1f42c;官方配置文件说明&#x1f42c;测试并发量&#x1f420;线程数量设置100&#x1f420;线程数量设置200&#x1f420;线程数量设置500&#x1f42c;测试结论&#x1f42c;官方配置文件说明 官方说明 master.exec.threads&#xff1a; master工作线程数量,用于…

【2013】408联考数据结构真题整理

2013年 1 题目 解析 原始&#xff1a;升序 升序 变 升序 尾插法 改编&#xff1a;升序 升序 变 降序 头插法 2 题目 答案&#xff1a;C 解析 3 题目 答案&#xff1a;B 解析 二叉排序树&#xff0c;或者是空树&#xff0c;或者是满足以下性质的二叉树&#xff1a; …

基于PHP+MySQL音乐网站的设计与实现

随着时代的发展,音乐已经逐渐成为了人们生活中必不可少的一种调剂品,人们对音乐的追求也越来越强烈,为此我通过PHP和MYSQL开发了本音乐网站 本音乐网站是一个综合性的音乐分享网站,它主要实现了娱乐新闻,上榜歌手,音乐试听,音乐下载,下载排行,音乐库,在线留言等基本的音乐共享功…

Matplotlib绘制折线图、散点图、柱状图、直方图、饼图代码

一、折线图 以折线的上升或下降来表示统计数量的增减变化的统计图 特点&#xff1a;能够显示数据的变化趋势&#xff0c;反映事物的变化情况(变化)函数&#xff1a;plt.plot(x, y) import matplotlib.pyplot as plt import randomfrom pylab import mpl mpl.rcParams["f…

day31 文件上传js验证mimeuser.ini语言特性

前言 #知识点&#xff1a; 1、文件上传-前端验证 2、文件上传-黑白名单 3、文件上传-user.ini妙用 4、文件上传-PHP语言特性 #详细点&#xff1a; 1、检测层面&#xff1a;前端&#xff0c;后端等 2、检测内容&#xff1a;文件头&#xff0c;完整性&#xff0c;二次渲染…

基于MATLABsimulink的《电路原理》课程仿真实验平台开发

目 录 摘 要 I Abstract II 第一章 绪论 1 1.1选题背景及意义 1 1.2设计内容 1 1.3设计思想 2 第二章 MATLAB简介 3 2.1 MATLAB程序设计 3 2.2 Simulink仿真 4 2.2.1 Simulink 启动 4 2.2.2 Simulink 模块库及模块操作 4 2.2.3 仿真参数设置 6 2.3图形用户界面&#xff08;GUI&…

基于STM32的温控风扇

本设计是基于STM32的温控风扇&#xff0c;主要实现以下功能&#xff1a; 温度控制风速&#xff0c;四个挡位&#xff0c;停止、低速、中速、高速 按键可切换模式&#xff0c;可手动切换挡位&#xff0c;四个挡位&#xff0c;停止、低速、中速、高速 按键设置温度值&#xff0c…

网络刷卡器开发,刷新移动物联新生活

在物联网应用需求和身份校验普及的影响下&#xff0c;沐渥自主研发生产了一款基于网络协议传输的读卡设备——网络刷卡器&#xff0c;这是一款体积小巧&#xff0c;方便携带&#xff0c;即插即用&#xff0c;无需安装驱动&#xff0c;采用USB通讯&#xff0c;即刻响应对接客户的…

Linux系统漏洞本地提权

目录 一、实验项目名称 二、实验目的 三、实验内容 四、实验环境 五、实验步骤 六、实验结果 七、实验总结 一、实验项目名称 Linux系统漏洞本地提权及跳板设置实验 二、实验目的 1.msf工具的使用&#xff1b; 2.“脏牛”漏洞CVE-2016-5195漏洞利用方法。 三、实验…

MCE | 铁死亡——调节性细胞死亡

多细胞生物中&#xff0c;调节性细胞死亡过程 (RCD) 是细胞维持组织形态和功能必不可少的稳态机制。此前研究较多的调节性细胞死亡包括三大类&#xff1a;细胞凋亡、自噬和坏死。 “铁死亡”这一概念最早在 2012 年由 Dr. Brent R Stockwell 提出&#xff0c;它是一种铁离子依赖…

ORM概念

ORM概念 ORM是Object Relational Mapping 对象关系映射。简单来说&#xff0c;就是把数据库表和实体类及实体类的属性对应起来&#xff0c;让开发者操作实体类就实现操作数据库表。 ORM(Object Relation Mapping)对象关系映射 思想&#xff1a;将关系数据库中表中的记录映射为对…

百货集团数字化转型方案

一、案例简述 后疫情时代对零售业&#xff0c;特别是百货和购物中心造成了巨大冲击&#xff0c;驱使实体零售业拥抱数字化转型。从总体上看&#xff0c;实体零售企业对于数字化转型的必要性已有充分的认知及一定的实践操作&#xff0c;但数字化仍处于起步阶段&#xff0c;线上…

”消费全返“是割韭菜,非也,广告电商引领全新“全返”模式

谈及消费全返&#xff0c;大家可能会避而不谈&#xff0c;因为有“云联惠”这个实实在在的案例发生&#xff0c;各大企业都怕进局子&#xff0c;现在都不弄消费全返了。说到“云联惠”&#xff0c;为什么它能在短时间内火得这么快&#xff0c;因为他们以“全返”为阙头&#xf…

驱动开发2

P 19 驱动设计的思想&#xff1a;面向对象/分层/分离 用结构体来表示某个对象 分离思想 将某个有很多类似操作的写在一起&#xff0c;这样我们根据参数就可以替换不同的步骤了 1、上下分层 将设计硬件的比如 初始化gpio、设置GPIO写成board.c 同用的就写在drv.c里面 2、左右分…