深刻理解hashmap(三)哈希表和二叉搜索树的恩怨情仇

前面两篇文章介绍了hashmap的源码和理论,今天把剩余的部分成黑树讲一下。理解好红黑树,对咱们后续对hashmap或者其余数据结构的理解都是颇有好处的。比方说为何后面jdk要把hashmap中的单链表更新成红黑树?算法

要理解红黑树首先要弄清楚普通二叉树的一些基本概念

父节点和子节点,这个我就很少说了。应该都知道。数组

若是某几个子节点的父节点都是一个节点,那他们就是兄弟节点。数据结构

若是某个节点没有父节点,那他就是根节点。iphone

若是某个节点没有子节点,拿他就是叶子节点函数

看下面这个二叉树:性能

A节点的高度:就是节点A到最远端的叶子节点的边数。 比方说这里A节点的高度就是33d

E节点的深度:就是根节点A到节点E的边数,这里明显就是2了,因此E节点的深度就是2指针

E 节点的层数:就是E节点的深度+1. 因此这里E节点的层数就是3.cdn

再看这张图:对象

图1 就叫作满二叉树

条件1:叶子节点所有在最下面

条件2:除了叶子节点之外,每一个节点都有左右2个子节点。

满二叉树很好理解对吧,很少说了。咱们继续看图2,

图2 就叫作 彻底二叉树

条件1:叶子节点只能在最下面2层

条件2:最后一层的叶子节点都靠左排列。

条件3:除了最后一层,其余层的子节点个数必须达到最大。(也能够理解成:把最后一层去掉之后,必须是一个满二叉树

恩,好像彻底二叉树的断定就有点复杂了,不要紧,咱们看几个图练习一下

再看几张图:

图1:不是彻底二叉树吧,条件1就不符合。由于第二层就有一个叶子节点了

图2:不符合条件2,图2 里最后一层叶子节点都向右边了

图3:不符合条件3 ,由于咱们把最后一层去掉之后发现并非一个满二叉树。

为何要有一个彻底二叉树,这东西干啥的?为何条件2要求最后一层叶子节点都靠左排列?向右不能够吗?

首先要明确一个概念,二叉树除了用链表实现之外,还能够用数组来实现。

链表实现的二叉树很少说了,网上太多了,其实数据结构就是一个data字段,而后配一个left指针和right指针。

那若是用数组来作,怎么存储一个二叉树呢?看下面这张图:

很好理解吧,数字就表明数组的下标。好比说 跟节点A 就放在[1]位置,节点F 就放在[6]位置,节点I就放在[9]位置,等等

因此数学概括法之后,咱们能够抽象出以下的规则:

若是一个二叉树用数组来存储,那么节点对应的下表规则以下:

规则1:下标为2*i的 必然是左子节点。

规则2:下标为2*i+1 的,必然是右子节点。

规则3:下标为i/2的节点必然是 节点i的 父亲节点。

因此咱们只要把一个二叉树的根节点放到数组的任意一个位置中,就必然能够把整个二叉树利用上述的规则串联起来,整个就是二叉树的数组存储法。

而用数组存储二叉树的好处就是能够节省left和right 这2个指针,能够节约内存空间

再看一张图:

你看这个二叉树,咱们的出来的数组下标是 1,2,3,4,7,8,9,13.

浪费了 5,6,10,11,12. 浪费了这5个数组空间空闲在那里。

因此彻底二叉树的优势就是不会浪费任何数组空间,因此对于彻底二叉树来讲,数组存储是最合适的方法,比方说堆就是用数组来 作二叉树的存储结构的

前序遍历:先打印节点自己,再打印左子树,再打印右子树

中序遍历:左子树-节点自己-右子树

后序遍历:左子树-右子树-节点自己

那这三种遍历的方法我就很少说了,网上一搜一大堆。

有了这些前置的概念 咱们就能够继续深挖。

二叉搜索树是咩啊?

二叉搜索树:对于任意一个节点来讲,他的左节点的值都要小于这个节点,右节点的值都要大于这个节点。

那么,咱们来对一个二叉搜索树进行他的 查找,插入操做。 为何要学这个,由于对于大部分数据结构来讲,衡量他的

性能主要就是 看查找数据和插入数据的效率。比方说数组和链表 就是查找和插入效率截然相反的两种数据结构。

废话很少说,咱们首先来看一下二叉搜索树的 查找操做要怎么完成:

其实也很简单,对于二叉搜索树来讲, 左节点的值《父节点《右节点。

因此想查找一个值,咱们只要从二叉搜索树的顶部也就是根节点来遍历便可:

若是这个值比节点大,那么就去右子树继续递归查找,若是这个值比节点的值小,那么就去左子树查。直到找到为止

这个递归函数我就不写了,你们知道这个算法思路就能够,本身多练习一下。

二叉搜索树的插入操做就稍微复杂一点:

1.若是要插入的值,比节点的值要大,而且这个节点的右子树为空,那么就直接放在这个节点的右节点位置上(注意哦,这个就是递归的结束条件)

2.若是不为空,那么就继续 步骤1的操做,直到咱们找到对应的节点位置为止。

  1. 若是插入的值 比节点的值要小,而且这个节点的左子树为空,那么就直接放在节点的左节点位置上。。 以此类推。和步骤1-2 实际上是一个思路

能够看出来,递归是实现二叉树相关算法的核心思想。

此外最重要的一点,咱们再回顾一下这个二叉搜索树的特性你会发现:若是咱们用中序遍历的方法 print这个 二叉搜索树,那么 获得的结果必定是一个有序的。时间复杂度为o(n)

因此,二叉搜索树又叫二叉排序树。

若是二叉搜索树中有重复的数据要插入怎么办?

这个问题问的好,这里给出2个解决方案,你们有兴趣能够实现一下

方案1:

正常状况下,咱们一个二叉树若是用链表来实现,那么基础数据结构确定是 data字段和left以及right指针对吧,那么

咱们能够人为的增长一个字段,叫 more指针。这个指针是干吗的呢?这个指针就是把重复的数据串起来。

比方说下图的例子:

你们能够看一下,这个二叉搜索树的 11的那个节点 有一个链表,存储的都是值为11的节点。

这个处理方式是否是有点像hashmap中处理哈希冲突的方法?

那有人又要问了,重复的值插到二叉搜索树的意义何在?

在实际生产中,咱们存储的数据结构不多是一个个简单的int值,而是一个个对象,那么一个个对象里面确定有不少字段, 好比 商品这个对象,能够有不少字段,价格,商品名称,商品图片等。 若是用商品价格来做为二叉搜索树的key值, 那么就确定会出现这种状况,由于相同价格的商品能够是不一样的商品,好比iphone和mate20 都卖7000元,但显然他们是不 同样的东西

方案2:

在插入数据的时候,若是发现插入的值和节点的值相同,那么就把这个值插入到这个节点的右节点的位置。(也就是说 插入一个大于节点的值和插入一个等于节点的值 方法是同样的)

那么针对这种状况,咱们的查找方法就要修改一下了,不能说是找到和节点相同的值就退出递归了,而是找到相同节点的值之后 还要继续找他的右子树,看看有没有值和查找的值相同,一直到叶子节点为止(注意退出递归的条件)

二叉搜索树的效率如何?看起来不如哈希表啊,存在的意义何在?

实际上,咱们看一下二叉搜索树的算法能够得出来一个结果,他的查找效率是O(logn)的,彻底不如哈希表查找的效率o(1). 且,二叉搜索树极端状况下很容易退化成单链表,那么单链表的查找效率咱们都知道是O(N)的。这个就更慢了。

但,即使如此,二叉搜索树还有一些哈希表不具有的优势:

  1. 二叉搜索树用中序遍历能够很容易对 数据进行排序,这个是哈希表作不到的。

  2. 哈希表遇到哈希冲突的时候须要扩容,这个扩容操做至关耗时,性能有时候不稳定,虽说二叉搜索树的性能也不稳定, 可是 咱们有更特殊的 平衡二叉搜索树能够把时间复杂度稳定在O(logn)

  3. 二叉搜索树比较简单,数据结构一目了然,递归递归递归就好了,可是哈希表大家懂的,实现起来超级复杂。

因此能够知道,hashmap和二叉搜索树各有各的优势,具体怎么用,仍是要看实际状况,可是你们要知道这两种数据结构的 优劣在哪里以及为何?

千呼万唤始出来的 红黑树究竟是什么?和咱们上面提到的平衡二叉搜索树又是什么关系呢?

前面咱们说到了,普通的二叉搜索树极端状况下会退化成单链表,好比说:

你看这个二叉搜索树运气就很是差,全在左边,一点都不平衡,看起来跟个单链表同样,一看就是很慢的数据结构。

因此 所谓的平衡二叉搜索树就是要想办法保证在插入的过程当中,让这个二叉搜索树变的平衡起来,所谓的平衡起来 就是不能全在左边或者全在右边,最好是左右两边都有,且左右两边子树的高度越接近就越好就越平衡。由于只有这样 咱们的时间复杂度才会稳定在 O(logn)

平衡二叉搜索树的实现方法有不少,其中最出名的就是红黑树了,对于咱们普通写业务代码不是专业写算法的人来讲 红黑树几乎就能够表明平衡二叉搜索树,二者无限接近。

可是咱们要注意的是,红黑树是近似平衡的 平衡二叉搜索树,并非严格的平衡二叉树,有兴趣的同窗能够查一查AVL树 这个才是严格平衡二叉树,不过这个东西由于太严格致使每次插入删除的效率过低,因此生产环境用的人不多。

关于红黑树的具体实现,我这里就很少说了,由于红黑树的实现比较复杂,我估计除非是写算法的人,不然绝大多数人 一生都不会写一次红黑树,若是实在要用,实际上跳表写起来比红黑树简单多了。。也好理解。

因此这里有兴趣的同窗能够自行搜索红黑树的相关资料,开开眼界。没有兴趣的同窗就只要知道红黑树是一种平衡二叉查找树,它是为了解决普通二叉查找树在数据插入的时候容易退化成单链表致使效率大幅下降的数据结构。他的高度会无限接近于log2n,因此 他的查找效率也就能够稳定在O(logn)了。这也就是为何hashmap在高版本jdk中的实现 用红黑树代替单链表来解决哈希碰撞的缘由。

相关文章
相关标签/搜索