动态规划-构造最优二叉树的解路径_20230403

动态规划-最优二叉搜索树的解路径(算法导论)

  1. 前言

本文将探索递归的先序和后续对信息表达的影响,通过考察最优二叉搜索树的解roo[i][j]的解,我们可以分析先序和后续遍历之间的互相转换关系,以及为了转换,所付出的空间代价。

本文来源于《算法导论》的习题,习题是对课本中的最优二叉搜索树的解空间的具体表达。首先回顾一下,最优二叉搜索树的求解过程。给定特定的关键字,已知每个关键字的查询概率以及关键字之间未被查询到的概率。相对于常规的最优二叉搜索树,我们考虑了无法搜到到的概率。
在这里插入图片描述

  1. 问题描述

通过动态规划计算,一致root[i][j]代表节点i和节点j之间的根节点,root数组形成上三角形式的查询矩阵。其中i代表起始关键字,j代表结束关键字. root[i][j]表达的数字为这两个关键字之间的根节点的位置。我们从下表中观察到,root[1][5]=2, 代表节点2为节点[1…5]之间的根节点,同理可以观察到节点[2…5]的根节点为4。

在这里插入图片描述

要求在roo[i][j]已知前提下,根据关键字打印出下列对应的二叉树结构:

在这里插入图片描述

  1. 问题求解

对于这个问题的求解,因为涉及到左右孩子和根节点,最原始和直接的理解就是利用前序、中序或后续遍历的形式,对客户需要的打印信息进行分类。那么就需要判断到底采用哪种顺序遍历的数据结构,其实每种形式都可以实现,最终的区别在于,如果我们提前提取和利用相关的信息,那么就可以称之为先序信息处理;如果递归发生后再对信息处理,那么称之为后续信息处理。后续和先序的本质差异在于,一个在于递归后利用现成信息,一个先对信息进行处理,然后再利用。

对于本问题,两个方法都适用,我们分别对其进行阐述。

3.1 先序处理信息

如果要先序处理信息,我们必须清楚左右根节点和目前待处理节点的相对位置,F(i,j,last)分为四种情况:

  • i<j, 在这种情形下:

    • 如果j<last, 那么j就是根节点(last)的左子树
    • 如果j>=last, 那么j就是根节点(last)的右子树
  • i>j,在种种情形下:

    • 如果j<last,那么d(i-1)就是last的左子树,此时,d(i-1)代表叶子节点,某两个关键字之间的信息未被搜索到的概率
    • 如果j>last,那么d(i-1)就是last的右子树,此时d(i-1)也代表叶子节点,表示某两个关键字之间的信息未被搜索到的概率

分析到这里,实际上递归方程在纸面上就呼之欲出,左右摇摆的二叉树肯定是所需要的二叉树结构。同时在前序中做好判断条件,根绝上述四种不同的情形,那么就可以打印出 题干要求的对应答案。

对函数作进一步分析,函数第一个关键字root保留[i]和[j]连续关键字之间的根节点信息,参数i表示起始节点序列号,参数j为结束节点序列号,值得关注的是命名为last的参数,它保留根节点的序列号,总的根节点的根节点并不存在,所以在函数入口前赋值last=-1。

整体函数采用前序遍历的形式,按照上述四类情形,分别进行不同的打印操作。

值得一提的是,对于遍历的赋值,每次都需要把last赋值为root[i][j],以便在下次的前序遍历过程中保留根节点的具体序列号。

void contruct_optimal_bst(int (*root)[N + 2], int i, int j, int last)
{
    if(i>j)
    {
        if (j < last)
        {
            printf("d%d is the left child of k%d\n", i - 1, last);
        }
        else
        {
            printf("d%d is the right child of k%d\n", i-1, last);
        }

        return;  //if i>j, it indicates there is no action to take(no print)
    }
   
    if(last==-1)
    {
        printf("k%d is the root of tree\n",root[i][j]);
    }
    else if(j<last)
    {
        //a[i...j] is located in the left side of the last node
        //j will act the new 'root' node
        // last is the previous the root node
        printf("k%d is the left child of k%d\n",root[i][j],last);
    }
    else
    {
        printf("k%d is the right child of k%d\n", root[i][j], last);
    }

    contruct_optimal_bst(root,i,root[i][j]-1,root[i][j]);
    contruct_optimal_bst(root,root[i][j]+1,j,root[i][j]);

    return;
}

上述遍历最终会形成一颗递归二叉树,对递归二叉树中的i,j及last信息的比较,就可以给出有效的二叉树的信息。假定有5个关键字,我们可以勾勒出递归二叉树的具体形式。

在这里插入图片描述

3.2 混合order 处理信息

前文提过,如果采用多种次序对信息进行处理,也不失为一种有效方式。这种条件下,往往需要提前对信息进行挖掘,以便能提前进行对应信息的打印。

为了方便问题的阐述,我们先上代码,后作具体的分析。

递归函数与上一个函数类似,最大区别在于没有保存根节点的信息,参数root保留连续两个节点之间的根节点,参数i为起始编号,参数j为结束编号,其中n为常数,为总的节点数量。

本操作结合先序、中序和后序三类方式进行遍历,由于未保存根节点信息,所以需要预先判定i与root[i][j]-1的关系,以及root[i][j]+1与j之间的关系。

void contruct_optimal_bst_2(int (*root)[N + 2], int i, int j,int n)
{
    if(i==1 && j==n)
    {
        printf("k%d is the root\n",root[i][j]);
    }

    if(i<j) //start the recursive action with pre-order and post-order traversal method
    {
        if (i <= (root[i][j] - 1))
        {
            printf("k%d is the the left child of k%d\n", root[i][root[i][j] - 1], root[i][j]);
        }
            
        contruct_optimal_bst_2(root,i,root[i][j]-1,n);

        if((root[i][j] + 1)<=j)
        {
            printf("k%d is the the right child of k%d\n", root[root[i][j] + 1][j], root[i][j]);
        }        

        contruct_optimal_bst_2(root, root[i][j] +1,j,n);
    }

    if(i==j) // it has nothing to do with the recursive, just check the situation of dn
    {
        printf("d%d is the the left child of k%d\n", i-1, i);
        printf("d%d is the the right child of k%d\n", i, i);
    }

    if(i>j)
    {
        printf("d%d is the the right child of k%d\n", j, j);
    }

    return;
}

遍历形成的二叉树与第一个先序遍历类似,由于剪枝原因,叶子节点更少。

在这里插入图片描述

  1. 小结

对中间信息的处理起始不改变二叉树的遍历过程,人为定义先序、中序和后序只是对过程中信息处理的时间不同罢了,它并不影响递归树的整体过程和流程。

参考资料

  1. 《Introduction to algorithm, 4ed》
  2. 15.5 Optimal binary search trees - CLRS Solutions (walkccc.me)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯第23天(Python)(疯狂刷题第6天)

题型&#xff1a; 1.思维题/杂题&#xff1a;数学公式&#xff0c;分析题意&#xff0c;找规律 2.BFS/DFS&#xff1a;广搜&#xff08;递归实现&#xff09;&#xff0c;深搜&#xff08;deque实现&#xff09; 3.简单数论&#xff1a;模&#xff0c;素数&#xff08;只需要…

下一个系统不是Win12,微软要复活Win10X

先是 Windows 三年发布周期回归又是官方 UI 泄露&#xff0c;再到前不久新增的测试频道… 微软将在2024年推出或许名为 Windows 12 的下一代系统基本已经板上钉钉了。 相比过去&#xff0c;小蝾总觉得即便是换代更新能带来的震撼都越来越少了。 当年每一个版本都是划时代的更…

.net C#反编译及脱壳常用工具--小结

1、Reflector --微软自家工具--推荐 Reflector是最为流行的.Net反编译工具。Reflector是由微软员工Lutz Roeder编写的免费程序。Reflector的出现使NET程序员眼前豁然开朗&#xff0c;因为这个免费工具可以将NET程序集中的中间语言反编译成C#或者Visual Basic代码。除了能将IL转…

【学习笔记、面试准备】机器学习西瓜书要点归纳和课后习题参考答案——第3章

机器学习西瓜书要点归纳第3章 线性模型3.1 基本形式3.2 线性回归3.3 对数几率回归3.4 线性判别分析3.5 多分类学习3.6 类别不平衡问题3.7 阅读材料习题目录地址 第3章 线性模型 3.1 基本形式 线性模型定义&#xff1a; 其中x是输入向量 优点&#xff1a;形式简单&#xff…

C#中的转换

一、什么是转换 将一个类型转换为另外一个类型&#xff1b;可以是两个值类型之间的转换&#xff1b;可以是两个引用类型之间的转换&#xff1b;可以是值类型和引用类型之间的转换&#xff08;拆箱与装箱&#xff09;&#xff1b;可以用户自定义转换。转换的时候有隐式转换/自动…

lombok快速入门

Lombok是一个实用的Java类库&#xff0c;可以通过简单的注解来简化和消除一些必须有但显得很臃肿的Java代码。 通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法&#xff0c;并可以自动化生成日志变量&#xff0c;简化java开发、提高效率。 <…

好用到爆的windows文件检索工具--Everything

如果你的电脑是windows系统&#xff0c;那么这款软件强烈推荐大家安装>Everything&#xff0c;他可以帮助你快速的检索的磁盘里的文件&#xff0c;话不多说&#xff0c;开始安装 1.下载 访问https://www.voidtools.com/zh-cn/会跳转官方下载地址 双击安装包运行 效果如下…

Tensor张量基础与常用方法【Pytorch】

Tensor中文译名为张量&#xff0c;标量是零维张量&#xff0c;向量是一维张量&#xff0c;矩阵是二维张量&#xff0c;矩阵的堆叠是三维张量…… 张量的维数可以无穷大&#xff0c;不过由于现实世界是三维的&#xff0c;因此更高维度的图形我们无法想象&#xff0c;但是这并不…

即时通讯-6-已读回执的方案设计

背景-为什么展示已读未读 部分即时通讯软件会选择展示给用户已读未读&#xff0c; 主要是快速感知对方的阅读状态&#xff0c; 感觉到自己受重视&#xff0c; 方便做下一步操作。 如果要带点高度的讲&#xff0c;满足软件所代表的关键用户的诉求 什么场景下要展示已读回执 t…

462. 最小操作次数使数组元素相等 II——【Leetcode每日一题】

462. 最小操作次数使数组元素相等 II 给你一个长度为 n 的整数数组 nums &#xff0c;返回使所有数组元素相等需要的最小操作数。 在一次操作中&#xff0c;你可以使数组中的一个元素加 1 或者减 1 。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;2 …

微信小程序获取手机号47001 data format error hint的完美解答(restTemplate发送post请求)

发现问题 这几天正在搞微信小程序获取手机号功能开发&#xff0c;发现发送post请求接口时候&#xff0c;接口返回如下错误&#xff1a; {"errcode": 47001,"errmsg": "data format error hint: [******] rid: ******" } post请求的url为&…

动态代理原理

一、案例分析 1、引出问题 回到Spring之初控制事务繁琐的问题。 回到Spring之初控制事务繁琐的问题. 考虑一个应用场景∶需要对系统中的某些业务方法做事务管理&#xff0c;拿简单的save和update操作举例。没有加上事务控制的代码如下。 加上事务代码&#xff0c;如下&#x…

大数据平台开发——使用Java和Python调用Shell脚本

大数据平台开发——使用Java和Python调用Shell脚本 背景 在大数据平台开发中&#xff0c;经常会遇到需要调用Shell脚本的场景&#xff0c;倒不是说只能用Shell&#xff0c;毕竟大数据开发到头来一定是个语言无关的事情&#xff1a; 从Hive源码解读大数据开发为什么可以脱离S…

Java进阶

注解 什么是注解 Java注解&#xff08;Annotation&#xff09;又称Java标注&#xff0c;是JDK5.0引入的一种注释机制。 Java语言中类、方法、变量、参数和包等都可以被标注。Java标注可以通过反射获取标注内容。在编译器生成类文件时&#xff0c;标注可以被嵌入到字节码中。Ja…

【Python实操】一行代码就可以自动画出这种艺术画?(详细教程)

文章目录前言一.准备阶段二、开始使用 Discoart1.引入库2.显示/保存/加载配置总结前言 DiscoArt 是一个很牛逼的开源模块&#xff0c;它能根据你给定的关键词自动绘画。 绘制过程是完全可见的&#xff0c;你可以在 jupyter 页面上看见这个绘制的过程&#xff1a; 一.准备阶段…

零拷贝内存 固定内存

一、总览 虚拟内存是一种计算机内存管理的技术&#xff0c;它让程序认为程序自身有一段完整的连续可用的内存&#xff08;一个地址空间&#xff09;。当程序运行时所占的内存空间大于物理空间容量&#xff0c;操作系统可以将暂时不用的数据放入到磁盘&#xff0c;用的时候再拿出…

Linux--高级IO--select--0326

目录 IO为什么低效&#xff1f; 1.快速理解五种IO模式 2.五种IO模型 3.非阻塞IO fcntl() 4.IO多路转接 select select fd_set类型 struct timeval*类型 5.Select的代码测试 5.1 问题一&#xff1a;一开始&#xff0c;我们只有一个listen套接字 5.2 问题二&#xff1…

《项目管理知识体系指南(PMBOK)》第7版之8大绩效域

项目绩效域被定义为一组对有效交付项目成果至关重要的相关活动。 《项目管理知识体系指南&#xff08;PMBOK&#xff09;》第7版将项目管理划分为干系人、团队、开发方法和生命周期、规划、项目工作、交付、测量、不确定性共8大绩效域。 一、干系人绩效域 解决与干系人相关的…

【对YOLOv8(ultralytics)打印测试结果的调整】(1)使得map值打印显示从0.551变为55.08 (2)打印出FPS

目录1. 最终打印效果2. 做两处更改2.1 修改map显示&#xff0c;在ultralytics-main/ultralytics/yolo/v8/detect/val.py中操作2.2 打印FPS&#xff0c;在ultralytics-main/ultralytics/yolo/engine/validator.py中操作❗❗❗ 兄弟姐妹们&#xff0c;如果看习惯了运行train.py时…

PMP应该如何备考?

PMP现在是新考纲&#xff0c;PMP新版大纲加入了 ACP 敏捷管理的内容&#xff0c;而且还不少&#xff0c;敏捷混合题型占到了 50%&#xff0c;前不久官方也发了通知 8月启用第七版《PMBOK》&#xff0c;大家都觉得考试难度提升了&#xff0c;我从新考纲考完下来&#xff0c;最开…