一篇文章带你了解红黑树并将其模拟实现

news2024/11/23 19:21:58

在这里插入图片描述

了解红黑树并将其模拟实现

  • 红黑树的概念和性质
    • 1. 概念
    • 2. 性质
  • 红黑树的结构
  • 红黑树的节点定义及红黑树结构成员定义
  • 红黑树的插入
    • 1. 按照二叉搜索的树规则插入新节点
    • 2. 检测新节点插入后,红黑树的性质是否造到破坏
      • 情况一: cur为红,p为红,g为黑,u存在且为红
      • 情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑
      • 情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑
    • 3. 插入代码解析
    • 4. 插入动态效果
      • 1. 以升序插入构建红黑树
      • 2. 以降序插入构建红黑树
      • 3. 随机插入构建红黑树
  • 红黑树的验证
  • 红黑树的遍历输出和左旋右旋
    • 1. 遍历输出
    • 2. 左单旋
    • 3. 右单旋
  • 红黑树模拟实现全部代码
  • 红黑树的应用
  • 红黑树与AVL树的比较

红黑树的概念和性质

1. 概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是RedBlack通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的

在这里插入图片描述

2. 性质

  1. 每个节点要么是红色,要么是黑色。
  2. 根节点是黑色的。
  3. 如果一个节点是红色的,那么它的两个子节点都是黑色的。
  4. 从任意节点到其每个叶子节点的每条路径都包含相同数量的黑色节点,这被称为“黑高相等性”。
  5. 每个叶子节点(NIL节点,通常表示为空节点)是黑色的。

红黑树的这些性质确保了树的平衡性,从而保证了树的高度在对数范围内,使得基本操作的时间复杂度保持在O(log n)级别

红黑树的结构

为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为跟节点必须为黑色,为了与根节点进行区分,将头结点给成黑色,并且让头结点的 pParent 域指向红黑树的根节点,pLeft域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点。

当在红黑树的实现中增加一个头结点时,目的是为了简化操作和处理边界情况,而不必为特殊情况单独编写代码。这个头结点通常是一个黑色节点,位于红黑树的根节点之上,其pParent指向红黑树的根节点,pLeft指向红黑树中最小的节点,pRight指向红黑树中最大的节点。

以下是对头结点的作用和实现细节的解释:

  1. 简化边界条件处理:头结点的存在使得根节点始终是黑色的,因为根节点是头结点的右子节点。这样,在插入和删除等操作中,不需要特殊情况处理根节点的颜色和父节点是否存在的问题。
  2. 快速访问最小和最大节点:头结点的pLeft指向红黑树中最小的节点,pRight指向最大的节点。这意味着你可以在O(1)时间内找到树中的最小和最大节点,而无需进行遍历。
  3. 统一根节点访问:无论是插入、删除还是其他操作,都可以始终从头结点开始进行操作,因为头结点的pParent指向了真正的根节点。这简化了代码逻辑,因为你不必特殊处理根节点的情况。

下面是一个头结点的示例实现:

struct Node {
    int data;
    Color color;
    Node* left;
    Node* right;
    Node* parent;
};

class RedBlackTree {
private:
    Node* root; // 实际的根节点
    Node* header; // 头结点

    // ...其他成员函数和辅助函数...

public:
    RedBlackTree() : root(nullptr), header(new Node) {
        header->color = BLACK;
        header->left = nullptr;
        header->right = nullptr;
        header->parent = nullptr;
    }

    // ...其他公共成员函数...
};

在这个示例中,我们添加了一个名为header的成员变量,它是一个指向头结点的指针。头结点的初始化在构造函数中完成,并确保它始终是黑色的,根节点则位于头结点的pParent中。头结点的存在将简化红黑树的操作和边界情况处理。

这是一种实现方式,但在后面我们实现不是采用的这种方式,当然你也可以选择这种方式实现,更为简易,红黑树的实现过程只是为了让我们更熟悉它的底层,现实中需要我们实现的场景很少

红黑树的节点定义及红黑树结构成员定义

这里我们的实现采用了三叉链的形式,后面我们会提到这种写法的好处

我们这里使用键值对的模板是为了方便后期模拟实现map和set

enum Colour
{
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;

	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
	{}
};

template<class K, class V>
struct RBTree
{
	typedef RBTreeNode<K,V> Node;
private:
	Node* _root = nullptr;
};
  1. 枚举类型 Colour:这里定义了一个枚举类型,包括两个成员 REDBLACK。这个枚举类型用于表示红黑树中节点的颜色。在红黑树中,节点可以是红色或黑色,用这个枚举类型来表示节点颜色是一种常见的做法。

  2. 结构体 RBTreeNode<K, V>:这个结构体定义了红黑树的节点结构,其中包含以下成员变量:

    • _left_right:分别指向节点的左子节点和右子节点的指针。
    • _parent:指向节点的父节点的指针。
    • _kv:用于存储键值对(Key-Value Pair)的成员变量。这个键值对通常用于存储节点的数据。
    • _col:表示节点的颜色,可以是 REDBLACK

    构造函数 RBTreeNode(const pair<K, V>& kv) 用于初始化节点对象,将各个成员变量赋初值,包括键值对 _kv 和颜色 _col

  3. 结构体 RBTree<K, V>:这个结构体定义了整个红黑树的结构,其中包含以下成员变量和一个别名:

    • _root:指向红黑树的根节点的指针。根节点是红黑树的起始点,所有操作都从根节点开始。

    typedef RBTreeNode<K,V> Node; 定义了一个别名 Node,用于表示红黑树节点的类型。这样做可以简化代码,使得在代码中引用节点类型更加方便。

红黑树的插入

1. 按照二叉搜索的树规则插入新节点

bool Insert(const 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 (cur->_kv.first < kv.first)
        {
            parent = cur;
            cur = cur->_right;
        }
        else if (cur->_kv.first > kv.first)
        {
            parent = cur;
            cur = cur->_left;
        }
        else
        {
            return false;
        }
    }

    cur = new Node(kv);
    cur->_col = RED;//新插入节点即为红节点,不懂结合性质和我下面的讲解

    if (parent->_kv.first < kv.first)//同搜索树规则先直接插入
    {
        parent->_right = cur;
    }
    else
    {
        parent->_left = cur;
    }

    cur->_parent = parent;//更新新插入节点的父指针

    while (parent && parent->_col == RED)
    {
        Node* grandfater = parent->_parent;
        assert(grandfater);
        assert(grandfater->_col == BLACK);
        if (parent == grandfater->_left)//具体看下面讲解
        {
            Node* uncle = grandfater->_right;
            // 情况一 : uncle存在且为红,变色+继续往上处理
            if (uncle && uncle->_col == RED)
            {
                parent->_col = uncle->_col = BLACK;
                grandfater->_col = RED;
                // 继续往上处理
                cur = grandfater;
                parent = cur->_parent;
            }// 情况二+三:uncle不存在 + 存在且为黑
            else
            {
                // 情况二:右单旋+变色
                if (cur == parent->_left)
                {
                    RotateR(grandfater);
                    parent->_col = BLACK;
                    grandfater->_col = RED;
                }
                else
                {
                    // 情况三:左右单旋+变色
                    RotateL(parent);
                    RotateR(grandfater);
                    cur->_col = BLACK;
                    grandfater->_col = RED;
                }

                break;
            }
        }
        else // (parent == grandfater->_right)
        {
            Node* uncle = grandfater->_left;
            // 情况一
            if (uncle && uncle->_col == RED)
            {
                parent->_col = uncle->_col = BLACK;
                grandfater->_col = RED;
                // 继续往上处理
                cur = grandfater;
                parent = cur->_parent;
            }
            else
            {
                // 情况二:左单旋+变色
                if (cur == parent->_right)
                {
                    RotateL(grandfater);
                    parent->_col = BLACK;
                    grandfater->_col = RED;
                }
                else
                {
                    // 情况三:右左单旋+变色
                    RotateR(parent);
                    RotateL(grandfater);
                    cur->_col = BLACK;
                    grandfater->_col = RED;
                }

                break;
            }
        }

    }

    _root->_col = BLACK;
    return true;
}

其实前面的代码和我们上一篇讲到的AVL树的插入类似,只不过这里的红黑树没有了平衡因子,而变为了颜色

接下来我们看每种情况的解析

2. 检测新节点插入后,红黑树的性质是否造到破坏

因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:

约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

情况一: cur为红,p为红,g为黑,u存在且为红

在这里插入图片描述

cur和p均为红解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

在这里插入图片描述

p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转,p、g变色–p变黑,g变红

情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑

在这里插入图片描述

p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转,则转换成了情况2

3. 插入代码解析

  1. 如果 _root 是空的,表示红黑树为空,那么将创建一个新的节点 _root,并将其颜色设置为黑色。这是根节点,并且遵循红黑树规则的规定,根节点必须为黑色。
  2. 如果 _root 不为空,那么代码会在树中查找正确的位置来插入新节点。通过遍历树的方式,找到正确的父节点 parent,以及新节点要插入的位置 cur
  3. 如果要插入的键已经存在于树中,就返回 false,因为不允许键重复。
  4. 创建新节点 cur 并将其颜色设置为红色。新节点的颜色一开始设置为红色,这是为了满足红黑树的性质。
  5. 然后,代码进入一个循环,循环的目的是确保插入新节点后仍然满足红黑树的性质。这是红黑树插入操作的关键部分。
    • 在循环中,首先查看 parent 节点的颜色,如果 parent 是红色,那么需要进一步处理来保持红黑树性质。
    • 然后,代码会检查 uncle 节点的颜色,即 parent 的兄弟节点。根据 uncle 的颜色,分为情况一、情况二和情况三。
    • 最后,退出循环后将根节点的颜色设置为黑色,以确保根节点满足红黑树的性质。

4. 插入动态效果

1. 以升序插入构建红黑树

在这里插入图片描述

2. 以降序插入构建红黑树

在这里插入图片描述

3. 随机插入构建红黑树

在这里插入图片描述

红黑树的验证

bool IsBalance()
	{
		if (_root == nullptr)
		{
			return true;
		}

		if (_root->_col == RED)//检查根节点是否为黑
		{
			cout << "根节点不是黑色" << endl;
			return false;
		}

		// 黑色节点数量基准值
		int benchmark = 0;
		return PrevCheck(_root, 0, benchmark);
	}

返回值函数PrevCheck的实现

bool PrevCheck(Node* root, int blackNum, int& benchmark)
{
    if (root == nullptr)
    {
        if (benchmark == 0)
        {
            benchmark = blackNum;
            return true;
        }

        if (blackNum != benchmark)
        {
            cout << "某条黑色节点的数量不相等" << endl;
            return false;
        }
        else
        {
            return true;
        }
    }

    if (root->_col == BLACK)
    {
        ++blackNum;
    }

    if (root->_col == RED && root->_parent->_col == RED)
    {
        cout << "存在连续的红色节点" << endl;
        return false;
    }

    return PrevCheck(root->_left, blackNum, benchmark)
        && PrevCheck(root->_right, blackNum, benchmark);
}
  1. 黑高度相同性质检查:函数使用递归方式遍历树中的节点,并维护一个 blackNum 变量,用于跟踪从根节点到当前节点经过的黑色节点数。在遍历过程中,它会检查每条从根到叶子节点的路径上的黑色节点数是否相同。如果路径上的黑色节点数不相同,说明违反了红黑树的性质。
  2. 连续红色节点检查:在遍历的过程中,代码还检查是否存在连续的红色节点。在红黑树中,不允许存在相邻的两个红色节点。如果存在连续的红色节点,也违反了红黑树的性质。
  3. 返回值:函数返回一个布尔值,用于指示红黑树是否满足性质。如果在遍历过程中发现任何性质被破坏,函数将返回 false,否则返回 true

这个函数是用于验证红黑树的一种常见方法,用于检查树是否保持了红黑树的性质,包括黑高度相同和不连续的红色节点。如果该函数返回 true,则表示树是一个有效的红黑树,否则表示树的结构违反了红黑树的性质。

红黑树的遍历输出和左旋右旋

1. 遍历输出

void _InOrder(Node* root)
{
    if (root == nullptr)
    {
        return;
    }

    _InOrder(root->_left);
    cout << root->_kv.first << ":" << root->_kv.second << endl;
    _InOrder(root->_right);
}

这里的遍历是一种按照节点值的大小顺序遍历二叉搜索树(BST)的方法。在这里,红黑树也是一种BST,因此中序遍历可以用来按键的顺序输出树中的节点。

函数的主要逻辑包括:

  1. 如果输入的 root 节点为空(即树为空),则直接返回,不执行任何操作。
  2. 否则,函数会递归地执行以下操作:
    • 先递归调用 _InOrder 函数来遍历左子树(左子节点)。
    • 输出当前节点的键值对(这里假设键是整数,值是整数,根据需要调整输出格式)。
    • 最后递归调用 _InOrder 函数来遍历右子树(右子节点)。

2. 左单旋

void RotateL(Node* parent)
{
    Node* subR = parent->_right;
    Node* subRL = subR->_left;

    parent->_right = subRL;
    if (subRL)
        subRL->_parent = parent;

    Node* ppNode = parent->_parent;

    subR->_left = parent;
    parent->_parent = subR;

    if (_root == parent)
    {
        _root = subR;
        subR->_parent = nullptr;
    }
    else
    {
        if (ppNode->_left == parent)
        {
            ppNode->_left = subR;
        }
        else
        {
            ppNode->_right = subR;
        }

        subR->_parent = ppNode;
    }

}
  1. 首先,将 parent 节点的右子节点 subRsubR 的左子节点 subRL 分别保存起来。subR 将会成为新的根节点,而 subRL 将成为 parent 节点的右子节点。
  2. 接下来,将 parent 节点的右子节点指针 _right 指向 subRL,同时确保 subRL 的父指针 _parent 指向 parent。这一步是将 subRLparent 连接起来。
  3. parent 节点的父节点指针 ppNode 保存起来。这是为了在旋转后更新 ppNode 的左子节点或右子节点指向 subR,以确保树的连接正确。
  4. subR 的左子节点指针 _left 指向 parent,同时将 parent 的父节点指针 _parent 指向 subR。这一步是将 parent 旋转到 subR 的位置上。
  5. 接下来,处理根节点的情况。如果 _root 指向 parent,那么现在 _root 应该指向 subR,因此将 _root 更新为 subR,并将 subR 的父指针设置为 nullptr
  6. 如果 _root 不指向 parent,则需要根据 ppNode 的情况来更新 ppNode 的左子节点或右子节点指向 subR,以确保整棵树的连接正确。

左单旋和右单旋和我们之前的AVL树实现基本一致,若不能理解请看上一篇文章

3. 右单旋

void RotateR(Node* parent)
{
    Node* subL = parent->_left;
    Node* subLR = subL->_right;

    parent->_left = subLR;
    if (subLR)
    {
        subLR->_parent = parent;
    }

    Node* ppNode = parent->_parent;

    subL->_right = parent;
    parent->_parent = subL;

    if (_root == parent)
    {
        _root = subL;
        subL->_parent = nullptr;
    }
    else
    {
        if (ppNode->_left == parent)
        {
            ppNode->_left = subL;
        }
        else
        {
            ppNode->_right = subL;
        }

        subL->_parent = ppNode;
    }

}
  1. 首先,将 parent 节点的左子节点 subLsubL 的右子节点 subLR 分别保存起来。subL 将会成为新的根节点,而 subLR 将成为 parent 节点的左子节点。
  2. 接下来,将 parent 节点的左子节点指针 _left 指向 subLR,同时确保 subLR 的父指针 _parent 指向 parent。这一步是将 subLRparent 连接起来。
  3. parent 节点的父节点指针 ppNode 保存起来。这是为了在旋转后更新 ppNode 的左子节点或右子节点指向 subL,以确保树的连接正确。
  4. subL 的右子节点指针 _right 指向 parent,同时将 parent 的父节点指针 _parent 指向 subL。这一步是将 parent 旋转到 subL 的位置上。
  5. 接下来,处理根节点的情况。如果 _root 指向 parent,那么现在 _root 应该指向 subL,因此将 _root 更新为 subL,并将 subL 的父指针设置为 nullptr
  6. 如果 _root 不指向 parent,则需要根据 ppNode 的情况来更新 ppNode 的左子节点或右子节点指向 subL,以确保整棵树的连接正确。

红黑树模拟实现全部代码

#pragma once
#include<iostream>
#include<assert.h>
#include<time.h>
using namespace std;

enum Colour
{
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;

	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
	{}
};

template<class K, class V>
struct RBTree
{
	typedef RBTreeNode<K,V> Node;
public:
	bool Insert(const 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 (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv);
		cur->_col = RED;

		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		cur->_parent = parent;

		while (parent && parent->_col == RED)
		{
			Node* grandfater = parent->_parent;
			assert(grandfater);
			assert(grandfater->_col == BLACK);
            
			if (parent == grandfater->_left)
			{
				Node* uncle = grandfater->_right;
				// 情况一 : uncle存在且为红,变色+继续往上处理
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;
					// 继续往上处理
					cur = grandfater;
					parent = cur->_parent;
				}// 情况二+三:uncle不存在 + 存在且为黑
				else
				{
					// 情况二:右单旋+变色
					if (cur == parent->_left)
					{
						RotateR(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else
					{
						// 情况三:左右单旋+变色
						RotateL(parent);
						RotateR(grandfater);
						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
			else // (parent == grandfater->_right)
			{
				Node* uncle = grandfater->_left;
				// 情况一
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;
					// 继续往上处理
					cur = grandfater;
					parent = cur->_parent;
				}
				else
				{
					// 情况二:左单旋+变色
					if (cur == parent->_right)
					{
						RotateL(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else
					{
						// 情况三:右左单旋+变色
						RotateR(parent);
						RotateL(grandfater);
						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}

		}

		_root->_col = BLACK;
		return true;
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	bool IsBalance()
	{
		if (_root == nullptr)
		{
			return true;
		}

		if (_root->_col == RED)
		{
			cout << "根节点不是黑色" << endl;
			return false;
		}

		// 黑色节点数量基准值
		int benchmark = 0;

		return PrevCheck(_root, 0, benchmark);
	}

private:
	bool PrevCheck(Node* root, int blackNum, int& benchmark)
	{
		if (root == nullptr)
		{
			if (benchmark == 0)
			{
				benchmark = blackNum;
				return true;
			}

			if (blackNum != benchmark)
			{
				cout << "某条黑色节点的数量不相等" << endl;
				return false;
			}
			else
			{
				return true;
			}
		}

		if (root->_col == BLACK)
		{
			++blackNum;
		}

		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}

		return PrevCheck(root->_left, blackNum, benchmark)
			&& PrevCheck(root->_right, blackNum, benchmark);
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		Node* ppNode = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}

			subR->_parent = ppNode;
		}

	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}

		Node* ppNode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}

			subL->_parent = ppNode;
		}

	}

private:
	Node* _root = nullptr;
};

红黑树的应用

红黑树是一种自平衡的二叉搜索树,由于其平衡性和高效性能,广泛用于各种计算机科学和软件工程领域。以下是一些红黑树的常见应用:

  1. C++ STL中的std::mapstd::set:C++标准模板库(STL)中的std::mapstd::set通常使用红黑树实现。它们用于实现有序的关联容器,其中键值对被自动排序并保持平衡,以支持高效的查找、插入和删除操作。
  2. 数据库系统:红黑树常用于数据库系统中的索引结构,如B+树的实现中。数据库中的索引需要高效地支持查找、范围查询和排序等操作,而红黑树是一种性能良好的选择。
  3. 操作系统:红黑树在操作系统中用于进程调度、虚拟内存管理和文件系统的实现。在这些场景中,需要高效的数据结构来管理和操作各种系统资源。
  4. 网络路由表:红黑树被广泛用于路由表的实现,以支持网络路由器和交换机等网络设备的高效路由查找。
  5. 编译器:编译器可以使用红黑树来管理符号表,以便进行语法分析、类型检查和代码生成。
  6. 计算机图形学:在计算机图形学中,红黑树可用于空间分割数据结构,如八叉树和四叉树的实现,以支持高效的图形渲染和碰撞检测。
  7. 高性能库和框架:红黑树常被用作高性能库和框架中的核心数据结构,以提供高效的数据管理和操作功能。
  8. 实时系统:实时系统需要高效的数据结构来管理任务和事件,红黑树可以用于实现定时器和调度器。

总的来说,红黑树是一种多功能的数据结构,适用于需要高效的自平衡二叉搜索树的任何领域,特别是需要高效的插入、删除和查找操作的场景。其平衡性和性能使其成为各种应用中的重要工具。

红黑树与AVL树的比较

红黑树(Red-Black Tree)和AVL树(Adelson-Velsky and Landis Tree)都是自平衡二叉搜索树,它们在维护平衡性和支持高效的插入、删除和查找操作方面有很多相似之处,但也有一些关键的区别。以下是红黑树和AVL树之间的比较:

  1. 平衡性要求
    • 红黑树:红黑树放宽了平衡性要求,它保证了树的高度最多是其2倍。
    • AVL树:AVL树要求更为严格,它保证树的高度差不超过1,因此AVL树的平衡性更强。
  2. 性能
    • 红黑树:由于平衡性要求相对较低,插入和删除操作在平均情况下可能比AVL树更快,因此在那些需要频繁插入和删除操作的场景中,红黑树可能更适合。
    • AVL树:AVL树在查找操作上可能稍微快一些,因为它的平衡性更严格,但它的插入和删除操作可能更慢,因为它需要更频繁地执行旋转操作来保持平衡。
  3. 旋转操作
    • 红黑树:红黑树的旋转操作相对较少,因为它放宽了平衡性要求。通常,红黑树的旋转操作较少,但需要更多的颜色变换操作来保持平衡。
    • AVL树:AVL树的旋转操作更频繁,因为它要求更严格的平衡性。这可能导致在插入和删除操作中执行更多的旋转。
  4. 内存消耗
    • 红黑树:由于红黑树的平衡性要求较低,通常会消耗更少的内存,因为不需要额外的平衡因子来跟踪节点的高度差。
    • AVL树:AVL树需要为每个节点存储平衡因子,这可能会导致更多的内存消耗。
  5. 应用场景
    • 红黑树:适用于需要高效的插入和删除操作,而对查找操作的性能要求相对较低的场景,如C++的STL中的std::mapstd::set
    • AVL树:适用于对查找操作有更高性能要求的场景,但可以容忍较慢的插入和删除操作的情况,如数据库索引。

总的来说,选择使用红黑树还是AVL树取决于应用的需求和性能要求,在C++的map和set的实现中,底层调用的就是红黑树,但在实际的面试和工作中,红黑树的应用可能更为广泛。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1003684.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

正中优配:A股三大指数集体反弹 医药板块全线走强

周一&#xff0c;A股商场展开反弹&#xff0c;三大指数大部分时间单边上扬&#xff0c;特别是午后在人民币汇率增值的提振下&#xff0c;指数呈现一轮脉冲式上涨&#xff0c;同时伴随北向资金显着回流。医药、轿车板块全天表现强势&#xff0c;券商板块午后显着反弹。 到昨日收…

电动取暖器、加热器、暖风机、亚马逊各国要求标准都有哪些?

UL1278测试报告介绍 UL1278是针对电气安全方面的测试报告标准&#xff0c;主要用于评估各种电器的安全性能&#xff0c;以确保它们在使用过程中不会对人身安全造成威胁。桌面暖风机作为一款加热设备&#xff0c;需要满足UL1278标准才能进入美国市场。 每年的十月份开始国外气温…

接口测试(详细总结)

序章 ​ 说起接口测试&#xff0c;网上有很多例子&#xff0c;看了不不知道他们说的什么&#xff0c;觉得接口测试&#xff0c;好高大上。认为学会了接口测试就能屌丝逆袭&#xff0c;走上人生巅峰&#xff0c;迎娶白富美。因此学了点开发知识后&#xff0c;发现接口测试其实都…

VMware中安装WindowsXP虚拟机详细步骤

有些小伙伴肯定会好奇&#xff1a;这都 Windows11 的年代了&#xff0c;怎么还要学习安装 Windows XP 操作系统呢&#xff1f; 虽然我们普通用户基本都是用 Windows10 或者 Windows11&#xff0c;但是你会发现很多公司、部门包括一些特殊场合用的都是 Windows XP 系统&#xff…

[每周一更]-(第62期):SRE 是什么?

在公司Devops平台搭建&#xff0c;采用了JenkinsGitGitlabDocker&#xff0c;进行了自动化构建和部署代码&#xff0c;解放了繁杂的代码更改到test/prod环境的问题&#xff1b; 这部分更多是运维比例极大&#xff0c;少量的开发操作&#xff0c;基本都是配置命令行以及yml配置、…

【LeetCode75】第五十三题 猜数字大小

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目就是让我们猜数字&#xff0c;要猜中的数字为1~n&#xff0c;并且给我们提供一个api&#xff0c;传入一个数字表示是我们猜的数&…

ABB UF C911B108 3BHE037864R010控制主板模块

ABB UF C911B108 3BHE037864R010 控制主板模块通常用于ABB的工业自动化和控制系统中&#xff0c;作为关键组件之一&#xff0c;用于执行控制、监测和通信任务。以下是通常情况下控制主板模块的一些产品功能&#xff1a; 高性能处理器&#xff1a;ABB UF C911B108 3BHE037864R01…

使用ExcelJS快速处理Node.js爬虫数据

什么是ExcelJS ExcelJS是一个用于处理Excel文件的JavaScript库。它可以让你使用JavaScript创建、读取和修改Excel文件。 以下是ExcelJS的一些主要特点&#xff1a; 支持xlsx、xlsm、xlsb、xls格式的Excel文件。可以创建和修改工作表、单元格、行和列。可以设置单元格样式、字…

C++之构造函数初始化列表()与{}区别(一百九十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

物资管理系统数据分析报告

目 录 1.需求分析 1.1系统目标与要求 1.2系统分析 1.3业务流程图 2.系统逻辑方案 2.1数据流程图&#xff08;DFD&#xff09;&#xff1a; 2.2数据字典&#xff08;简称DD&#xff09; 3系统总体结构设计 3.1软件系统总体结构设计 3.2数据存储的总体设计…

【Spark的五种Join策略解析】

join基本流程 Spark将参与Join的两张表抽象为流式遍历表(streamIter)和查找表(buildIter)&#xff0c;通常streamIter为大表&#xff0c;buildIter为小表&#xff0c;我们不用担心哪个表为streamIter&#xff0c;哪个表为buildIter&#xff0c;这个spark会根据join语句自动帮我…

硬不硬你说了算!近 40 张图解被问千百遍的 TCP 三次握手和四次挥手面试题

前言 不管面试 Java 、C/C、Python 等开发岗位&#xff0c; TCP 的知识点可以说是的必问的了。 任 TCP 虐我千百遍&#xff0c;我仍待 TCP 如初恋。 遥想小林当年校招时常因 TCP 面试题被刷&#xff0c;真是又爱又狠…. 过去不会没关系&#xff0c;今天就让我们来消除这份恐…

hadoop运行WordCount时,Input path does not exist错误原因

修改配置文件core-site.xml 为如下所示 vim /usr/local/hadoop/etc/hadoop/core-site.xmlxml文件改为 <configuration></configuration>如果将core-site.xml文件变动为原来的内容&#xff0c;则程序将不再去hdfs://localhost:9000下寻找input文件&#xff0c;而是…

解决uniapp局部页面(scrollview)下拉刷新出现不能复位的问题

问题所在&#xff1a;&#xff08;困扰了我一天&#xff09; 局部页面自定义刷新出现下拉页面不能复位的问题 看下解决后的效果 废话不多说直接上代码 <view class"content"><scroll-view class"section" :style"{height:300px}" :re…

IDEA设置方法分割线

IDEA设置方法分割线 最近在学习视频教程的时候&#xff0c;总是能看到一些大佬用的IDEA与自己的仿佛不是一个软件&#xff01; 如下图&#xff1a; 看到一些老师用IDEA时&#xff0c;方法上都会有一条横线。感觉这样很方便&#xff0c;于是乎自己设置了一下。现在分享给大家&…

react处理跨域

如果是新建的react项目&#xff0c;没有将webpack的配置文件释放出来的话&#xff0c;请先运行 npm run eject 根目录会出现config文件夹&#xff0c;找到path.js就可以看到proxy的配置&#xff0c;默认读取的是src/setupProxy.js 那么我们可以在src目录下新建setupProxy.js…

快解析内网穿透如何帮你轻松实现外网远程连接?

外网相信大家多少了解一点&#xff0c;其实就是连接不同地区局域网&#xff0c;或者是城域网计算机通信的远程网&#xff0c;因此被称为广域网或者公网。在这个互联网信息时代&#xff0c;很多用户还是不知道怎么连接外网&#xff1f;外网远程桌面连接的步骤是怎样的&#xff1…

【Samba】win 11 不允许一个用户使用一个以上用户名与服务器或共享资源的多重连接

win11 遇到不允许一个用户使用一个以上用户名与服务器或共享资源的多重连接 原因 之前使用两个用户登录过&#xff0c;没有释放 解决方案1 1、打开cmd命令窗口&#xff1a; net use * /del /y 命令中断开所有连接. 2、重新登录 解决方案2 删除后重启

【Linux问题】日期校准

问题 请求阿里云对象存储返回 The difference between the reguest time and the current time is too large. 规定时间和当前时间之间的差异太大。 由于虚拟机出现问题导致服务器时间不准 正常的服务器时间 异常的服务器时间 设置一下时间就好 校准时间 安装 yum insta…

winscope怎么实现user版本上导出方案设计探讨-千里马android framework车载车机手机系统开发

背景 在马哥给讲解怎么用winscope来分析各种闪黑&#xff0c;黑屏等问题后&#xff0c;很多买课的同学都开始使用这个工具用于实际公司的项目了&#xff0c;但是很多同学又开始发现有一个问题&#xff0c;那就发现在user版本的手机设备上发现无法抓取相关的winscope&#xff0…