Java数据结构和算法(二)树的基本操作
Java 数据结构 - 二叉查找树:有了高效的哈希表,为什么还需要二叉树
数据结构与算法之美目录(https://www.cnblogs.com/binarylei/p/10115867.html)
在学习二叉查找树之前,我们先看一下,目前已经接触的几种高效的数据结构的时间复杂度:
- 有序数组:查找的时间复杂度为 O(logn),但如果数据发生变化,查找前就需要重新排序,时间复杂度就升为 O(nlogn)。所以动态数据不适合使用二分法查找,也就是不支持动态数据。
- 跳表:通过缓存索引来实现链表的二分法查找,其查找的时间复杂度 O(logn)。另外,在插入和删除时也需要先查找到该结点,因此时间复杂度也是 O(logn)。支持动态数据。
- 哈希表:通过散列函数和数组的随机访问,实现插入、删除、查找的时间复杂度都是 O(1),是一种非常高效的数据结构。支持动态数据。当然散列函数、散列冲突、加载因子的选择直接决定了哈希表的性能。但哈希表的最大缺点是不支持顺序访问,这也是工程中经常将哈希表和链表一起使用的原因。
1. 基本操作
二叉查找树(Binary Search Tree)也叫排序树或有序树或搜索树,它是为实现快速查找而生。二叉查找树的左子树的节点都小于它的父节点,右子树中的节点都大于它的父节点,因此若按中序遍历则从小到大的排序。
二叉排序树或者是一棵空树,或者是具有下列性质的二叉树,它的严格定义如下:
- 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 左、右子树也分别为二叉排序树;
- 没有键值相等的节点。
二叉排序树在搜索中的应用非常广泛,同时二叉排序树的一个变种(红黑树)是 java 中 TreeMap 和 TreeSet 的实现基础。我们看一下二叉查找树的查找、插入、删除这些基本的操作,重点关注其时间复杂度,也就明白了为什么要使用平衡二叉查找树。
1.1 查找
二叉查找树的查找的代码比较简单。
public Object search(Node tree, int value) {
Node p = tree;
while (p != null) {
if (value < p.data) {
p = p.left;
} else if (value > p.data) {
p = p.right;
} else {
return p.data;
}
}
return null;
}
说明: 我们来分析一下二叉查找树的查找的时间复杂度,如果二叉树比较平衡,时间复杂度就是 O(logn)。但如果二叉树退化成了链表,那么时间复杂度就降为 O(n)。
1.2 插入
二叉查找树的插入也很简单,因为只能插入到叶子结点上。
private Node tree;
public void add(int value) {
if (tree == null) {
tree = new Node(value);
return;
}
Node p = tree;
while (p != null) {
if (value < p.data) {
if (p.left == null) {
p.left = new Node(value);
break;
}
p = p.left;
} else {
if (p.right == null) {
p.right = new Node(value);
break;
}
p = p.right;
}
}
}
说明: 在插入前也需要查找结点所在的位置,因此平衡二叉树的最好时间复杂度为 O(logn),但如果退化为链表则最坏时间复杂度为 O(n)。
1.3 删除
删除元素则会比较复杂一点,初删除的结点可能是叶子结点、左子树或右子树只存在一个、左子树或右子树两个都存在。前两种情况比较简单一点(被删除结点 p,删除结点的父结点 pp)
- 被删除结点没有子结点:直接将其父结点对应的左子树或右子树设置为 null。如删除结点 4 时,pp.left = null 或 pp.right = null。
- 被删除结点只有一个子结点:直接将其父结点指向被删除结点的左子树或右子树。如删除结点 7 时,pp.left = p.left 或 pp.right = p.right。
- 被删除结点有两个子结点:此时删除比较复杂,需要先从被删除结点的右子树查找最小值结点 p2 赋值给被删除结点。这样就变成删除这个右子树查找最小值结点 p2 的问题,同第一种情况完全一样。如删除结点 20 时间,需要在其右子树中查找一个最小的值结点 25,将 20 对应结点的值替换成 25,然后再删除 25。
public void remove(int data) {
Node p = tree; // p指向要删除的节点,初始化指向根节点
Node pp = null; // pp记录的是p的父节点
while (p != null && p.data != data) {
pp = p;
if (data > p.data) p = p.right;
else p = p.left;
}
// 没有找到
if (p == null) return;
// 要删除的节点有两个子节点
if (p.left != null && p.right != null) { // 查找右子树中最小节点
Node minP = p.right;
Node minPP = p; // minPP表示minP的父节点
while (minP.left != null) {
minPP = minP;
minP = minP.left;
}
p.data = minP.data; // 将minP的数据替换到p中
p = minP; // 下面就变成了删除minP了
pp = minPP;
}
// 删除节点是叶子节点或者仅有一个子节点
Node child; // p的子节点
if (p.left != null) child = p.left;
else if (p.right != null) child = p.right;
else child = null;
if (pp == null) tree = child; // 删除的是根节点
else if (pp.left == p) pp.left = child;
else pp.right = child;
}
说明: 删除时首先也要查找这个被删除的结点,时间复杂度为 O(logn)。如果有两个子结点时,还需要查找其右子树的最小值,时间复杂度也是 O(logn)。最后删除这个结点。因此总的时间复杂度也是 O(logn)。但如果退化成链表,时间复杂度还是 O(n)。
总结: 二叉查找树的时间复杂度都和树的高度息息相关,也就是 O(height)。如果满足平衡二叉树时树高度为 logn,如果退化成链表时树高度为 n。因此,实际工程使用中,我们使用的是平衡二叉查找树,但为什么又不是完全满足平衡二叉树的红黑树呢?我们在下节继续分析。
2. 二叉查找树 vs 哈希表
前面也说了,哈希表的插入、删除、查找的时间复杂度都是 O(1),而平衡二叉查找树的插入、删除、查找的时间复杂度都是 O(logn),那为什么还要使用二叉树呢?
- 散列表中的数据是无序存储的,如果要输出有序的数据,需要先进行排序。而对于二叉查找树来说,我们只需要中序遍历,就可以在 O(n) 的时间复杂度内,输出有序的数据序列。
- 散列表扩容耗时很多,而且当遇到散列冲突时,性能不稳定。而尽管二叉查找树的性能不稳定,但是在工程中,我们最常用的平衡二叉查找树的性能非常稳定,时间复杂度稳定在 O(logn)。
- 尽管散列表的查找等操作的时间复杂度是常量级的,但因为哈希冲突的存在,这个常量不一定比 logn 小,所以实际的查找速度可能不一定比 O(logn) 快。加上哈希函数的耗时,也不一定就比平衡二叉查找树的效率高。
- 散列表的构造比二叉查找树要复杂,需要考虑的东西很多。比如散列函数的设计、冲突解决办法、扩缩容等。平衡二叉查找树只需要考虑平衡性这一个问题,而且这个问题的解决方案比较成熟、固定。
参考:
- 《平衡二叉树的操作》:https://www.cnblogs.com/zhangbaochong/p/5164994.html
- 《实现一个自己的二叉查找树》:https://www.cnblogs.com/Dylansuns/p/6793032.html
每天用心记录一点点。内容也许不重要,但习惯很重要!
Java数据结构和算法(二)树的基本操作的更多相关文章
- Java数据结构和算法(二)--队列
上一篇文章写了栈的相关知识,而本文会讲一下队列 队列是一种特殊的线性表,在尾部插入(入队Enqueue),从头部删除(出队Dequeue),和栈的特性相反,存取数据特点是:FIFO Java中queu ...
- Java数据结构和算法 - 什么是2-3-4树
Q1: 什么是2-3-4树? A1: 在介绍2-3-4树之前,我们先说明二叉树和多叉树的概念. 二叉树:每个节点有一个数据项,最多有两个子节点. 多叉树:(multiway tree)允许每个节点有更 ...
- Java数据结构和算法(七)B+ 树
Java数据结构和算法(七)B+ 树 数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html) 我们都知道二叉查找树的查找的时间复杂度是 ...
- Java数据结构和算法(一)树
Java数据结构和算法(一)树 数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html) 前面讲到的链表.栈和队列都是一对一的线性结构, ...
- Java数据结构和算法(七)--AVL树
在上篇博客中,学习了二分搜索树:Java数据结构和算法(六)--二叉树,但是二分搜索树本身存在一个问题: 如果现在插入的数据为1,2,3,4,5,6,这样有序的数据,或者是逆序 这种情况下的二分搜索树 ...
- 【Java数据结构学习笔记之二】Java数据结构与算法之栈(Stack)实现
本篇是java数据结构与算法的第2篇,从本篇开始我们将来了解栈的设计与实现,以下是本篇的相关知识点: 栈的抽象数据类型 顺序栈的设计与实现 链式栈的设计与实现 栈的应用 栈的抽象数据类型 栈是 ...
- Java数据结构和算法(四)赫夫曼树
Java数据结构和算法(四)赫夫曼树 数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html) 赫夫曼树又称为最优二叉树,赫夫曼树的一个 ...
- java数据结构和算法07(2-3-4树)
上一篇我们大概了解了红黑树到底是个什么鬼,这篇我们可以看看另外一种树-----2-3-4树,看这个树的名字就觉得很奇怪.... 我们首先要知道这里的2.3.4指的是任意一个节点拥有的子节点个数,所以我 ...
- Java数据结构和算法(十四)——堆
在Java数据结构和算法(五)——队列中我们介绍了优先级队列,优先级队列是一种抽象数据类型(ADT),它提供了删除最大(或最小)关键字值的数据项的方法,插入数据项的方法,优先级队列可以用有序数组来实现 ...
- Java数据结构和算法 - 堆
堆的介绍 Q: 什么是堆? A: 这里的“堆”是指一种特殊的二叉树,不要和Java.C/C++等编程语言里的“堆”混淆,后者指的是程序员用new能得到的计算机内存的可用部分 A: 堆是有如下特点的二叉 ...
随机推荐
- 代码: 日期和时间 datepicker
bootstrap 的相关的时间插件 http://www.bootcss.com/p/bootstrap-datetimepicker/ jquery ui的日期插件 http://www.w3cs ...
- xe7 控件升级
rm.ehlib.synedit OK SynSQLSyn1->TableNames 为NULL,导致添加数据失败,XE6正常 放在按钮里也不正常,就不说初始化次序引起的.
- Simple2D-18(音乐播放器)使用 bass 音频库
BASS 简介 BASS是一个在多个平台上用于软件的音频库.其目的是为开发人员提供功能强大且高效的示例流(MP3,MP2,MP1,OGG,WAV,AIFF),MOD 音乐(XM,IT,S3M,MOD ...
- 从底层谈WebGIS 原理设计与实现(一):开篇
从底层谈WebGIS 原理设计与实现(一):开篇 作者:naaoveGI… 文章来源:http://www.cnblogs.com/naaoveGIS/ 点击数:4773 更新时间: ...
- TEXT 6 Travelling with baggage
TEXT 6 Travelling with baggage 背着行囊去旅行 Feb 16th 2006 From The Economist print edition (1)FEW modern ...
- Filter接口编写过滤器
Filter,过滤器,顾名思义,即是对数据等的过滤,预处理过程.为什么要引入过滤器呢?在平常访问网站的时候,有时候发一些敏感的信息,发出后显示时 就会将敏感信息用*等字符替代,这就是用过滤器对信息 ...
- 显示AVI文件的桢数
procedure TForm1.Button1Click(Sender: TObject);begin MediaPlayer1.TimeFormat := tfFrames; ShowMess ...
- js中常见的内置对象
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- docker 入門
http://dockone.io/article/277 我的碎碎念:Docker入门指南 [编者的话]之前曾经翻译过很多Docker入门介绍的文章,之所以再翻译这篇,是因为Anders的角度很独特 ...
- 如何获取某个网站的favicon.ico
http://moco.imooc.com/player/report.html 今天看到这个网站上,左侧的小图片挺好看的,想弄下来,检查源码,也没有看到 <head> <meta ...