文章目录
在网络上流传着这样一张图片:
这张图片表达的侧面意思是:红黑树非常难!!!但如果认真阅读了这篇的博客,并且你有 AVL 树的基础的话 (重点是 AVL 树的旋转),其实你会发现,红黑树难只是指红黑树比较抽象,但它的逻辑其实是比 AVL 树要简单的,并且红黑树的代码也不难写。
什么是红黑树
红黑树是一种平衡二叉搜索树,但和 AVL 树使用高度来控制平衡不同,红黑树在每个结点上增加一个存储位来表示结点的颜色,可以是Red或Black,然后通过对任何一条从根到叶子的路径上各个结点着色方式的限制来达到接近平衡;
红黑树通过对每个节点颜色的限制,从而使得整棵树的最长路径不超过最短路径的两倍 (注意是整棵树,对子树没有要求),这样红黑树就可以达到接近平衡。
注意:因为红黑树只要求整棵树中最长路径是最短路径的两倍,所以红黑树是近似平衡的,即子树中某一路径可能比另一条路径长两倍不止;但 AVL 是通过高度控制平衡的,且 AVL 能达到的平衡已经是二叉树中能够达到的最好平衡了,所以 AVL 树是绝对平衡的。
红黑树的性质
红黑树有如下性质:

特别注意第五点中的叶子节点并不是指我们平时说的叶子节点,而是指的空节点 (图中的NIL节点),它之所以要加这么一条规则是为了更好的标识每条路径 (比如认为图中的13 8 1 NIL 也是一条路径);但是我认为我们完全可以不用管第五点,而且这样也不会造成什么影响,因为如果不计算 NIL 节点,那么树中所有路径的黑色节点数量都会减一,并不会违反规则4,所以可以看到第五点我是用删除线进行修饰的。
思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?
我们可以在极端场景下来思考这个问题 – 极端场景下,当一条路径中的全部节点都为黑色时该路径为最短路径,当一条路径中黑色节点与红色节点交替时该路径为最长路径,此时最长路径刚好为最短路径的两倍;如下:
红黑树的节点结构和 AVL 树整体上类似,三个节点指针分别指向左孩子、右孩子和父亲,然后还包含一个 pair 键值对,和 AVL 树不同的时,红黑树不再需要 bf 变量来作为树的平衡因子,而是需要一个 col 变量来标识节点的颜色:
//节点颜色
enum Color {
RED,
BLACK
};
template<class K, class V>
struct RBTreeNode {
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
std::pair<K, V> _kv;
Color _col; //节点颜色
RBTreeNode(const std::pair<K, V>& kv)
: _kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(RED) //默认给成红色
{}
};
可以看到,在构造函数的初始化列表中,我们将节点的颜色默认初始化为 RED,这是因为新增节点的颜色默认给成红色更合适,原因如下:
红黑树插入的前面部分很简单,就是一般二叉搜索树的插入逻辑,需要注意的是如果插入的节点是根节点,则我们需要将节点的颜色改为黑色,因为新增节点默认是被初始化为红色的;
红黑树插入的难点在于检测插入后红黑树的性质是否被破坏,其一共可以分为两种情况:
bool insert(const std::pair<K, V>& kv) {
//判断根节点是否为空
if (_root == nullptr) {
_root = new Node(kv);
_root->_col = BLACK; //根节点的颜色一定为黑
return true;
}
//寻找插入位置
Node* parent = nullptr;
Node* cur = _root;
while (cur) {
if (kv.first < cur->_kv.first) {
parent = cur;
cur = cur->_left;
}
else if (kv.first > cur->_kv.first) {
parent = cur;
cur = cur->_right;
}
else {
return false; //不允许重复节点
}
}
//走到空开始插入,注意这里还需要修改父节点的指向
//新增节点的颜色为默认被初始化为红色,所以这里不需要再显式赋值
Node* newNode = new Node(kv);
if (kv.first < parent->_kv.first)
parent->_left = newNode;
else
parent->_right = newNode;
newNode->_parent = parent; //修改父节点指向
//如果父节点颜色为红色,则需要进行调整
//红黑树的调整
}
而红黑树的调整又分为两种情况:(约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点)
下面我们来具体探讨这两种情况。
如果cur为红,p为红,g为黑,u存在且为红,此时我们只需要将p和u变黑,再将g变红即可 (约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点):
可以看到,当p为红时,由于g是p的父亲,所以g一定为黑,如果此时叔叔存在且为红,那么我们只需要将p和u变黑,再将g变红即可;这样做不会改变g这棵子树黑色节点的数量,所以不会影响到其他路径,同时g的子树中每条路径的黑色节点数量也是相同的,符合红黑树的性质。
节点颜色修改完毕之后,这里又分为两种情况:

另外,和 AVL 树中旋转的图一样,这里给出的也是抽象图,其中 a/b/c/d/e 代表的是高度为 h 的红黑树子树,h 可以取任何非负整数,所以这张图代表的是一类情况,我们以 h == 1 为例 (图中给出的组合数并不一定是准确的):
注意:上面我们是以左左为例子进行讨论的,但其实上面的处理方式也适用于 右右、左右、右左 的情况 (注意:这里的左左、右右、左右、右左和上一节 AVL 树里面的其实是差不多的,只是红黑树不通过平衡因子来控制平衡,所以进行 if 条件需要进行相应的调整而已);
也就是说,只要叔叔存在且为红,那么无论是插入节点的父亲在祖父节点的左边还是右边,插入节点在父亲的左边还是右边都没有关系,统一将父节点和叔叔节点变黑,将祖父节点变红。
之所以将叔叔不存在与叔叔存在且为黑分为一类情况是因为它们的处理方式是相同的 (约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点):
当u不存在时,cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p中一定有一个节点的颜色是黑色,否则不满足性质4 – 每条路径黑色节点个数相同;此时我们的操作是旋转 + 变色,其中旋转会根据父节点位置和插入节点位置的不同分为四种情况,而变色是统一将旋转后子树的根节点变为黑色,将根的左右节点变为红色。
如果u存在且为黑,那么cur节点原来的颜色一定是黑色的,现在看到其是红色是因为cur的子树之前为情况1,然后情况1调整后将祖父节点也就是cur节点变成了红色,否则g左子树的黑色节点数量会比右子树黑色节点数量少1,违反性质4;此时我们的操作和u不存在时一模一样 – 旋转 + 变色,旋转分为四种情况,变色统一将旋转后子树的根节点变为黑色,将根的左右节点变为红色;所以说我们可以将u不存在和u存在且为黑分同一类 (本质是我们不会改变u及其子树的颜色)。

红黑树的旋转 – 和 AVL 树一样,红黑树会根据父节点位置的不同和插入节点位置的不同选择不同的旋转方式来进行调整,旋转一共分为四类:
可以看到,红黑树的旋转其实就是 AVL 树中的四种旋转,只不过红黑树中不再需要更新平衡因子,而是需要更新节点颜色而已;不过红黑树中叔叔不存在或存在且为黑情况下节点颜色的更新十分简单 – 统一将旋转后子树的根节点变为黑色,将根的左右节点变为红色即可。
需要注意的是,和情况1 – 叔叔存在且为红不同,由于这里我们将祖父节点的颜色改为黑色 (旋转后子树的根节点为祖父节点),所以不用考虑祖父的父节点为红色从而违反性质4的问题,即 这里我们不需要继续向上调整。
最后,由于左单旋、右单旋、左右双旋和右左双旋这四种旋转我们在上一节 AVL 树中已经讲解的十分清楚,所以这里我们不再重复讲解,而仅仅是给出左右双旋和右左双旋两种情况的例图 (由于右单旋的例图在上面我们已经给出,所以下面只给出双旋的例图),如果看不懂例图或对旋转的细节有疑问的同学可以再回顾一下上一节;
左右双旋的情况如下:
右左双旋的情况如下:
旋转的代码如下:
bool insert(const std::pair<K, V>& kv) {
//判断根节点是否为空
if (_root == nullptr) {
_root = new Node(kv);
_root->_col = BLACK; //根节点的颜色一定为黑
return true;
}
//寻找插入位置
Node* parent = nullptr;
Node* cur = _root;
while (cur) {
if (kv.first < cur->_kv.first) {
parent = cur;
cur = cur->_left;
}
else if (kv.first > cur->_kv.first) {
parent = cur;
cur = cur->_right;
}
else {
return false; //不允许重复节点
}
}
//走到空开始插入,注意这里还需要修改父节点的指向
//新增节点的颜色为默认被初始化为红色,所以这里不需要再显式赋值
Node* newNode = new Node(kv);
if (kv.first < parent->_kv.first)
parent->_left = newNode;
else
parent->_right = newNode;
newNode->_parent = parent; //修改父节点指向
cur = newNode;
//如果父节点颜色为红色,则需要进行调整,且最多调整到根
while (parent && parent->_col == RED) {
Node* grandfater = parent->_parent; //祖父节点
if (grandfater == nullptr)
break;
//如果父节点在祖父节点的左边
if (parent == grandfater->_left) {
Node* uncle = grandfater->_right; //叔叔节点
//情况一:叔叔存在且为红--父和叔叔变黑,爷爷变红,继续向上调整
if (uncle && uncle->_col == RED) {
parent->_col = BLACK;
uncle->_col = BLACK;
grandfater->_col = RED;
//继续向上调整
cur = grandfater;
parent = cur->_parent;
}
//情况二:叔叔不存在或叔叔存在且为黑
else {
//如果cur在parent的左边,则右单旋--注意走到这里p已经是g的左边了
if (cur == parent->_left) {
rotateR(grandfater);
//更新节点颜色
parent->_col = BLACK;
cur->_col = grandfater->_col = RED;
}
else { //左右双旋
//先以parent为轴进行左单旋
rotateL(parent);
//再以grandfather进行右单旋
rotateR(grandfater);
//更新节点颜色--此时cur为子树的根节点
cur->_col = BLACK;
parent->_col = grandfater->_col = RED;
}
//最后需要跳出循环,因为此时不会再影响上层
break;
}
}
//如果父节点在祖父节点的右边
else {
Node* uncle = grandfater->_left; //叔叔节点
//情况一:叔叔存在且为红--父和叔叔变黑,爷爷变红,继续向上调整
if (uncle && uncle->_col == RED) {
parent->_col = BLACK;
uncle->_col = BLACK;
grandfater->_col = RED;
//继续向上调整
cur = grandfater;
parent = cur->_parent;
}
//情况二:叔叔不存在或叔叔存在且为黑
else {
//如果cur在parent的右边,则左单旋--注意走到这里p已经是g的右边了
if (cur == parent->_right) {
rotateL(grandfater);
//更新节点颜色
parent->_col = BLACK;
cur->_col = grandfater->_col = RED;
}
else { //右左双旋
//先以parent为轴进行右单旋
rotateR(parent);
//再以grandfather进行左单旋
rotateL(grandfater);
//更新节点颜色--此时cur为子树的根节点
cur->_col = BLACK;
parent->_col = grandfater->_col = RED;
}
//最后需要跳出循环,因为此时不会再影响上层
break;
}
}
}
//在循环外面将_root的颜色重新置黑,避免循环内部将根节点的颜色变红了
_root->_col = BLACK;
return true;
}
红黑树节点的调整其实很简单,一共就两种情况:(注意新增节点一定要初始化为红色)
简单来说,红黑树如何调整取决于叔叔节点。
红黑树的验证和 AVL 树一样,验证一共分为两部分:
验证二叉搜索树很简单,我们向树中插入数据,然后实现一个 InOrder 函数看遍历得到的数据是否有序即可。
//中序遍历
void InOrder() {
return _inOrder(_root);
}
void _inOrder(Node* root) {
if (root == nullptr)
return;
_inOrder(root->_left);
std::cout << root->_kv.first >> root->_kv.second >> std::endl;
_inOrder(root->_right);
}

验证平衡则比较麻烦,因为我们需要分别验证红黑树的几个性质 – 根节点为黑色,不允许出现连续的红色,每条路径黑色节点的数量是红色节点数量的两倍;同时,在验证的过程中我们可以顺便将不符合要求的地方的节点的key值打印出来,方便发生错误时进行调试
bool Check(Node* root, int blackCount, int baseCount) {
//如果走到空,说明这条路径结束了,此时看count与baseCount是否相等
if (root == nullptr) {
if (blackCount != baseCount) {
std::cout << "此路径黑色节点数量有误,节点key值:" << root->_kv.first << std::endl;
return false;
}
return true;
}
//检查是否出现连续红色节点
if (root->_col == RED) {
if (root->_parent->_col == RED) { //红色节点一定有父亲,因为根节点为黑色
std::cout << "出现连续红色节点,节点key值:" << root->_kv.first << std::endl;
}
}
if (root->_col == BLACK)
blackCount++;
return Check(root->_left, blackCount, baseCount) && Check(root->_right, blackCount, baseCount);
}
//验证红黑树的性质
bool IsBanlance() {
if (_root == nullptr)
return true;
//检查根节点
if (_root->_col == RED) {
std::cout << "根节点为红" << std::endl;
return false;
}
//以最左路径黑色节点数量为基准,同其他路径的黑色节点数量相比较,看是否相等
int baseCount = 0;
Node* cur = _root;
while (cur) {
if (cur->_col == BLACK)
baseCount++;
cur = cur->_left;
}
//检查红黑树的其他性质
return Check(_root, 0, baseCount);
}


如果我们用几千上万甚至几十上百万个随机数测试出来的结果都是真,那么我们的 红黑树 就基本上没问题了,当然前提是你的 IsBanlance 函数没写错。
和 AVL 树一样,红黑树的删除我们作为了解内容即可,如果对其特别感兴趣的同学可以参考一下《算法导论》或者《STL源码剖析》,也可以看看下面这位大佬的博客:红黑树 - Never - 博客园 (cnblogs.com)
红黑树和 AVL 树都是高效的平衡二叉树,增删改查的时间复杂度都是 O(logN),但由于红黑树不追求绝对平衡,只需要保证最长路径不超过最短路径的2倍,所以在某些极端情况下红黑树的查询效率相较于 AVL 树要低一点点;例如当树左子树全部为黑色节点,右子树全部为一黑一红交替时,红黑树的高度差不多是 AVL 树的两倍,但是此时红黑树的查询效率仍然属于 O(logN) 这个量级;
不过也正是由于红黑树不要求左右子树绝对平衡,所以红黑树相较于 AVL 树在插入和删除时的旋转次数要少一些,所以在经常进行增删的结构中红黑树的性能比 AVL 树更优,而且红黑树实现比较简单,所以在实际业务中一般都使用红黑树,而不使用 AVL 树。
RBTree.h:
#pragma once
//节点颜色
enum Color {
RED,
BLACK
};
template<class K, class V>
struct RBTreeNode {
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
std::pair<K, V> _kv;
Color _col; //节点颜色
RBTreeNode(const std::pair<K, V>& kv)
: _kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(RED) //默认给成红色
{}
};
template<class K, class V>
class RBTree {
typedef RBTreeNode<K, V> Node;
public:
bool insert(const std::pair<K, V>& kv) {
//判断根节点是否为空
if (_root == nullptr) {
_root = new Node(kv);
_root->_col = BLACK; //根节点的颜色一定为黑
return true;
}
//寻找插入位置
Node* parent = nullptr;
Node* cur = _root;
while (cur) {
if (kv.first < cur->_kv.first) {
parent = cur;
cur = cur->_left;
}
else if (kv.first > cur->_kv.first) {
parent = cur;
cur = cur->_right;
}
else {
return false; //不允许重复节点
}
}
//走到空开始插入,注意这里还需要修改父节点的指向
//新增节点的颜色为默认被初始化为红色,所以这里不需要再显式赋值
Node* newNode = new Node(kv);
if (kv.first < parent->_kv.first)
parent->_left = newNode;
else
parent->_right = newNode;
newNode->_parent = parent; //修改父节点指向
cur = newNode;
//如果父节点颜色为红色,则需要进行调整,且最多调整到根
while (parent && parent->_col == RED) {
Node* grandfater = parent->_parent; //祖父节点
if (grandfater == nullptr)
break;
//如果父节点在祖父节点的左边
if (parent == grandfater->_left) {
Node* uncle = grandfater->_right; //叔叔节点
//情况一:叔叔存在且为红--父和叔叔变黑,爷爷变红,继续向上调整
if (uncle && uncle->_col == RED) {
parent->_col = BLACK;
uncle->_col = BLACK;
grandfater->_col = RED;
//继续向上调整
cur = grandfater;
parent = cur->_parent;
}
//情况二:叔叔不存在或叔叔存在且为黑
else {
//如果cur在parent的左边,则右单旋--注意走到这里p已经是g的左边了
if (cur == parent->_left) {
rotateR(grandfater);
//更新节点颜色
parent->_col = BLACK;
cur->_col = grandfater->_col = RED;
}
else { //左右双旋
//先以parent为轴进行左单旋
rotateL(parent);
//再以grandfather进行右单旋
rotateR(grandfater);
//更新节点颜色--此时cur为子树的根节点
cur->_col = BLACK;
parent->_col = grandfater->_col = RED;
}
//最后需要跳出循环,因为此时不会再影响上层
break;
}
}
//如果父节点在祖父节点的右边
else {
Node* uncle = grandfater->_left; //叔叔节点
//情况一:叔叔存在且为红--父和叔叔变黑,爷爷变红,继续向上调整
if (uncle && uncle->_col == RED) {
parent->_col = BLACK;
uncle->_col = BLACK;
grandfater->_col = RED;
//继续向上调整
cur = grandfater;
parent = cur->_parent;
}
//情况二:叔叔不存在或叔叔存在且为黑
else {
//如果cur在parent的右边,则左单旋--注意走到这里p已经是g的右边了
if (cur == parent->_right) {
rotateL(grandfater);
//更新节点颜色
parent->_col = BLACK;
cur->_col = grandfater->_col = RED;
}
else { //右左双旋
//先以parent为轴进行右单旋
rotateR(parent);
//再以grandfather进行左单旋
rotateL(grandfater);
//更新节点颜色--此时cur为子树的根节点
cur->_col = BLACK;
parent->_col = grandfater->_col = RED;
}
//最后需要跳出循环,因为此时不会再影响上层
break;
}
}
}
//在循环外面将_root的颜色重新置黑,避免循环内部将根节点的颜色变红了
_root->_col = BLACK;
return true;
}
//左单旋--右右
void rotateL(Node* parent) {
Node* subR = parent->_right; //右子树
Node* subRL = subR->_left; //右子树的左子树
//右子树的左子树链接到parent的右边
parent->_right = subRL;
if (subRL) //subR可能为空(h==0)
subRL->_parent = parent;
//parent链接到右子树的左边
subR->_left = parent;
Node* pparent = parent->_parent; //保存parent的父节点
parent->_parent = subR;
//让subR成为子树的根
if (pparent) {
if (pparent->_left == parent) {
pparent->_left = subR;
subR->_parent = pparent;
}
else {
pparent->_right = subR;
subR->_parent = pparent;
}
}
else { //如果parent的parent为空,说明parent是整棵树根节点,此时subR成为新的根节点
subR->_parent = nullptr;
_root = subR;
}
}
//右单旋--左左
void rotateR(Node* parent) {
Node* subL = parent->_left; //左子树
Node* subLR = subL->_right; //左子树的右子树
//将左子树的右子树链接到根的左边
parent->_left = subLR;
if (subLR) //应对h==0的情况
subLR->_parent = parent;
//将根链接到左子树的右边
subL->_right = parent;
Node* pparent = parent->_parent; //记录父节点的父节点
parent->_parent = subL;
//让subL成为子树的根
if (pparent) {
if (pparent->_left == parent) {
pparent->_left = subL;
subL->_parent = pparent;
}
else {
pparent->_right = subL;
subL->_parent = pparent;
}
}
else { //如果parent的parent为空,说明parent是整棵树的根节点,此时subL成为新的根节点
subL->_parent = nullptr;
_root = subL;
}
}
//中序遍历
void InOrder() {
return _inOrder(_root);
}
bool Check(Node* root, int blackCount, int baseCount) {
//如果走到空,说明这条路径结束了,此时看count与baseCount是否相等
if (root == nullptr) {
if (blackCount != baseCount) {
std::cout << "此路径黑色节点数量有误,节点key值:" << root->_kv.first << std::endl;
return false;
}
return true;
}
//检查是否出现连续红色节点
if (root->_col == RED) {
if (root->_parent->_col == RED) { //红色节点一定有父亲,因为根节点为黑色
std::cout << "出现连续红色节点,节点key值:" << root->_kv.first << std::endl;
}
}
if (root->_col == BLACK)
blackCount++;
return Check(root->_left, blackCount, baseCount) && Check(root->_right, blackCount, baseCount);
}
//验证红黑树的性质
bool IsBanlance() {
if (_root == nullptr)
return true;
//检查根节点
if (_root->_col == RED) {
std::cout << "根节点为红" << std::endl;
return false;
}
//以最左路径黑色节点数量为基准,同其他路径的黑色节点数量相比较,看是否相等
int baseCount = 0;
Node* cur = _root;
while (cur) {
if (cur->_col == BLACK)
baseCount++;
cur = cur->_left;
}
//检查红黑树的其他性质
return Check(_root, 0, baseCount);
}
private:
void _inOrder(Node* root) {
if (root == nullptr)
return;
_inOrder(root->_left);
std::cout << root->_kv.first << ":" << root->_kv.second << std::endl;
_inOrder(root->_right);
}
private:
Node* _root = nullptr;
};
test.cpp:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include "RBTree.h"
//验证搜索树
void RBTree_test1() {
int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
RBTree<int, int> rb;
for (auto e : a)
rb.insert(std::make_pair(e, e));
rb.InOrder();
}
//验证红黑树的性质
void RBTree_test2() {
//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
RBTree<int, int> rb;
for (auto e : a)
rb.insert(std::make_pair(e, e));
rb.InOrder();
cout << rb.IsBanlance() << endl;
}
//增大测试用例--采用N个随机数来验证
void RBTree_test3() {
srand((size_t)time(0));
const int N = 10000;
RBTree<int, int> rb;
for (int i = 0; i < N; i++) {
int x = rand();
rb.insert(make_pair(x, x));
}
cout << rb.IsBanlance() << endl;
}
int main() {
//RBTree_test1();
//RBTree_test2();
RBTree_test3();
return 0;
}
HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候
🖥️NodeJS专栏:Node.js从入门到精通🖥️博主的前端之路(源创征文一等奖作品):前端之行,任重道远(来自大三学长的万字自述)🖥️TypeScript知识总结:TypeScript从入门到精通(十万字超详细知识点总结)🧑💼个人简介:大三学生,一个不甘平庸的平凡人🍬👉你的一键三连是我更新的最大动力❤️!文章目录1、浅拷贝要求思路代码2、简易深拷贝要求思路代码3、完整深拷贝要求思路代码1、浅拷贝要求补全JavaScript代码,要求实现一个对象参数的浅拷贝并返回拷贝之后的新对象。注意:参数可能包含函数、正则、日期、ES6新对象是对对象的参数进行浅拷贝,并不是直接对整个对象进行浅拷贝(整个
文章目录常用的排序算法1.冒泡排序2.选择排序3.插入排序4.快排排序5.归并排序6.堆排序Java的sort基于什么实现排序算法原理,何为稳定不稳定,快排是否稳定查找二分查找复盘笔试题3.寻找重复的子树树的遍历方式树的遍历方式(先序、中序、后序)先序中序后序如何用数组模拟二叉树的遍历过程?求二叉树的深度两种方法栈、队列232.用栈实现队列225.用队列实现栈字符串序列化与反序列化统计字母出现次数从大到小排序字符串中的最长不重复子串动态规划跳台阶最长公共子序列链表反转链表leetcode445.两数相加II寻找字符串最长回文串力扣14.最长公共前缀1701平均等待时间先说思路,然后写代码常用的
【【快乐手撕LeetCode题解系列】——移除链表元素😎前言🙌删除有序数组中的重复项🙌解法一:画图分析:😍思路分析:😍源代码分享:😍解法二:画图分析:😍思路分析:😍源代码分享:😍解法三:画图分析:😍思路分析:😍源代码分享:😍总结撒花💞 😎博客昵称:博客小梦😊最喜欢的座右铭:全神贯注的上吧!!!😊作者简介:一名热爱C/C++,算法等技术、喜爱运动、热爱K歌、敢于追梦的小博主!😘博主小留言:哈喽!😄各位CSDN的uu们,我是你的博客好友小梦,希望我的文章可以给您带来一定的帮助,话不多说,文章推上!欢迎大家在评论区唠嗑指正,觉得好的话别忘了一键三连哦!😘前言🙌 哈喽各位友友们😊,我今天又学到了很
这个问题在这里已经有了答案:HashMapJava8implementation(6个答案)关闭5年前。我研究了Java8的特性,发现当桶上的条目集数量增加时,HashMap使用红黑树而不是链表。但是,这不要求键是Comparable或键的某些顺序存在吗?这是如何工作的?这种转换实际上何时发生以及如何发生?
Javacollections/Guava/ApacheCommons库中是否有RedBlackTree/AVLTreedata结构实现?如果是的话,你能把它们指给我看吗?基本上我正在寻找一种数据结构,查询应该在O(lgn)time内发生。数据结构也会有一些更新,但不会像查询那样频繁。 最佳答案 BasicallyIamlookingforadatastructurewherethequeriesshouldhappeninO(lgn)time使用TreeMap.它由Red-Blacktree支持所以它的访问时间是O(logN)(我
我正在O(logn)时间内实现一个具有插入、搜索和删除功能的红黑树。插入和搜索工作正常。但是我坚持删除。我在网上找到了这张ppt幻灯片,它显示了RBT删除的算法:http://www.slideshare.net/piotrszymanski/red-black-trees#btnNext从第56页开始。我知道我问的有点太多了,但我已经坚持了2周多了,我找不到问题所在。我理解自上而下删除的方式是您必须相应地旋转和重新着色节点,直到找到要删除的节点的前身。当你确实找到这个节点时——它可能是一个叶子节点或一个有一个右child的节点,用这个节点的数据替换要删除的节点数据,然后像正常的BST
前言: 本专栏旨在记录高频笔面试手撕代码题,以备数字前端秋招,本专栏所有文章提供原理分析、代码及波形,所有代码均经过本人验证。目录如下:1.数字IC手撕代码-分频器(任意偶数分频)2.数字IC手撕代码-分频器(任意奇数分频)3.数字IC手撕代码-分频器(任意小数分频)4.数字IC手撕代码-异步复位同步释放5.数字IC手撕代码-边沿检测(上升沿、下降沿、双边沿)6.数字IC手撕代码-序列检测(状态机写法)7.数字IC手撕代码-序列检测(移位寄存器写法)8.数字IC手撕代码-半加器、全加器9.数字IC手撕代码-串转并、并转串10.数字IC手撕代码-数据位宽转换器(宽-窄,窄-宽转换
芯片设计验证社区·芯片爱好者聚集地·硬件相关讨论社区·数字verifier星球四社区联合力荐!近500篇数字IC精品文章收录!【数字IC精品文章收录】学习路线·基础知识·总线·脚本语言·芯片求职·EDA工具·低功耗设计Verilog·STA·设计·验证·FPGA·架构·AMBA·书籍Verilog无毛刺时钟切换电路一、前言二、题目三、原理3.1有毛刺时钟切换3.2无毛刺时钟切换四、RTL设计五、仿真六、仿真分析一、前言本系列旨在提供100%准确的数字IC设计/验证手撕代码环节的题目,原理,RTL设计,Testbench和参考仿真波形,每篇文章的内容都经过仿真核对。快速导航链接如下:1.奇数分频
有没有人见过STL的实现,其中STL::set不是实现为红黑树?我问的原因是,在我的实验中,B树优于std::set(和其他红黑树实现)2到4倍,具体取决于值B.我很好奇,当似乎有更快的数据结构可用时,是否有令人信服的理由使用红黑树。 最佳答案 Google的一些人实际上构建了一个B-treebasedimplementationoftheC++standardlibrarycontainers.它们的性能似乎比标准二叉树实现要好得多。不过有一个问题。C++标准保证从映射或集合中删除元素只会使指向映射或集合中相同元素的其他迭代器无效