0. 概述

  对于普通的搜索树,如果一直插入比第一个元素小的元素,它会退化成一个无限向左下角眼神的单链表,使得时间复杂度退化为O(n)。如果我们在插入时保持树的结构是平衡的,则可以保证查找、插入和删除的时间复杂度有对数级的时间性能,下面讲到的AVL树和红黑树都是平衡搜索树,通过旋转来保持平衡

1. AVL树

1.1 定义

  Adelson-Velskii 和 Landis 在 1962年提出的一种平衡树,保证搜索树的高度是O(logn),这样就可以保证查找、插入和删除的时间为O(logn)

1.2 AVL树的描述

  AVL 树一般用链表描述,为了简化插入和删除操作,为每个节点增加一个平衡因子 bf ,平衡因子 bf(x) 的定义为:x 的左子树的高度 - x 的右子树的高度

  从 AVL 树的定义可以知道,平衡因子 bf 的取值为 -1、0 和 1

1.3 AVL树的搜索

  与普通的搜索树相同,根据 theKey 不断向左孩子或右孩子移动寻找即可,时间复杂度为O(logn)

1.4 AVL树的插入

  首先是区分4种旋转情况的代码,具体在1.4.1-1.4.4部分

template<class K, class V>
bool avlTree<K, V>::insert(K key, V val)
{
// 1.根节点为空,直接插入
if (_root == NULL)
{
_root = new Node<K, V>(key, val);
return true;
}
// 2.根节点不为空
else
{
Node<K, V>* cur = _root;
Node<K, V>* parent = NULL; // 2.1)找到要插入节点的位置
while (cur!=NULL)
{
parent = cur;
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return false; //不允许出现重复元素的节点
} // 2.2)插入新节点
cur = new Node<K, V>(key, val);
if (parent->_key > key)
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
} // 2.3)插入完成后,调整平衡因子
while (parent!=NULL)
{
if (cur == parent->_left)//插入节点在左子树父节点bf++,反之--
parent->_bf++;
else
parent->_bf--; // 2.3.1)插入新节点后,双亲结点高度为0, 说明这个父节点原先已有一个孩子, 这次插入到另一个孩子的位置了, 树整体的高度无变化, 结束
if (parent->_bf == 0)
break;
// 2.3.2)插入节点后双亲节点高度为-1或1, 说明子树高度改变,则继续向上调整
else if (parent->_bf == -1 || parent->_bf == 1)
{
cur = parent;
parent = parent->_parent;
}
// 2.3.3)插入节点后parent->_bf==-2||parent->_bf==2;说明已经不平衡,需要旋转
else
{
if (parent->_bf == 2)
{
if (cur->_bf == 1)
rotateLL(parent); // parent(2), child(1)
else
rotateLR(parent); // parent(2), child(-1) }
else
{
if (cur->_bf == -1)
rotateRR(parent); // parent(-2), child(-1)
else
rotateRL(parent); // parent(-2), child(1)
}
break;
}
}
return true;
}
}

1.4.1 LL型不平衡(单旋转)

  插入前左子树高度比右子树高度高 1,然后在左子树的左侧插入一个新的元素,只需要一次 右单旋 就可以转为平衡搜索树。具体操作如下,根节点A的左孩子B转换为新的根节点,B的右孩子转换为A的左孩子

template <class K, class V>
void avlTree<K, V>::rotateLL(Node<K, V>* parent)
{
Node<K, V>* subL = parent->_left;
Node<K, V>* subLR = subL->_right;
Node<K, V>* ppNode = parent->_parent; // 一共两步, 重新连接节点即可
parent->_left = subLR; // 1.当前 parent 节点的左孩子 改成 其左孩子的右孩子
if (subLR != NULL)
subLR->_parent = parent; subL->_right = parent; // 2.把当前 parent 节点改成 subL 的右孩子
parent->_parent = subL; if (_root == parent) // 判断不平衡的点是不是根节点
{
_root = subL;
subL->_parent = NULL;
}
else
{
if (ppNode->_right == parent)
{
ppNode->_right = subL;
}
else
{
ppNode->_left = subL;
} subL->_parent = ppNode;
} subL->_bf = 0;
parent->_bf = 0;
}

1.4.2 RR型不平衡(单旋转)

  插入前右子树高度比左子树高度高 1,然后在右子树的右侧插入一个新的元素,只需要一次 左单旋 就可以转为平衡搜索树。具体操作如下,根节点A的右孩子B转换为新的根节点,B的左孩子转换为A的右孩子

template <class K, class V>
void avlTree<K, V>::rotateRR(Node<K, V>* parent)
{
Node<K, V>* subR = parent->_right;
Node<K, V>* subRL = subR->_left;
Node<K, V>* pParent = parent->_parent; // 一共两步, 重新连接节点即可
parent->_right = subRL; // 1.当前 parent 节点的右孩子 改成 其右孩子的左孩子
if (subRL != NULL)
subRL->_parent = parent; subR->_left = parent; // 2.把当前 parent 节点改成 subR 的左孩子
parent->_parent = subR; if (parent == _root) // 判断不平衡的点是不是根节点
{
_root = subR;
_root->_parent = NULL;
} else
{
if (pParent->_left = parent)
pParent->_left = subR;
else
pParent->_right = subR; subR->_parent = pParent;
}
parent->_bf = 0;
subR->_bf = 0;
}

1.4.3 LR型不平衡(双旋转)

  左子树高度更高的情况下,在左子树的右侧插入一个节点。首先进行一次 左单旋 ,将它转换为LL型不平衡,然后进行一次 右单旋 转换为平衡搜索树

template <class K, class V>
void avlTree<K, V>::rotateLR(Node<K, V>* parent)
{
Node<K, V>* subL = parent->_left;
Node<K, V>* subLR = subL->_right;
int bf = subLR->_bf; rotateRR(parent->_left);
rotateLL(parent); if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
}

1.4.4 RL型不平衡(双旋转)

  与LR型不平衡类似,这里直接给出代码

template <class K, class V>
void avlTree<K, V>::rotateRL(Node<K, V>* parent)
{
Node<K, V>* subR = parent->_right;
Node<K, V>* subRL = subR->_left;
int bf = subRL->_bf; rotateLL(parent->_right);
rotateRR(parent); if (bf == 1)
{
subR->_bf = 0;
parent->_bf = -1;
subRL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
}

2. 红黑树(red-black tree)

2.1 基本概念

  红黑树是一棵扩充二叉树,每个空指针用一个外部节点来代替,除此之外还有以下性质

  • 根节点和外部节点颜色都是黑色
  • 在根节点到外部节点的路径上,没有连续两个节点是红色
  • 在所有根节点到外部节点的路径上,黑色节点的数目都相同

  红黑树一个节点的阶(rank):从该节点到一外部节点的路径上黑色节点的数量

  红黑树最大的高度是2log2(n+1)

2.2 RBT的搜索

  与普通的搜索树相同,根据 theKey 不断向左孩子或右孩子移动寻找即可,时间复杂度为O(logn)

2.3 RBT的插入

  我们的插入目标实际上是,和普通搜索树一样插入一个元素,然后再让它额外满足红黑树的性质。

2.3.1 情况一:变色处理

  这种情况是最简单的情况,如果插入节点的叔叔节点(父亲的对称孩子)也是红色,则只需要进行变色处理

  • 父亲节点和叔叔节点变为黑色
  • 曾祖父节点变为红色

  循环处理直到根节点为止,最后将根节点变为黑色结束

2.3.2 情况二:单旋加变色处理

  如果新插入节点的叔叔为黑色,并且新插入节点在外侧

  • 进行一次单旋转
  • 把父亲节点更改为黑色,曾祖父节点更改为红色(最后这个三角形是黑色连两个红色)

2.3.3 情况三:双旋加变色处理

  如果新插入节点的叔叔为黑色,并且新插入节点在内测

  • 对父亲节点进行一次单旋转
  • 对曾祖父节点进行一次单旋转
  • 将新插入节点修改为黑色,曾祖父节点修改为红色(最后这个三角形是黑色连两个红色)

2.3.4 RBT插入的实现

2.3.4.1 对外暴露的插入函数

template <class K, class V>
bool RBTree<K, V>::insert(K key, V val)
{
RBTNode<K, V>* z = NULL; if ((z = new RBTNode<K, V>(key, val, RED, NULL, NULL, NULL)) == NULL)
return false; return insert(this->_root, z);
}

2.3.4.2 按照普通的搜索树进行插入操作

template <class K, class V>
bool RBTree<K, V>::insert(RBTNode<K, V>*& root, RBTNode<K, V>* node)
{
// 1.根节点为空,直接插入
if (root == NULL)
{
node->_color = BLACK;
root = node;
return true;
}
// 2.根节点不为空
else
{
RBTNode<K, V>* cur = root;
RBTNode<K, V>* parent = NULL; // 2.1)找到要插入节点的位置
while (cur != NULL)
{
parent = cur;
if (node->_key < cur->_key)
cur = cur->_left;
else if (node->_key > cur->_key)
cur = cur->_right;
else
return false; //不允许出现重复元素的节点
} // 2.2)插入新节点
if (parent->_key > node->_key)
{
parent->_left = node;
node->_parent = parent;
}
else
{
parent->_right = node;
node->_parent = parent;
} return insertFixUp(root, node);
}
}

2.3.4.3 插入完成后的颜色修正

template <class K, class V>
bool RBTree<K, V>::insertFixUp(RBTNode<K, V>*& root, RBTNode<K, V>* node)
{
RBTNode<K, V>* parent, * grandparent, * cur;
cur = node;
parent = cur->_parent; // 若“父节点存在,并且父节点的颜色是红色”
while (parent && rb_is_red(parent))
{
grandparent = parent->_parent; //若“父节点”是“祖父节点的左孩子”
if (parent == grandparent->_left)
{
RBTNode<K, V>* uncle = grandparent->_right; if (uncle && rb_is_red(uncle))
{// 情况1:叔叔节点是红色, 修改后继续检查
rb_set_black(uncle);
rb_set_black(parent);
rb_set_red(grandparent);
cur = grandparent;
parent = cur->_parent;
}
else
{// 情况2: 叔叔节点不存在或者是黑色, 修改后结束循环
if (parent->_left == cur)
{// 情况2.1:叔叔是黑色,且当前节点是左孩子 (单旋+变色)
rightRotate(grandparent);
rb_set_black(parent);
rb_set_red(grandparent);
}
else
{// 情况2.2:叔叔是黑色,且当前节点是右孩子
leftRotate(parent);
rightRotate(grandparent);
rb_set_black(cur);
rb_set_red(grandparent);
}
break;
}
}
else//若“父节点”是“祖父节点的右孩子”
{
RBTNode<K, V>* uncle = grandparent->_left; if (uncle && rb_is_red(uncle))
{
rb_set_black(uncle);
rb_set_black(parent);
rb_set_red(grandparent);
cur = grandparent;
parent = cur->_parent;
}
else
{
if (parent->_right == cur)
{
leftRotate(grandparent);
rb_set_black(parent);
rb_set_red(grandparent);
}
else
{
rightRotate(parent);
leftRotate(grandparent);
rb_set_black(cur);
rb_set_red(grandparent);
}
}
}
}
// 将根节点设为黑色
rb_set_black(root);
return true;
}

【数据结构】7.平衡搜索树(AVL树和红黑树)的更多相关文章

  1. 单例模式,堆,BST,AVL树,红黑树

    单例模式 第一种(懒汉,线程不安全): public class Singleton { private static Singleton instance; private Singleton () ...

  2. B树,B+树,红黑树应用场景AVL树,红黑树,B树,B+树,Trie树

    B B+运用在file system database这类持续存储结构,同样能保持lon(n)的插入与查询,也需要额外的平衡调节.像mysql的数据库定义是可以指定B+ 索引还是hash索引. C++ ...

  3. 对于AVL树和红黑树的理解

    AVL又称(严格)高度平衡的二叉搜索树,也叫二叉查找树.平衡二叉树.window对进程地址空间的管理用到了AVL树. 红黑树是非严格平衡二叉树,统计性能要好于平衡二叉树.广泛的在C++的STL中,ma ...

  4. AVL树与红黑树

    平衡树是平时经常使用数据结构. C++/JAVA中的set与map都是通过红黑树实现的. 通过了解平衡树的实现原理,可以更清楚的理解map和set的使用场景. 下面介绍AVL树和红黑树. 1. AVL ...

  5. AVL树,红黑树,B-B+树,Trie树原理和应用

    前言:本文章来源于我在知乎上回答的一个问题 AVL树,红黑树,B树,B+树,Trie树都分别应用在哪些现实场景中? 看完后您可能会了解到这些数据结构大致的原理及为什么用在这些场景,文章并不涉及具体操作 ...

  6. Mysql为什么使用b+树,而不是b树、AVL树或红黑树?

    首先,我们应该考虑一个问题,数据库在磁盘中是怎样存储的?(答案写在下一篇文章中) b树.b+树.AVL树.红黑树的区别很大.虽然都可以提高搜索性能,但是作用方式不同. 通常文件和数据库都存储在磁盘,如 ...

  7. 论AVL树与红黑树

    首先讲解一下AVL树: 例如,我们要输入这样一串数字,10,9,8,7,15,20这样一串数字来建立AVL树 1,首先输入10,得到一个根结点10 2,然后输入9, 得到10这个根结点一个左孩子结点9 ...

  8. 二叉树,AVL树和红黑树

    为了接下来能更好的学习TreeMap和TreeSet,讲解一下二叉树,AVL树和红黑树. 1. 二叉查找树 2. AVL树 2.1. 树旋转 2.1.1. 左旋和右旋 2.1.2. 左左,右右,左右, ...

  9. [BinaryTree] AVL树、红黑树、B/B+树和Trie树的比较

    转自:AVL树.红黑树.B/B+树和Trie树的比较 AVL树 最早的平衡二叉树之一.AVL是一种高度平衡的二叉树,所以通常的结果是,维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应 ...

  10. AVL树,红黑树,B树,B+树,Trie树都分别应用在哪些现实场景中?

    AVL树: 最早的平衡二叉树之一.应用相对其他数据结构比较少.windows对进程地址空间的管理用到了AVL树. 红黑树: 平衡二叉树,广泛用在C++的STL中.如map和set都是用红黑树实现的. ...

随机推荐

  1. css实现居中对齐的几种方式

    一般来说居中的话可分为水平居中与垂直居中,以下是我个人总结的几种方式 1.水平居中: inline元素:text-algin:center实现 block元素:margin:auto absolute ...

  2. JumpServer介绍及v4版本单机部署

    概述 JumpServer官网:https://www.jumpserver.org/ JumpServer官网文档:https://docs.jumpserver.org/zh/v4/ GitHub ...

  3. Mac Catalina关闭系统更新提示

    catalina每隔一段时间就会提示更新,系统更新图标上会显示红色的更新提示,有没有觉得很烦? 如果有那就如下操作: 1.打开系统设置->软件更新 2.点击右下角高级 3.取消所有的勾选(这一步 ...

  4. mybatis下的ResultMap配置一对一以及一对多

    一对一: 在数据库里面有这样的一个主外键关系的表: 我需要查找身份证的号码就要知道这个人的姓名(通过一个SQL语句要查到两个实体类里面的信息): SELECT c.*,p.* FROM idcard ...

  5. 指标+AI+BI:构建数据分析新范式丨2024袋鼠云秋季发布会回顾

    10月30日,袋鼠云成功举办了以"AI驱动,数智未来"为主题的2024年秋季发布会.大会深度探讨了如何凭借 AI 实现新的飞跃,重塑企业的经营管理方式,加速数智化进程. 作为大会的 ...

  6. 数栈大数据组件:Hive优化之配置参数的优化

    Hive是大数据领域常用的组件之一,主要用于大数据离线数仓的运算,关于Hive的性能调优在日常工作和面试中是经常涉及的一个点,因此掌握一些Hive调优是必不可少的一项技能.影响Hive效率的主要因素有 ...

  7. Ding!您有一份ChunJun实用指南,请查收

    ChunJun是易用.稳定.高效的批流一体的数据集成框架,主要应用于大数据开发平台的数据同步/数据集成模块,使大数据开发人员可简洁.快速的完成数据同步任务开发,供企业数据业务使用. 本文主要整理Chu ...

  8. 前端开发系列124-进阶篇之html-parser

    本文简单研究 html标签的编译过程,模板的编译是前端主流框架中的基础部分,搞清楚这块内容对于理解框架的工作原理.`virtual-DOM` 有诸多益处 ,因限于篇幅所以本文将仅仅探讨把 html 字 ...

  9. 前端开发系列086-Node篇之require

    本文主要介绍require对象(函数)的结构,使用方法和注意点,对模块和CommanJS规范等内容不进行展开. 一.require函数 在Node中,所有的文件都被认为是一个模块.根据来源的不同,我们 ...

  10. 前端开发系列024-基础篇之Canvas绘图(基础)

    本文将对Canvas绘图技术进行简单介绍,主要包括Canvas标签.CanvasRenderingContext2D对象核心API的使用以及复杂图形的绘制等内容. 一.Canvas简单介绍 基本信息 ...