黑马视频笔记系列-4
很多内容从其他人的博客那里来的,如有侵权,则删
数据结构
常用的而数据结构:栈、队列、数组、红黑树。
**栈:**其限制是仅允许在标的一端进行插入和删除操作,不允许在其他任何位置进行添加、查找、删除等操作。
特点:
- 先进后出
- 栈的入口、出口的都是栈的顶端位置。
- 压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
- 弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。
**队列:**其限制是仅允许在表的一端进行插入,而在表的另一端进行删除。
特点:
- 先进先出
- 队列的入口、出口各占一侧
数组:
是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。
特点:
- 查找元素快:通过索引,可以快速访问指定位置的元素
- 增删元素慢:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。
- 指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中
链表
由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时i动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
特点:
- 多个结点之间,通过地址进行连接。
- 查找元素慢:想查找某个元素,只能从头节点开始一个一个遍历
- 增删元素快:
- 增加元素:只需要修改连接下个元素的地址即可。
- 删除元素:只需要修改连接下个元素的地址即可。
红黑树:
二叉树:binary tree ,是每个结点不超过2的有序树(tree)。
红黑树就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。
红黑树的约束:
- 节点可以是红色的或者黑色的
- 根节点是黑色的
- 叶子节点(特指空节点)是黑色的
- 每个红色节点的子节点都是黑色的
- 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
红黑树的特点:
速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍
先讲一下二叉查找树=二叉搜索树=二叉排序树:
二叉查找树的特点:
- 某节点的左子树节点值仅包含小于该节点值
- 某节点的右子树节点值仅包含大于该节点值
- 左右子树每个也必须是二叉查找树
可以看到1.右边的的所有数都小于根节点,右边的所有数都大于根节点,导致右边的每一个数都大于左边的每一个数
查找的时间复杂度O(log2N)
二叉查找树的两个极端情况下:
-
完全二叉树,所有节点尽量填满树的每一层,上一层填满后还有剩余节点的话,则由左向右尽量填满下一层。如上图
-
每一层只有一个节点的二叉树
第一种情况下:查询复杂度:O(log2n)
第二种情况下:
树中每层只有一个节点,该状态的树结构更倾向于一种线性结构,节点的查询类似于数组的遍历。查询复杂度是O(N)
所以二叉查找树的查询复杂度为O(log2n)–O(N)
删除复杂度:
二叉搜索树的节点删除包括两个过程,查找和删除。查询的过程和查询复杂度已知,这里说明一下删除节点的过程。
节点的删除有以下三种情况:根据其子节点的个数
- 待删除节点度为零;
- 待删除节点度为一;
- 待删除节点度为二。
度为0的情况:可以直接删除
度为1的情况:将删除的节点的左子树/右子树移到删除节点的位置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
度为2 的情况:将删除的节点中的最大的左子树的节点移到被删除的节点上
[
插入节点
- 插入节点的过程是,比较元素值是否相等,相等则返回,表示已存在,不相等则判断大小情况,迭代查询左、右子树,直到找到相等的元素,或子节点为空,则将节点插入该空节点位置。
- 插入节点的复杂度和查询的复杂度相同,O(log2N)—O(N)
总结
- 线性结构:在构造和删除复杂度占优
- 完全二叉树:查询复杂度占优
二叉查找树的节点查询、插入和树的高度有关。如果二叉树能够平衡一些,避免树结构向线性结构倾斜,就饿能够显著降低时间复杂度。但是在存储上,内存消耗更多
**红黑树:**Red-Black Tree 「RBT」
是一个自平衡(不是绝对的平衡)的二叉查找树(BST),树上的每个节点都遵循下面的规则:
- 每个节点都有红色或黑色
- 树的根始终是黑色的 (黑土地孕育黑树根, )
- 没有两个相邻的红色节点(红色节点不能有红色父节点或红色子节点,并没有说不能出现连续的黑色节点)
- 从节点(包括根)到其任何后代NULL节点(叶子结点下方挂的两个空节点,并且认为他们是黑色的)的每条路径都具有相同数量的黑色节点
红黑树两大操作:
- recolor (重新标记黑色或红色)
- rotation (旋转,这是树达到平衡的关键)
假设我们插入一个新节点X:步骤
- 将新插入的节点标记为红色
- 如果 X 是根结点(root),则标记为黑色
- 如果 X 的 parent 不是黑色,同时 X 也不是 root:
-
3.1 如果 X 的 uncle (叔叔) 是红色
-
- 3.1.1 将 parent 和 uncle 标记为黑色
- 3.1.2 将 grand parent (祖父) 标记为红色
- 3.1.3 让 X 节点的颜色与 X 祖父的颜色相同,然后重复步骤 2、3
一下面这幅图为例子:
应用上面的步骤:
- 将新插入的 X 节点标记为红色
- 发现 X 的 parent § 同样为红色,这违反了红黑树的第三条规则「不能有两个连续相邻的红色节点」
- 发现 X 的 uncle (U) 同样为红色
- 将 P 和 U 标记为黑色
- 将 X 和 X 的 grand parent (G) 标记为相同的颜色,即红色,继续重复公式 2、3
- 发现 G 是根结点,标记为黑色
- 结束
往往不会去改变根节点的颜色
也就是说:插入一个新节点的时候,先标红,插入(按二叉查找树的插入)之后,判断是否违背红黑树的原则
+ 先看是否连续红节点
+ 再看uncle节点是否为黑色,若红色,则一起变黑,若黑色,则需旋转
+ 最后判断根节点颜色是否满足要求
下面说一下uncle节点是黑色的情况,就需要旋转
- 如果 X 的 parent 不是黑色,同时 X 也不是 root:
-
3.2 如果 X 的 uncle (叔叔) 是黑色,我们要分四种情况处理
-
- 3.2.1 左左 (P 是 G 的左孩子,并且 X 是 P 的左孩子)
- 3.2.2 左右 (P 是 G 的左孩子,并且 X 是 P 的右孩子)
- 3.2.3 右右 (和 3.2.1 镜像过来,恰好相反)
- 3.2.4 右左 (和 3.2.2 镜像过来,恰好相反)
uncle 是黑色的时候我们第一步要考虑的是 旋转,然后分四种情况去旋转
**左左情况:**想象这是一根绳子,手提起 P(插入节点的父节点) 节点,然后变色
旋转完之后,新的根节点,要变成黑色,而原来的根节点要变成红色
左右情况:
左旋: 使 X 的父节点 P 被 X 取代,同时父节点 P 成为 X 的左孩子,然后再应用 左左情况
右右:与左左情况一样,想象成一根绳子
右左
右旋: 使 X 的父节点 P 被 X 取代,同时父节点 P 成为 X 的右孩子,然后再应用 右右情况
首先,红黑树是为了避免让二叉查找树变成线性结构,所以本质上还是一棵二叉查找树,引入红黑节点以及旋转是为了让二叉查找树尽可能变成一棵完全二叉树。而这样的而旋转规则也是基于二叉查找树的原则,旋转之后还是一颗二叉查找树
例子:插入 10,20,30,15 到一个空树中
插入10:向空树中第一次插入数字 10,肯定是 root 节点,标记成黑色
插入20:
- 向树中插入新节点 20,标记为红色
- 20>10,20是10的右节点
插入30:
- 向树中插入新节点 30,标记为红色
- 按二叉树的方式,30变成20的右节点
- 30,20都是红色,违背原则,这个时候没有uncle节点,等价于uncle节点为黑色的情况
- 进行一次右右情况的旋转
- 20变成了根节点,标成黑色,原来的根节点要变成红色
目前的情况:
20(黑色)
/ \
10(红) 30(红)
插入15:
- 向树中插入新节点 15,标记为红色
- 按二叉树的方式,15变成10的右节点
- 10,15,都是红色,uncle节点也是红色
- 将节点10和30,改成黑色即可
其他一些红黑树的问题
- jdk 1.8 HashMap 中有使用到红黑树,你知道触发条件是什么吗?有读过源码是如何 put 和 remove 的吗?
- 这里讲的是红黑树的 insert,delete 又是什么规则呢?
- 哪些场景可以应用红黑树?
- 你了解各种树的时间复杂度吗?