上一篇我们手写了HashMap,还有一个很重要的Map的实现类TreeMap。打开源码第一句话:* A Red-Black tree based {@link NavigableMap} implementation.TreeMap是一个基于红黑树的实现。对红黑树没有了解怎么办,那就先搞清楚红黑树的原理。只要理解红黑树的玩法,TreeMap实现起来就没有那么大的难度了。

红黑树(Red Black Tree) 是一种自平衡二叉查找树。所以我们先搞明白什么是二叉查找树和完美平衡二叉树。

二叉排序树(Binary Sort Tree)是具有下列性质的二叉树:

  1. 元素以关键字为依据,每个结点可比较大小,各元素关键字互不相同。
  2. 对于每个结点,其左子树上所有结点均小于该结点,右子树上所以结点均大于该结点。
  3. 每个结点的左右子树也分别为二叉排序树。
  • 查找:从根结点开始查找,根据每一次比较结果,在当前结点的左子树与右子树选择其一,从而缩小一半的查找范围。如果到达叶子结点仍然不相等,则查找不成功。
  • 插入:首先使用查找算法确定元素的插入位置。如果查找成功,说明相同元素已经存在,则不插入;否则在查找不成功的一条路径之尾插入结点,作为叶子结点。
  • 删除:a. 叶结点直接删除 b. 如果被删除的结点有一个子结点,将子结点移到被删除的元素的位置。c. 采用中序遍历(左-根-右),找到待删除的结点的后继结点,将其与待删除的结点互换,然后删除待删除的结点。假如我们要删除29,中序遍历结果为10-11-13-20-27-29-31-32-39-41-53-50-65-72-91,所以29的后继结点是31,然后互换位置,删除29。
  • 二叉查找树的查询复杂度,和二分查找一样,插入和查找的时间复杂度均为 O(logn) ,但是在最坏的情况下仍然会有 O(n) 的时间复杂度。原因在于插入和删除元素的时候,树没有保持平衡。

为了降低二叉排序树的高度,提高查找效率。平衡二叉树(又称为AVL树)。平衡二叉树(Balanced Binary Tree)是具有下列性质的二叉排序树:

  1. 左子树和右子树都是平衡二叉树
  2. 左子树与右子树高度差绝对值不超过一。结点的平衡因子定义为其左子树与右子树的高度之差。
  3. 在平衡二叉树中,插入或删除一个结点可能破坏二叉树的平衡性,因此在插入或删除时都要调整二叉树。

插入:如果插入一个结点后破坏了二叉树的平衡性,需要调整一棵最小不平衡子树。最小不平衡子树是离插入结点最近,且以平衡因子绝对值大于1的结点为根的子树。若出现不平衡,则要根据新插入的结点与最低不平衡结点的位置关系进行相应的调整。分为LL,RR,LR,RL四种类型。以下只介绍了最简单的情况。

LL型:在最低不平衡结点的左孩子的左子树上插入结点。插入C后,A的平衡因子由1增加到2 。结点B变成新的根结点。A变成右孩子结点,C变成左孩子结点。

RR型:在最低不平衡结点的右孩子的右子树上插入结点。结点B变成新的根结点。C变成右孩子结点,A变成左孩子结点。

LR型:在最低不平衡结点的左孩子的右子树上插入结点。结点C变成新的根结点。A变成右孩子结点,B变成左孩子结点。

RL型:在最低不平衡结点的右孩子的左子树上插入结点。结点C变成新的根结点。B变成右孩子结点,A变成左孩子结点。

平衡二叉树似乎完美解决了二叉排序树的问题,通过旋转使树的高度最小化,对于有 n 个节点的平衡树,最坏的查找时间复杂度也为 O(logn)。

那么我们为什么还需要红黑树?

因为我们不仅仅需要关心查找的效率,还要考虑插入的效率。因为平衡二叉树要求每个节点的左子树和右子树的高度差至多等于1,这个要求非常严格,每次进行插入/删除数据的时候,几乎都会破坏这个规则。在插入/删除数据很频繁的场景中,平衡二叉树的性能就会受到很严重的影响。所以我们需要红黑树。

红黑树的定义

  1. 每个节点或者是黑色,或者是红色。
  2. 根节点是黑色。
  3. 每个叶子节点是黑色。
  4. 如果一个节点是红色的,则它的子节点必须是黑色的
  5. 从任意一个节点到叶子节点,经过的黑色节点是一样的。
  • 查找:红黑树的查找和二叉排序树一样,不再赘述
  • 添加:

这是一个把数据结构可视化的网站,我们用网站进行一些插入数据的试验

Red/Black Tree Visualization​www.cs.usfca.edu

添加根结点时,结点为黑色
添加一个子结点,插入结点为红色,满足红黑树的定义,无需变化
在添加一个孩子,如果添加的数比根结点小,很简单
如果添加的数比根结点的右结点还要大,左旋,15变成根结点,并且改变颜色
如果继续插入一个数,因为不满足性质4,所以10和20的颜色变黑,30变红
继续在右子树上添加35,左旋
继续插入25,此时不需要旋转,只需要改变结点颜色,满足性质四

所以我们发现每一次插入数据之后,如果不满足红黑树的定义,就需要对红黑树的结构进行调整。两种方式:旋转和重新上色。所以我们在Java源码中找到了添加数据的方法,并分析这个方法。

/**

假如我们要往这个红黑数里面添加6
没有调整过的树是这样的
第一步,8和6两个同为红色结点,所以改变颜色,8和14变黑,10变红。对应源码中的情况1,当插入结点的叔叔结点为红,把父结点变黑,叔叔结点也变黑,再把爷爷结点变红
如果插入结点的爷爷结点是右孩子结点,我们找到爷爷结点的父结点,然后在这个结点上左旋,对应源码中的情况2
下一步,右旋,10变黑,20变红,对应源码中的情况3

从源码中看出,情况2是情况3的一种扩展。情况4、5、6是1、2、3的镜像,就不展开了。

删除也需要调整树的结构,但是更复杂一些,我们看一下源码中的方法。

/**

删除一共8种情况,1-4和5-8也是镜像的,就不举具体的例子了。TreeMap果然要比HashMap还要复杂的多,就算读懂了 rebalance的方法,真正实现起来还是有很大的难度。以此为基础,下一篇我们尝试一下手写HashMap。

映射表map(平衡二叉树实现)_手动实现Java集合容器之TreeMap(上)相关推荐

  1. 数据结构之映射表(Map)---第一篇---用链表实现

    一.映射表(Map)简介 映射表是一种依照键/值对存储元素的容器,又称字典(directory),散列表(hash table). 映射表将键和值一起保存,键类似于数组中的下标,不能有重复的键,每个键 ...

  2. 12.映射表map.rs

    /* 映射表*/use std::collections::HashMap;fn test_1() {println!("-------------------------test_1--- ...

  3. JAVA中如何取map的值_如何在java中取map中的键值 的两种方法

    第一种方法根据键值的名字取值 import java.util.HashMap; import java.util.Map; public class Test { /** * @param args ...

  4. java 将map写入文件_如何将java中map数据写入txt文件中

    用户提问 例如 static{ //初始化默认用户 user.setUsername("admin"); user.setPassword("123"); tr ...

  5. python java 爬数据_如何用java爬虫爬取网页上的数据

    当我们使用浏览器处理网页的时候,有时候是不需要浏览的,例如使用PhantomJS适用于无头浏览器,进行爬取网页数据操作.最近在进行java爬虫学习的小伙伴们有没有想过如何爬取js生成的网络页面吗?别急 ...

  6. java 转换上传文档_自己编写JAVA环境下的文件上传组件 (转)

    客户端上传后,端的数据流头尾部格式如下,这里上传了一个文档 我们看看数据流的头部: -----------------------------7d22f821706e0Content-Disposit ...

  7. 【c++】映射表std::map

    文章内容为网络搜集内容 std::map 映射表(Map)容器是一个按特定顺序存储以键值对组合而成的元素的关联容器 // <map> template < class Key,cla ...

  8. java集合——映射表+专用集合映射表类

    [0]README 0.1) 本文描述转自 core java volume 1, 源代码为原创,旨在理解 java集合--映射表+专用集合映射表类 的相关知识: 0.2) for full sour ...

  9. android 按键映射文件,《android 的按键映射表》.doc

    <android 的按键映射表>.doc Keymaps and Keyboard Input This document describes how keyboard input get ...

最新文章

  1. js字符串截取函数substr substring slice使用对比
  2. 8、python基础知识-用户输入
  3. SqlServer中的动态Sql
  4. WebService中因 URL 意外地以“/******”结束,请求格式无法识别。
  5. 手把手教你将pyqt程序打包成exe(1)
  6. 如何使用Spring Security和Basic身份验证保护Jersey REST服务
  7. STM32之串口原理
  8. 手机影音第十三天,xutils3、Glide的使用获取网络图片;下拉、上滑刷新;缓存网络资源...
  9. android 图片文字布局,Android自定义控件图片+文字布局
  10. 基于 MVP + Glide + Retrofit + RxJava2.0 + butterknife 的C9MJ TV App
  11. 七夕王者服务器维护,七夕主题华裳上架、职业平衡来了!8月7日服务器例行维护公告...
  12. HTML表格:日常消费账单表格展示网页
  13. 13.MD5对用户密码进行加密
  14. vue antvG6 绘制组织架构图
  15. vhost-user
  16. python 编程入门到实践
  17. java代理模式解析
  18. 解决Android的WebView无法加载微信公众号链接中的图片和视频
  19. sigmoid的通俗理解
  20. 鸿蒙音响怎么调,专业音响师必知的EQ调整小技巧

热门文章

  1. 链表(单链表、双链表、内核链表)
  2. Python 类的属性和实例属性 Python 的动态语言
  3. 有趣的天平秤假币问题
  4. TCP中间件_个人方案
  5. UITextField属性
  6. C++ 0x 使用可变参数模板类 实现 C# 的委托机制
  7. MFC COMBO-BOX最详细教程
  8. a.out.h 头文件分析 \linux-1.0\linux\include\linux\a.out.h
  9. harmonyos手机开发者beta,HarmonyOS 手机应用开发者 Beta 版到来,对开发者意味着什么...
  10. c语言 从文件读入数组,C++ 将一个文件读入数组再读出数组的方法