BST and Heap详解
BST(Binary Search Tree)
基本特点:
- 二叉树
- 集合中的数据具有可比较大小的关键码
- 数据之间满足BST特性
- 中序遍历可得到一个递增的数据序列(可作为判断一棵二叉树是否是BST的方法)
- 同一个数据集合,可存在多个不同形态的BST树
基本操作
- 问题描述+求解动机+算法思想+算法步骤+性能分析
- 进行操作,都需要:先找到要操作的数(位置),进行操作,保证BST的特性,保障优的算法性能。
查找(logn) | 插入(logn ~ n) |
---|---|
若给定值小于根结点的关键字,则继续 在左子树上进行查找 | 若给定值小于根结点的关键字,则继续在左子树上进行插入; 将返回值(结点指针)设置为(当前)根结点的左孩子 |
若给定值大于根结点的关键字,则继续 在右子树上进行查找 | 若给定值大于根结点的关键字,则继续在右子树上进行插入; 将返回值(结点指针)设置为(当前)根结点的右孩子 |
若给定值等于根结点的关键字,则查找 成功 | / |
删除操作:
从最简单的情况开始——删除最小值;
由BST特性可知,BST中最小值一定在左子树的最左边取到,于是去get到它。
BST删除最小值算法思想(非递归)
1)定义一个指向BST树结点的临时指针变量p和pp。
2)从BST的根结点开始;将其值赋值给p。
3)若p的左孩子不等于空指针,将p的值赋值给pp;然后,将p的左孩子指针的值赋值给p;
4)重复第3步,直至p的左孩子的值等于空,结束查找。
5)p所指结点即为最小值结点。
6)若最小值结点不是根结点(BST根结点的左孩子不为空),则将p的右孩子指针作为pp的左孩子,否则将根结点的右孩子作为新的根结点。
算法分析
BST的最小值结点,从根结点的左孩子开始, 第一个左孩子为空的结点。
算法的步骤分为查找和接入。
- BST删除最小值的时间复杂度等于查找时间复杂度。
- 如果二叉树是平衡的,则有n个结点的二叉树 的高度约为logn,但是,如果二叉树完全不平衡(如成一个链表的形状),则其高度可以达到n。
BST删除算法思想
- 定位到待删除结点,此时有三种情况,设要删除结点为x,其父节点为px
1) 无子节点:直接将该 px->child 置空;
2) 有一个子节点:px->child = px->child->child;
3) 有两个子节点:找到X结点右子树中的最小值结点(后继结点)或者是左子树中的最大值(前驱节点),交换X结点的值和该节点的值(实际上实现了两个结点的互换),删除该结点,设置被删除结点的地址为该结点右子树的的最小值地址。 - 此时要注意的是,这里指的删除是指删除某一个值,而不是非要删除这个结点(用其他结点代替它被删除也行,反正值不会再出现在二叉树中了)。
# Heap
堆树或是一棵空树,或是具有下列性质的一棵**完全二叉树**(堆的局部有序特性):
- 每一个结点存储的值都小于等于其子节点存储的值——最小值堆(小顶堆)
- 每一个结点存储的值都大于等于其子节点存储的值——最大值堆(大顶堆)
由于是完全二叉树,用顺序表就十分方便存储
同一个数据集合,可以存在多个不同形态的堆
基本操作
插入:插入新值,新增结点,保持堆特性,算法代价要小
在哪最容易插入呢?叶子结点 最后的那个
算法思想:
均以最大值堆为例
- 在堆的末尾新增一个结点,存放新元素值val;
- 对其进行调整以维持堆的特性:
1.若它大于父节点的值,则将其与父节点交换,继续进行比较;
2.若它小于等于父节点的值或者是根节点了,则已处在正确位置,结束。
**算法分析**
- 堆的插入操作,插入结点都是作为一个叶子 结点插入到堆中。
- 算法的步骤分为插(接)入和交换值。
- 接入过程不需要移动结点,也不会整体改动树,所以时间开销为常数。
- 堆的插入时间复杂度取决于交换的次数。
- 堆是完全二叉树,则有n个结点的堆的高度为logn。插入新元素时,最佳情况时不用交换,最差情况交换logn,平均情况为logn。
构建
- 逐个插入法:新建一棵空堆,然后把数据集中的n个元素依次取出,逐个执行堆的 插入基本操作。
- 交换法建堆:新建一棵有n个结点的完全二叉树;然后把数据集中的n个元素任意的存储到完全二叉树中;判断完全二叉树中所有的父子结点对,若父结点中的元素值小于子结点中的元素值,则交换,直至完全二叉树满足堆的性质。
仅对第二种方法进行介绍。
交换法建堆算法思想
先要掌握调整结点的两种方法:向上调整and向下调整
- 向上调整(上拉):将要调整的结点x与其父节点进行比较,若比其大,则将这俩交换位置,再继续将x与现在的父节点进行比较;直至x为根节点或x小于等于其父节点的值为止。
- 向下调整(下拉):将要调整的结点x与其两个子节点进行比较,若小于它们,则取其中较大的那个与x交换,继续将x与其子节点进行比较;直至x大于它两个子节点的值或者为叶节点为止。
掌握了上面两种方法,那么建堆就容易了。
简述思路:
1. 直接将要存的数据按输入顺序存放到数组中;
2. **从[n/2]号位开始倒着枚举,对每个遍历到的结点i进行[i,n]范围内的调整**
为什么倒着枚举呢?
首先说为什么从[n/2]号位开始吧,因为其叶节点无法向下调整了,只需对所有非叶节点进行调整即可;
每一次调整都**将一个结点调整到了合适的位置,也就是说,以该节点为根结点的树已经满足了堆的特性,当前子树中权值最大的结点就会处在根节点的位置,这样当遍历到其父亲结点时,就可以直接使用这个结果**。
这种做法保证每个结点都是以其为根节点的子树中的权值最大的结点。
删除
同样,我们先讨论最简单的情况——删除堆中最大值。
由堆的性质可知,堆的最大值就是根节点,那么就删除第一个元素就可以了。那怎么删?
算法思想
- 在堆树中找到保存最大值的结点
1)最大值堆的最大值结点是最大值堆树的根结点。 - 删除最大值
2)保存最大值,交换堆的根结点的(最大)值与堆的最后一个 结点的值,堆的元素个数减一。 - 保持树的堆特性
若新树不空,对新的根结点的值R ,执行下拉(shiftdown)操作
3)R的值大于或等于其两个子结点,此时堆结构已经完成;
4)R的值小于某一个或全部两个子结点的值,此时R应与两个子结点中值较大的一个交换,若R仍然小于其新子结点的一个或两个。在这种情况下,只需要简单地继续这种将“R拉下来” 的过程,直至到达某一层使它大于它的子结点,或者它成为叶结点。
算法分析
- 删除最大值堆的最大值,即删除最大值堆的根结点。
- 如果基于数组实现最大值堆,最大值在数组 的基地址。
- 算法的步骤分为交换和维持堆性质。
- 堆删除最大值的时间复杂度等于对根结点执行一次下拉操作的时间复杂度。
- 堆是完全二叉树,则有n个结点的堆的高度为logn。下拉根结点时,最佳情况时不用交换,最差情况交换logn,平均情况为logn。
删除算法:
算法思想
- 在堆树中找到保存被删除的值结点
1)遍历最大值堆树,查找被删除的值,记住值的结点地址。 删除该值
2)交换堆的结点的值与堆的最后一个结点的值,堆的元素个数减一。 - 保持新树的堆特性
3)这个结点位置新的值,可能比父结点的值大,要向上交换,直到 其小于或等于其父结点的值,或达到根结点位置
4)然后这个值(新的位置)很可能比它的另一个子树的结点小,要执行下拉(shiftdown)操作:
4.1)R的值大于或等于其两个子结点,此时堆结构已经完成;
4.2)R的值小于某一个或全部两个子结点的值,此时R应与两个子 结点中值较大的一个交换,若R仍然小于其新子结点的一个或两个。在这种情况下,只需要简单地继续这种将“R拉下来”的过程,直至到达某一层使它大于它的子结点,或者它成为叶结点。
算法分析
- 删除最大值堆的某个值。
- 算法的步骤分为查找,交换和维持堆性质。
- 如果基于数组实现最大值堆,堆删除值的时间复杂度取决于查找,交换和维持堆性质的的时间复杂度。
- 堆是完全二叉树,则有n个结点的堆的高度为 logn。堆的查找操作时间复杂度为O(n),交换 的时间开销是常数,维持堆性质的上推和下拉操作的时间复杂度为O(logn),所以堆删除操作的时间复杂度为O(logn)
那么我们来思考一下,为什么插入的时候将元素放到末尾只需将元素向上调整,而删除的时候,交换待删的元素与最后一个,需要向上调整再向下调整呢?为什么多了一步?
其实仔细想想还是比较容易想到的。插入的时候,原有的堆就已经具有堆的特性,只是在最后加了一个元素需要调整。举个例子,就是说上面的所有父节点的值都大于两个子节点的值,一旦大于父节点,毫不犹豫需要上移且不可能需要再次下移。
而删除的时候,由于最后一个元素移到了中间某个位置,整个堆的特性都已经被破坏。举个例子,该值可能比它的子节点的值要小,如果仅向上调整的话会导致堆特性没法恢复。
堆排序
由堆的特性可知,堆顶元素是最大的,因此堆排序的直观思路就是取出堆顶元素,然后将堆的最后一个元素替换至堆顶,再进行依次针对堆顶元素的向下调整——如此重复直至堆中仅有一个元素未被遍历。
具体实现时,为了节省空间,可以倒着遍历数组。假设当前访问到i号为,那么将堆顶元素与i号为元素交换,接着在[1,i-1]范围内对堆顶元素进行一次向下调整即可。
让我们来对比一下BST和堆吧
| BST | Heap |
---|---|
insert | logn |
find | n |
delete | logn |
create | n |
Traverse | n |
BST and Heap详解的更多相关文章
- JVM性能分析工具详解--MAT等
获得堆转储文件 巧妇难为无米之炊,我们首先需要获得一个堆转储文件.为了方便,本文采用的是 Sun JDK 6.通常来说,只要你设置了如下所示的 JVM 参数: -XX:+HeapDumpOnOutOf ...
- STL之heap与优先级队列Priority Queue详解
一.heap heap并不属于STL容器组件,它分为 max heap 和min heap,在缺省情况下,max-heap是优先队列(priority queue)的底层实现机制.而这个实现机制中的m ...
- Tomcat使用详解
Tomcat简介 官网:http://tomcat.apache.org/ Tomcat GitHub 地址:https://github.com/apache/tomcat Tomcat是Apach ...
- slf4j log4j logback关系详解和相关用法
slf4j log4j logback关系详解和相关用法 写java也有一段时间了,一直都有用slf4j log4j输出日志的习惯.但是始终都是抱着"拿来主义"的态度,复制粘贴下配 ...
- 数据结构图文解析之:二叉堆详解及C++模板实现
0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...
- centos7.2环境elasticsearch-5.0.1+kibana-5.0.1+zookeeper3.4.6+kafka_2.9.2-0.8.2.1部署详解
centos7.2环境elasticsearch-5.0.1+kibana-5.0.1+zookeeper3.4.6+kafka_2.9.2-0.8.2.1部署详解 环境准备: 操作系统:centos ...
- Chrome开发者工具详解(4)-Profiles面板
Chrome开发者工具详解(4)-Profiles面板 如果上篇中的Timeline面板所提供的信息不能满足你的要求,你可以使用Profiles面板,利用这个面板你可以追踪网页程序的内存泄漏问题,进一 ...
- JAVA中的GC机制详解
优秀Java程序员必须了解的GC工作原理 一个优秀的Java程序员必须了解GC的工作原理.如何优化GC的性能.如何与GC进行有限的交互,因为有一些应用程序对性能要求较高,例如嵌入式系统.实时系统等,只 ...
- mysql配置文件my.cnf详解
原文地址:mysql配置文件my.cnf详解 作者:gron basedir = path 使用给定目录作为根目录(安装目录). character-sets-dir = path 给出存放着字符集的 ...
随机推荐
- Bootstrap初识
目录 概述 快速入门 响应式布局 CSS样式和JS插件 全局CSS样式 组件 插件 案例:黑马旅游网 概述 概念:一个前端开发的框架,Bootstrap是美国Twitter公司的设计师Mark Ott ...
- 在web项目中使用shiro(记住我功能)
第一步,添加“记住我”复选框,rememberMe要设置参数 第二步,配置shiro的主配置文件 注意 rememberMeCookie对应的bean中要声明 <constructor-arg ...
- 【T-SQL】基础 —— 语法(1)
USE master--检查是否已经存在一个表,如果有就删除IF(EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ' ...
- javaScript 添加和移除class类名的几种方法
添加类属性: // 一次只能设置一个类值,如果当前属性本身存在类值,会被替换 element.className = '类名'; /* * .setAttribute 用来设置自定义属性和值的 * 自 ...
- zabbix-agent客户端安装与配置
zabbix-agent客户端安装与配置 下载abbix-agent客户端源码软件包 解压agent源码包,并且切换到解压目录. [root@localhost ~]# tar -zxf zabbix ...
- HDU 2006 (水)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2006 题目大意:给你几个数,求奇数的乘积和 解题思路: 很水,不需要数组的,一个变量 x 就行 代码: ...
- c++内存管理学习纲要
本系列文章,主要是学习c++内存管理这一块的学习笔记. 时间:6.7-21 之下以技术内幕的开头语,带入到学习C++内存管理的技术中吧: 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题 ...
- LeetCode链表专题
链表 套路总结 1.多个指针 移动 2.虚假链表头:凡是有可能删除头节点的都创建一个虚拟头节点,代码可以少一些判断(需要用到首部前一个元素的时候就加虚拟头指针) 3.快慢指针 如leetcode160 ...
- linux --开机自动挂载硬盘【转】
转:http://c.biancheng.net/view/900.html 了解了 mount 命令之后,读者可能会问,系统如何在开机时自动挂载硬盘,它又是怎么知道哪些分区是需要挂载的呢? 很简单, ...
- 不吹牛X,我真的干掉了if-else
我们在web开发中,经常使用数据库表中的字段作为"标记"来表示多个"状态",比如: 我们就以某宝的在线购物流程为例进行分析.在订单表中,使用zt字段来表示定单的 ...