数据结构是计算机存储
、组织数据
的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素
的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。接下来分别介绍下常见的数据结构类型。
数组(Array)是一种线性表数据结构。它用于存储具有固定大小的相同类型的数据元素。在数组中,数据元素按照有序的方式进行排列,可以通过索引访问数组中的任意位置的元素。
// 动态初始化:初始化时由程序员只指定数组长度,由系统为数组元素分配初始值
char c1[] = new char[5];
// 静态初始化: 初始化时由程序员显示置顶每个数组的初始值,由系统决定数组长度
char c2[] = new char[]{'E','D','U','Y','U'};
char c3[] = {'E','D','U','Y','U'};
数组的特点如下:
数组的优点:
数组的缺点:
队列是一种特殊的数据结构,其特点是遵循先进先出(FIFO)的原则。队列中的元素只能从一端(称为队尾)添加,而从另一端(称为队头)删除。
队列的特点如下:
Java代码的具体实现
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
queue.offer(3);//尾插
queue.offer(6);
queue.offer(9);
queue.offer(12);
System.out.println(queue);
System.out.println(queue.peek());//访问队列头元素
System.out.println(queue);
System.out.println(queue.poll());//删除队列头元素
System.out.println(queue);
}
链表是一种常见的数据结构,它通过指针将一组零散的内存块串联在一起。链表中的每个内存块被称为节点,每个节点除了存储数据之外,还需要记录链上的下一个节点的地址。
链表的特点是:
链表包括单向链表
、双向链表
和循环链表
等类型。其中,单向链表的节点只有一个后继指针next指向后面的节点;双向链表的节点除了有一个后继指针next指向后面的节点外,还有一个前驱指针prev指向前面的节点;循环链表与单向链表的唯一区别是尾节点的指针指向头节点,形成一个环。
栈(Stack)是一种后进先出(LIFO)的数据结构,它只能在一端进行插入和删除操作,这一端被称为栈顶,另一端被称为栈底。栈的元素之间存在一种顺序关系,这种顺序关系由元素的插入和删除操作决定。
栈的主要操作有:
非线性表:与线性表对立,在非线性表之中,数据之间并不是简单的前后关系。非线性结构是一种相对复杂的数据结构,它不满足线性结构的数据元素之间的一对一关系。非线性结构包括图结构、树结构、二维数组、广义表、多维数组等。
在非线性结构中,数据元素之间存在多对多的关系,这种关系可以通过指针、引用等来实现。非线性结构可以用来表示复杂的数据关系,例如网络关系、图形关系等。
本课程中我们介绍下和Java关联性比较强的非线性结构-树
树
[Tree]是n(n>=0)个结点的有限集。n=0时称为空树。在任意一颗非空树中:
此外,树的定义还需要强调以下两点:
如图,是一棵普通树
度数:结点拥有的子树数目称为结点的 度 。
节点关系:
节点层次:
从根开始定义起,根为第一层,根的孩子为第二层,以此类推。
树的深度:树中结点的最大层次,如上图深度为4
根据不同的分类方式,树可以分为不同的类型:
红黑树
、AVL树
、平衡二叉树
、平衡二叉搜索树
等。红黑树
是一种自平衡二叉查找树,AVL树
也是一种自平衡二叉查找树,它要求任何节点的两个子树的高度差最大为1。平衡二叉树和平衡二叉搜索树则是为了平衡树的左右子树的高度差。每个子节点只有两个节点的树,每个结点至多拥有两棵子树(即二叉树中不存在度大于2的结点),并且,二叉树的子树有左右之分,其次序不能任意颠倒。
二叉查找树也称为有序二叉查找树,满足二叉查找树的一般性质,是指一棵树具有如下性质:
二叉树又分为:
完美二叉树:又称为 满二叉树 ,除了叶子节点之外的每一个节点都有两个孩子节点,每层都被完全填充
完全二叉树:除了最后一层之外的其他每一层都被完全填充,并且所有的节点都保持向左对齐
完满二叉树:除了叶子节点之外的每一个节点都有两个孩子节点。
二叉树的遍历操作:
二叉树中的遍历规则有如下三种:
中序遍历:所谓的中序遍历就是先访问左节点,再访问根节点,最后访问右节点,即左-根-右遍历
先序遍历:所谓的前序遍历就是先访问根节点,再访问左节点,最后访问右节点,即根-左-右遍历(前序)
后序遍历:所谓的后序遍历就是先访问左节点,再访问右节点,最后访问根节点。即左-右-根遍历
查找最小值:沿着根节点的左子树一路查找,直到最后一个不为空的节点,该节点就是当前这个树的最小节点
查找最大值:沿着根节点的右子树一路查找,直到最后一个不为空的节点,该节点就是当前这个树的最大节点
查找前驱节点 :小于当前节点的最大值
查找后继节点 :大于当前节点的最小值
二叉树的删除操作:
二叉树中的删除节点:本质上是找前驱节点或者后继节点来替代
二叉树的查找的局限性:
一个二叉查找树是由n个节点随机构成,所以,对于某些情况,二叉查找树会退化成一个有n个节点的线性链.如下图:
BST存在的问题是,树在插入的时候会导致倾斜,不同的插入顺序会导致数的高度不一样,而树的高度直接影响了树的查找效率。最坏的情况所有的节点都在一条斜线上,这样树的高度为N。基于BST存在的问题,平衡查找二叉树(Balanced BST)产生了。平衡树的插入和删除的时候,会通过旋转操作将高度保持在LogN。其中两款具有代表性的平衡术分别为AVL树(高度平衡树,具备二叉搜索树的全部特性,而且左右子树高度差不超过1)和红黑树。
AVL树是如何实现平衡的呢?,具体是通过左旋或者右旋来实现的。具体如下图:
虽然AVL可以解决二叉树所存在的问题,但是AVL树要求太过严格,左旋和右旋的开销会比较大,这时出现了红黑树,只要求黑色节点平衡即可.
2-3-4树
是四阶的 B树(Balance Tree),他属于一种多路查找树,它的结构有以下限制:所有叶子节点都拥有相同的深度。节点只能是 2-节点、3-节点、4-节点之一。
所有节点必须至少包含1个元素,元素始终保持排序顺序,整体上保持二叉查找树的性质,即父结点大于左子结点,小于右子结点;而且结点有多个元素时,每个元素必须大于它左边的和它的左子树中元素。
下图是一个典型的 2-3-4树:
生成的过程
接下来我们通过演示来看看2-3-4树生成的过程
第一次插入—2节点
插入第二个节点–3节点 合并
插入第三个节点—4节点 合并
插入第4个节点—需要分裂
插入6
插入7
插入8
插入9
插入10
插入11
插入12
最后我们插入1来看看效果
到这儿相信大家对于2-3-4树应该有了个直观的认知了。然后来看看2-3-4树
和红黑树
的对应关系。这个能帮助我们更好的理解红黑树.
红黑树起源于2-3-4树,它的本质就是2-3-4树。
2节点
:一个2节点对应的红黑树节点就是一个黑色节点
3节点
:一个三节点可以有两种情况的红黑树节点,一种是右倾,一种是左倾,所以一个2-3-4树可以有多个红黑树
原则:上黑下红
4节点
:一个四节点转换的情况只有一种,中间节点黑色,左右节点红色
裂变状态
:还有就是在2-3-4树中存在的裂变状态。转换为红黑树后会先变色(不能有两个相邻的红色节点)。
转换为红黑树
:接下来具体看看一个2-3-4树是如何转换为对应的红黑树的,
原始的2-3-4树:
按照右倾规则来转换为:
转换后满足黑色节点平衡的要求
按照左倾规则来转换为:
通过对2-3-4树和红黑树的等价关系,对于我们后面分析红黑树的内容会非常有帮助!!!
红黑树,Red-Black Tree 「RBT」是一个自平衡(不是绝对的平衡)的二叉查找树(BST),树上的每个节点都遵循下面的规则:
红黑树能自平衡,它靠的是什么?三种操作:左旋、右旋和变色
操作 | 描述 |
---|---|
左旋 | 以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点, 右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。 |
右旋 | 以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点, 左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。 |
变色 | 结点的颜色由红变黑或由黑变红。 |
旋转操作
左旋:以某个节点作为旋转点,其右子节点变为旋转节点的父节点,右子节点的左子节点变为旋转节点的右子节点,左子节点保持不变。
右旋:以某!个节点作为旋转点,其左子节点变为旋转节点的父节点,左子节点的右子节点变为旋转节点的左子节点,右子节点保持不变。
Java代码实现旋转:
先进行类结构定义
package com.bobo.util.treemap;
public class BRTree {
private static final boolean RED = false;
private static final boolean BLACK = true;
private RBNode root;
public RBNode getRoot() {
return root;
}
public void setRoot(RBNode root) {
this.root = root;
}
/**
* 表示 节点
* @param
* @param
*/
static class RBNode<K extends Comparable<K>,V>{
// 节点是双向的
private RBNode parent;
private RBNode left;
private RBNode right;
private boolean color;
private K key;
private V value;
public RBNode() {
}
public RBNode(RBNode parent, RBNode left, RBNode right, boolean color, K key, V value) {
this.parent = parent;
this.left = left;
this.right = right;
this.color = color;
this.key = key;
this.value = value;
}
public RBNode getParent() {
return parent;
}
public void setParent(RBNode parent) {
this.parent = parent;
}
public RBNode getLeft() {
return left;
}
public void setLeft(RBNode left) {
this.left = left;
}
public RBNode getRight() {
return right;
}
public void setRight(RBNode right) {
this.right = right;
}
public boolean isColor() {
return color;
}
public void setColor(boolean color) {
this.color = color;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
}
左旋代码实现
/**
* 围绕p左旋
* p pr(r)
* / | / \
* pl pr(r) => p rr
* / \ / \
* rl rr pl rl
*
* 左旋的时候
* p-pl 和 pr-rr的关系不变
* pr-rl 要变为 p-rl
* 也就是 rl要变为 p的右子节点
* 同时 p要成为 rl 的父节点
* 还有就是要判断 p 是否有父节点
* 如果没有
* r 变为 root 节点
* 如果有
* r.parent = p.parent
* 还要设置 r为 p.parent 的子节点(可能左也可能右)
* 如果 p.parent.left == p
* p.parent.left = r;
* 否则
* p.parent.right = r;
* 最后
* p.parent = r;
* r.left = p;
* @param p
*/
private void leftRotate(RBNode p){
if(p != null){
RBNode r = p.right;
// 1.设置 pr-rl 要变为 p-rl
// 把rl设置到p的右子节点
p.right = r.left;
if(r.left != null){
// 设置rl的父节点为p
r.left.parent = p;
}
// 2.判断p的父节点情况
r.parent = p.parent; // 不管 p是否有父节点,都把这个父节点设置为 r的父节点
if(p.parent == null){
root = r; // p没有父节点 则r为root节点
}else if(p.parent.left == p){
p.parent.left = r; // 如果p为 p.parent的左子节点 则 r 也为 p.parent的左子节点
}else{
p.parent.right = r; // 反之设置 r 为 p.parent的右子节点
}
// 最后 设置 p 为 r 的左子节点
r.left = p;
p.parent = r;
}
}
右旋实现:
/**
* 围绕p右旋
* @param p
*/
public void rightRotate(RBNode p){
if(p != null){
RBNode r = p.left;
p.left = r.right;
if(r.right != null){
r.right.parent = p;
}
r.parent = p.parent;
if(p.parent == null){
root = r;
}else if(p.parent.left == p){
p.parent.left = r;
}else{
p.parent.right = r;
}
r.right = p;
p.parent = r;
}
}
2-3-4树中结点添加需要遵守以下规则:
而将这些规则对应到红黑树里,就是:
公式:红黑树+新增一个节点(红色)**=**对等的2-3-4树+新增一个节点
新增节点案例
我们通过新增2-3-4树的过程来映射对应的红黑树的节点新增
1.新增一个节点,2 节点
2.新增一个节点,与2节点合并,直接合并
3.新增一个节点,与3节点合并,直接合并
插入的值的位置会有3种情况
对应的红黑树为:
4.新增一个节点,与4节点合并,此时需要分裂
插入值的位置可能是
对应的红黑树的结构为:
新增代码实现
红黑树的新增规则我们理清楚了,接下来就可以通过Java代码来具体的实现了。
先实现插入节点,这就是一个普通的二叉树的插入
/**
* 新增节点
* @param key
* @param value
*/
public void put(K key , V value){
RBNode t = this.root;
if(t == null){
// 说明之前没有元素,现在插入的元素是第一个
root = new RBNode<>(key , value == null ? key : value,null);
return ;
}
int cmp ;
// 寻找插入位置
// 定义一个双亲指针
RBNode parent;
if(key == null){
throw new NullPointerException();
}
// 沿着跟节点找插入位置
do{
parent = t;
cmp = key.compareTo((K)t.key);
if(cmp < 0){
// 左侧找
t = t.left;
}else if(cmp > 0){
// 右侧找
t = t.right;
}else{
// 插入节点的值==比较的节点。值替换
t.setValue(value==null?key:value);
return;
}
}while (t != null);
// 找到了插入的位置 parent指向 t 的父节点 t为null
// 创建要插入的节点
RBNode<K, Object> e = new RBNode<>(key, value == null ? key : value, parent);
// 然后判断要插入的位置 是 parent的 左侧还是右侧
if(cmp < 0){
parent.left = e;
}else{
parent.right = e;
}
// 调整 变色 旋转
fixAfterPut(e);
}
然后再根据红黑树的特点来实现调整(旋转,变色)
private boolean colorOf(RBNode node){
return node == null ? BLACK:node.color;
}
private RBNode parentOf(RBNode node){
return node != null ? node.parent:null;
}
private RBNode leftOf(RBNode node){
return node != null ? node.left:null;
}
private RBNode rightOf(RBNode node){
return node != null ? node.right:null;
}
private void setColor(RBNode node ,boolean color){
if(node != null){
node.setColor(color);
}
}
/**
* 插入节点后的调整处理
* 1. 2-3-4树 新增元素 2节点添加一个元素将变为3节点 直接合并,节点中有两个元素
* 红黑树:新增一个红色节点,这个红色节点会添加在黑色节点下(2节点) --- 这种情况不需要调整
* 2. 2-3-4树 新增元素 3节点添加一个元素变为4节点合并 节点中有3个元素
* 这里有6中情况,( 根左左 根左右 根右右 根右左)这四种要调整 (左中右的两种)不需要调整
* 红黑树:新增红色节点 会添加到 上黑下红的节点中 = 排序后中间节点是黑色,两边节点是红色
*
* 3. 2-3-4树:新增一个元素 4节点添加一个元素需要裂变:中间元素升级为父节点,新增元素与剩下的其中一个合并
* 红黑树:新增节点是红色+爷爷节点是黑色,父亲节点和叔叔节点为红色 调整为
* 爷爷节点变红色,父亲和叔叔节点变为黑色,如果爷爷节点为root节点则调整为黑色
* @param x
*/
private void fixAfterPut(RBNode<K, Object> x) {
x.color = RED;
// 本质上就是父节点是黑色的就不需要调整,对应的 2 3的情况
while(x != null && x != root && x.parent.color == RED){
// 1. x 的父节点是爷爷的 左孩子
if(parentOf(x) == parentOf(parentOf(x)).left){
// 获取当前节点的叔叔节点
RBNode y = rightOf(parentOf(parentOf(x)));
// 情况3
if(colorOf(y) == RED){
// 说明是 上3的情况 变色处理
// 父亲节点和叔叔节点设置为黑色
setColor(parentOf(x),BLACK);
setColor(y,BLACK);
// 爷爷节点设置为 红色
setColor(parentOf(parentOf(x)),RED);
// 递归处理
x = parentOf(parentOf(x));
}else{
// 情况 2
if(x == parentOf(x).right){
// 如果x是父节点的右节点那么我们需要先根据 父节点 左旋
x = parentOf(x);
leftRotate(x);
}
// 叔叔节点为空 对应于 上面的情况2
// 将父节点变为黑色
setColor(parentOf(x),BLACK);
// 将爷爷节点变为红色
setColor(parentOf(parentOf(x)),RED);
// 右旋转 根据爷爷节点右旋转
rightRotate(parentOf(parentOf(x)));
}
}else{
// x 的父节点是爷爷是右孩子
// 获取父亲的叔叔节点
RBNode y = leftOf(parentOf(parentOf(x)));
if(colorOf(y) == RED){
// 情况3
setColor(parentOf(x),BLACK);
setColor(y,BLACK);
setColor(parentOf(parentOf(x)),RED);
x = parentOf(parentOf(x));
}else{
// 情况2
if( x == parentOf(x).left){
x = parentOf(x);
rightRotate(x);
}
setColor(parentOf(x),BLACK);
setColor(parentOf(parentOf(x)),RED);
leftRotate(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}
红黑树的删除操作:
红黑树的节点的删除其实也分为两步:
要删除这个节点先需要找到这个节点,找到节点就是普通的二分查找,具体代码如下
private RBNode getNode(K key){
RBNode node = this.root;
while (node != null ){
int cmp = key.compareTo((K) node.key);
if(cmp < 0){
// 在左子树
node = node.left;
}else if(cmp >0){
// 右子树
node = node.right;
}else{
return node;
}
}
return null;
}
在整理红黑树节点的删除操作时我们需要先理解清楚红黑树删除和2-3-4树删除的等价关系,这样理解起来才会比较容易
核心理论:红黑树删除操作的本质其实就是删除2-3-4树的叶子节点
情况一
情况2:删除的是非情况1的节点,根据我们前面介绍的删除的规则,会找到对应的前驱和后继节点,那么最终删除的还是叶子节点
首先删除节点的代码为:
/**
* 删除节点
* @param key
* @return
*/
public V remove(K key){
// 先找到这个节点
RBNode node = getNode(key);
if(node == null){
return null;
}
// 把值存起来 删除后 返回
V oldValue = (V) node.value;
deleteNode(node);
return oldValue;
}
/**
* 删除节点
* 3种情况
* 1.删除叶子节点,直接删除
* 2.删除的节点有一个子节点,那么用子节点来替代
* 3.如果删除的节点右两个子节点,此时需要找到前驱节点或者后继节点来替代
* 可以转换为 1、2的情况
* @param node
*/
private void deleteNode(RBNode node){
// 3.node节点有两个子节点
if(node.left !=null && node.right != null){
// 找到要删除节点的后继节点
RBNode successor = successor(node);
// 然后用后继节点的信息覆盖掉 要删除节点的信息
node.key = successor.key;
node.value = successor.value;
// 然后我们要删除的节点就变为了 后继节点
node = successor;
}
// 2.删除有一个子节点的情况
RBNode replacement = node.left != null ? node.left : node.right;
if(replacement != null){
// 替代者的父指针指向原来 node 的父节点
replacement.parent = node.parent;
if(node.parent == null){
// 说明 node 是root节点
root = replacement;
}else if(node == node.parent.left){
// 双向绑定
node.parent.left = replacement;
}else{
node.parent.right = replacement;
}
// 将node的左右孩子指针和父指针都指向null node等待GC
node.left = node.right = node.parent = null;
// 替换完成后需要调整平衡
if(node.color == BLACK){
// fixAfterRemove(replacement)
}
}else if(node.parent == null){
// 说明要删除的是root节点
root = null;
}else{
// 1. node节点是叶子节点 replacement为null
// 先调整
if(node.color == BLACK){
// fixAfterRemove(node)
}
// 再删除
if(node.parent != null){
if(node == node.parent.left){
node.parent.left = null;
}else{
node.parent.right = null;
}
node = null;
}
}
}
然后就是需要调整红黑树的平衡了。
删除后的平衡调整
1.情况一:自己能搞定的,对应叶子节点是3节点和4节点
2.情况二:自己搞不定,需要兄弟借,但是兄弟不借,找父亲借,父亲下来,然后兄弟找一个人去代替父亲当家
这种情况就是兄弟节点是3节点或者4节点
找兄弟节点
如果找到的兄弟节点是红色其实还要调整
执行如下调整先,先变色,然后左旋
找兄弟节点借
然后沿着7节点左旋
3.情况三:跟兄弟借,兄弟也没有(情同手足,同时自损)
兄弟节点是2节点,同时当前节点的父节点是红色节点的情况
删除后直接变色就可以了
兄弟节点是2节点,同时当前节点的父节点是黑色节点
变更操作为如下,如果继续有父节点那么还要递归处理
分析清楚了删除的3中情况,我们就可以撸处删除的调整的代码了
/**
* 2-3-4树删除操作:
* 1.情况一:自己能搞定的,对应叶子节点是3节点和4节点
* 2.情况二:自己搞不定,需要兄弟借,但是兄弟不借,找父亲借,父亲下来,然后兄弟找一个人去代替父亲当家
* 3.情况三:跟兄弟借,兄弟也没有
* @param x
*/
private void fixAfterRemove(RBNode x){
// 情况2、3
while(x != root && colorOf(x) == BLACK){
// 这种情况才需要调整
// x 是左孩子的情况
if(x == leftOf(parentOf(x))){
// 找兄弟节点
RBNode rNode = rightOf(parentOf(x));
// 判断此时的兄弟节点是否是真正的兄弟节点 兄弟是红色的情况要调整
if(colorOf(rNode) == RED){ // 2-3-4树的 3节点 交换颜色,然后左旋一次就可以了
setColor(rNode,BLACK);
setColor(parentOf(x),RED);
leftRotate(parentOf(x)); // 左旋一次
rNode = rightOf(parentOf(x)); // 找到真正的兄弟节点
}
// 情况3 找兄弟借 没得借
if(colorOf(leftOf(rNode)) == BLACK && colorOf(rightOf(rNode)) == BLACK){
// 情况复杂
setColor(rNode,RED);
x=parentOf(x); // 向上递归
}else{
// 情况2 找兄弟借 有借
// 兄弟节点是 3节点或者4节点
if(colorOf(rightOf(rNode)) == BLACK){
// 右孩子为空,则左孩子肯定不为空
// 兄弟节点 先要左一次右旋
setColor(rNode,RED);
setColor(leftOf(rNode),BLACK);
rightRotate(rNode);
// 重新调整叔叔节点的位置
rNode = rightOf(parentOf(x));
}
// 变色 兄弟节点是 3节点还是4节点 都旋转一次
setColor(rNode, colorOf(parentOf(x)));
setColor(parentOf(x),BLACK);
setColor(rightOf(rNode),BLACK);
// 左旋
leftRotate(parentOf(x));
x = root; // 结束循环 递归 针对的是 情况3
}
}else{
// 找兄弟节点
RBNode rNode = leftOf(parentOf(x));
// 判断此时的兄弟节点是否是真正的兄弟节点 兄弟是红色的情况要调整
if(colorOf(rNode) == RED){ // 2-3-4树的 3节点 交换颜色,然后左旋一次就可以了
setColor(rNode,BLACK);
setColor(parentOf(x),RED);
rightRotate(parentOf(x)); // 左旋一次
rNode = leftOf(parentOf(x)); // 找到真正的兄弟节点
}
// 情况3 找兄弟借 没得借
if(colorOf(rightOf(rNode)) == BLACK && colorOf(leftOf(rNode)) == BLACK){
// 情况复杂
setColor(rNode,RED);
x=parentOf(x); // 向上递归
}else{
// 情况2 找兄弟借 有借
// 兄弟节点是 3节点或者4节点
if(colorOf(leftOf(rNode)) == BLACK){
// 右孩子为空,则左孩子肯定不为空
// 兄弟节点 先要左一次右旋
setColor(rNode,RED);
setColor(leftOf(rNode),BLACK);
leftRotate(rNode);
// 重新调整叔叔节点的位置
rNode = leftOf(parentOf(x));
}
// 变色 兄弟节点是 3节点还是4节点 都旋转一次
setColor(rNode, colorOf(parentOf(x)));
setColor(parentOf(x),BLACK);
setColor(leftOf(rNode),BLACK);
// 左旋
rightRotate(parentOf(x));
x = root; // 结束循环 递归 针对的是 情况3
}
}
}
// 情况1:替代节点是红色,直接染黑 在情况3的情况下 补偿删除的黑色节点,这样红黑树依然保存平衡
setColor(x,BLACK);
}
好了。红黑树的内容大家应该搞清楚了。同时TreeMap的代码大家也搞清楚了。你可以看看TreeMap的代码其实和我们上面写的代码是一样的
B 树
(Balanced Tree)这个就是我们的多路平衡查找树,叫做B-Tree(B代表平衡)。跟AVL树一样,B树在枝节点和叶子节点存储键值、数据地址、节点引用。
它有一个特点:分叉数(路数)永远比关键字数多1。比如我们画的这棵树,每个节点存储两个关键字,那么就会有三个指针指向三个子节点。
B Tree的查找规则是什么样的呢?
比如我们要在这张表里面查找30。
因为30小于36,走左边。
因为30大于23,走右边。
在磁盘块7里面就找到了30,只用了3次IO。
这个效率会比AVL树的效率更高
加强版多路平衡查找树
因为B Tree的这种特性非常适合用于做索引的数据结构,所以很多文件系统和数据库的索引都是基于B Tree的。
但是实际上,MySQL里面使用的是B Tree的改良版本,叫做B+Tree(加强版多路平衡查找树)。
B+树的存储结构:
MySQL中的B+Tree有几个特点:
总结一下, B+Tree的特点带来的优势:
看完了基本的数据结构后我们还需要看看常用的一些算法。数据结构加算法就等于程序。
用于将一组数据按照特定的规则进行排序。排序算法可以分为内部排序和外部排序两种。
内部排序算法:
外部排序算法:
不同的排序算法在时间复杂度、空间复杂度和稳定性等方面有所差异,选择合适的排序算法取决于具体的应用场景和数据规模。
查找算法,也称为搜索算法,是一种用于在数据集中查找特定元素的算法。查找算法可以应用于各种数据结构,如数组、链表、树等。
常用的查找算法包括:
线性查找:顺序遍历数据集,逐个比较元素,直到找到目标元素或遍历完整个数据集。时间复杂度为O(n),其中n为数据集的大小。
二分查找:仅适用于已经排序的数据集。从数据集的中间元素开始比较,如果目标元素小于中间元素,则在左半部分继续查找;如果目标元素大于中间元素,则在右半部分继续查找;如果目标元素等于中间元素,则找到目标元素。时间复杂度为O(log n)。
哈希查找:通过哈希函数将目标元素映射到一个位置,然后在该位置进行查找。哈希查找的平均时间复杂度为O(1),但是在处理哈希冲突时可能需要线性查找。
二叉查找树:将数据集构建成二叉查找树,其中每个节点的左子树的值小于节点的值,右子树的值大于节点的值。通过比较目标元素和节点的值,可以在二叉查找树中进行快速查找。
平衡二叉查找树:在二叉查找树的基础上,通过旋转操作保持树的平衡,以提高查找效率。常用的平衡二叉查找树有红黑树、AVL树等。
B树和B+树:适用于大规模数据的查找,将数据集分散存储在多个节点中,通过多级索引进行查找。B树和B+树的高度较小,能够减少磁盘I/O操作,提高查找效率。
字符串匹配算法:用于在文本中查找特定的字符串。常用的字符串匹配算法有暴力匹配算法、KMP算法、Boyer-Moore算法等。