【C++ STL】 map&set


map和set

STL容器分为序列式容器和关联式容器。

  • 序列式容器vector、list等底层为线性数据结构,数据元素之间没有联系,一般用来存储数据。
  • 关联式容器map、set等底层采用平衡搜索树,存储的是<key/value>式的键值对,数据检索效率高。

1.容器

1.1 set

template < class T,                        // T 元素数据类型
           class Compare = less<T>,        // compare 该数据类型的比较函数
           class Alloc = allocator<T>>     // Alloc 空间配置器
class set;

set就是K模型的容器,按照搜索树的规则存储元素,具有排序和去重的功能。

set等搜索树容器都不支持修改元素,会破坏搜索树结构。find的返回值被const修饰。

接口

增加解释
pair<iterator,bool> insert (const value_type& val)单个插入
iterator insert (iterator position, const value_type& val)迭代器插入
void insert (InputIterator first, InputIterator last)范围插入
set<int> s;
s.insert(3);
s.insert(1);
s.insert(5);
s.insert(8);
s.insert(8);
s.insert(2);
s.insert(2);

std::set<int>::iterator it = s.begin();
while (it != s.end()) {
    cout << *it << " ";
    ++it;
}
// 1 2 3 5 8 //set底层是平衡搜索树,所以可以去重和排序
删除解释
size_type erase (const value_type& val)指定值删除
void erase (iterator position)迭代器删除
void erase (iterator first, iterator last)范围删除

值删除接口返回的是删除元素的个数,可通过此判断是否删除成功。

size_t ret = s.erase(30);
std::cout << ret << std::endl; // 使用值删除时,返回值是删除元素的个数
查找解释
iterator find (const value_type& val) const值查找
size_type count (const value_type& val) const返回个数
set<int>::iterator pos = s.find(30);
if (pos != s.end())
    s.erase(pos);

int cnt = s.count(30);
if (cnt == 1)
    cout << "ok" << endl;

1.2 multiset

**multiset没有去重机制,允许键值重复。**其底层也是搜索树,插入相同元素时,可以放到该节点的任意子节点。

对于键值重复的节点,查找返回的是中序遍历遇到的第一个节点

比如查找值为10的节点,在找到第一个10时,会到他的左子树去找,直到遇到比10小的数。

std::multiset<int> s;
s.insert(3);
s.insert(1);
s.insert(5);
s.insert(5);
s.insert(2);
s.insert(2);

multiset<int>::iterator it = s.begin();
while (it != s.end()) {
    std::cout << *it << " ";
    ++it;
}
// 1 2 2 3 5 5

接口

multiset<int>::iterator pos = s.find(1);
while (pos != s.end() && *pos == 1)
{
    s.erase(pos);
    ++pos;
}

删除和迭代器遍历操作不可以放在一起,删除就改变了原有树的结构,再++pos就访问非法空间了。可以用下面更简单的方式。

while (pos != s.end())
{
    s.erase(pos);
    std::cout << "找到了" << std::endl;
    pos = s.find(1);
}

int ret = 1;
while (ret)
{
    ret = s.erase(1);
    std::cout << "找到了" << std::endl;
}

 

1.3 map

map底层也是平衡搜索树,map的元素按照键值key进行排序。map支持下标访问符,可以找到与key对应的value。

定义

键值对是表示具有对应关系的一种结构,一般只有两个成员key和value,key代表键值,value表示对应的数据。

template <class T1, class T2>
struct pair
{
  typedef T1 first_type;
  typedef T2 second_type;
  first_type first;
  second_type second;
    
  pair(const T1& a, const T2& b) : first(a = T1()), second(b = T2())
  {}
};

pair就是键值对,实际上是一个结构体,被map当作元素类型使用。

template < class Key,                                     // map::key_type
           class T,                                       // map::mapped_type
           class Compare = less<Key>,                     // map::key_compare
           class Alloc = allocator<pair<const Key,T> >    // map::allocator_type
         > class map;

接口

typedef Key key_type;
typedef T mapped_type;
typedef pair<const Key, T> value_type;
增加解释
pair<iterator,bool> insert (const value_type& val)单个插入
dict.insert(pair<string, string>("arrary", "数组"));
dict.insert(make_pair("string", "字符串"));
dict.insert({"sort", "排序"});

auto ret = m.insert({"sort", "[[排序]]"});
cout <<(ret.first)->first << (ret.first)->second << ret.second << endl; // sort 排序 0

插入返回的是迭代器和布尔值的键值对。布尔值表示插入是否成功。

  • 树中无重复key值的元素则插入成功,迭代器表示插入位置。
  • 树中存在相同key值的元素则插入失败,迭代器表示该相同key值元素的位置
查找解释
iterator find (const key_type& k)查找
string str;
while (cin >> str) {
    map<string, string>::iterator it = m.find(str);
    if (it != m.end()) {
        cout << it->first << "-" << it->second << endl;
    }
}
删除解释
size_type erase (const key_type& k)指定值删除
void erase (iterator position)迭代器删除
void erase (iterator first, iterator last)范围删除

erase删除节点可以传迭代器,也可以传key进行遍历删除。

下标访问解释
mapped_type& operator[] (const key_type& k)下标访问
mapped_type& at (const key_type& k)下标访问

[]基本功能是查找,此外兼具插入和修改

count_map["pg"];         // 插入
cout << count_map["pg"]; // 查找
count_map["pg"] = 111;   // 修改
count_map["tz"] = 888;   // 插入+修改

调用insert,无则插入有则查找,返回value的引用表示支持修改。

他的内部实现是:

mapped_type& operator[] (const key_type& k)
{
    return insert(make_pair(k,mapped_type())).first->second;
    // pair<iterator, bool> ret = insert(make_pair(k, mapped_type()));
    // return ret.first->second;
}
string arr[] = {"西瓜","西瓜","苹果","西瓜","苹果","苹果","西瓜","苹果","香蕉","苹果","香蕉","梨"};
map<string, int> count_map;

for (auto& e : arr)
	count_map[e]++;
for (auto& kv: count_map)
	cout << kv.first << ": " << kv.second << endl;

1.4 multimap

multimapmap的区别是可存在重复数据。所以multimap无法重载[]操作符。

count可以用来统计同一key值元素的出现次数。

 

2. 底层结构

2.1 AVL树

AVL树的定义

搜索树的查找效率高,但如果数据有序或接近有序,搜索树就会退化成单支树,查找效率就会变成线性的。

使用AVL树插入新结点时会对树进行调整,保证每个结点的左右子树高度之差的绝对值不超过1。从而降低树的高度,减少平均搜索长度。

一棵AVL树要么是空树,要么是具有如下性质的搜索树:

  • 该树的左右子树都是AVL树,
  • 左右子树的高度之差(简称平衡因子)的绝对值不超过1

在这里插入图片描述

使用平衡因子只是AVL树的一种实现方式。

这样的树是高度平衡的,它的高度维持在 l o g n logn logn 左右,搜索的时间复杂度就是 O ( l o g n ) O(logn) O(logn)

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

    pair<K, V> _kv;
    int _bf; // balance factor

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

template<class K, class V>
class avl_tree
{
    typedef avl_node<K, V> node;
private:
    node* _root = nullptr;
};
AVL树的性能

AVL是一棵严格平衡的二叉搜索树,可以保证查询效率 O ( l o g n ) O(logn) O(logn) 。但插入删除时要维护平衡,会出现多次旋转,性能很低下。

因此,如果需要一种查询高效且有序的数据结构,且不常改变结构,可以考虑AVL树。AVL树在实际中不太常用,因为存在红黑树。

更新平衡因子

如图所示,插入节点会改变新节点到根的路径上所有节点的平衡因子。所以要先更新平衡因子,再对树旋转处理。

在这里插入图片描述

如果插入在父节点的左边,父节点的平衡因子要减1;如果插入在父节点的右边,父节点的平衡因子要加1。

  1. 如果父节点的平衡因子更新为0,说明所在树已经平衡且高度未变,不会影响到上层节点。
  2. 如果父节点的平衡因子更新为1/-1,说明所在树高度发生变化;
  3. 如果父节点的平衡因子更新为2/-2,说明子树已经不平衡,需要旋转处理。
bool Insert(const pair<K, V>& kv)
{
    // 如果根节点为空,将新节点设为根节点并返回true
    if (_root == nullptr) 
    {
        _root = new Node(kv);
        return true;
    }

    // 初始化父节点和当前节点为根节点
    Node* parent = nullptr;
    Node* curr = _root;

    // 在树中寻找插入位置
    while (curr) 
    {
        // 如果当前节点的键小于插入键,向右子树移动
        if (curr->_kv.first < kv.first) {
            parent = curr;
            curr = curr->_right;
        }
        // 如果当前节点的键大于插入键,向左子树移动
        else if (curr->_kv.first > kv.first) {
            parent = curr;
            curr = curr->_left;
        }
        // 如果当前节点的键等于插入键,返回false,不允许插入重复键的节点
        else {
            return false;
        }
    }

    // 创建新节点,并将其连接到父节点的相应子节点上
    curr = new Node(kv);
    if (parent->_kv.first < kv.first)
        parent->_right = curr;
    else
        parent->_left = curr;
    curr->_parent = parent;

    // 控制平衡
    // 1. 更新平衡因子
    // 2. 旋转处理异常平衡因子

    while (parent) // 更新到根
    { 
        // 更新当前父节点的平衡因子
        if (curr == parent->_left)
            parent->_bf--;
        else if (curr == parent->_right)
            parent->_bf++;
        
        // 检测平衡因子是否为0,表示已平衡,更新结束
        if (parent->_bf == 0) { 
            break;
        }
        // 如果平衡因子为1或-1,向上更新父节点
        else if (parent->_bf == 1 || parent->_bf == -1) 
        {
            curr = parent;
            parent = parent->_parent;
        }
        // 如果平衡因子为2或-2,进行旋转操作恢复平衡
        else if (parent->_bf == 2 || parent->_bf == -2) 
        {
            if (parent->_bf == -2 && curr->_bf == -1)
                RotateR(parent); // 右旋
            else if (parent->_bf == 2 && curr->_bf == 1)
                RotateL(parent); // 左旋
            else if (parent->_bf == -2 && curr->_bf == 1)
                RotateLR(parent); // 先左后右旋转
            else if (parent->_bf == 2 && curr->_bf == -1)
                RotateRL(parent); // 先右后左旋转
            break;
        }
        else { 
            assert(false); // 树构建出错,触发断言
        }
    }
    return true; // 返回插入成功
}

AVL树的旋转

先看如图所示的树结构的抽象图,节点下方的矩形代表多种可能,分别是a,b,c子树,其高度都是h。

旋转的方式有四种,目的是在搜索树规则下平衡二叉树,平衡的结果就是树的整体高度减1,提高搜索效率。

旋转后树中各节点的平衡因子达到最佳状态,不需要继续向上更新平衡因子。

总的来说,左旋和右旋是一个相对的概念,但是只要记住:

对一个节点左旋的时候,意味着将这个节点变成左节点,如果这个节点是根节点的话,那么也变成左节点,他的右节点就是这个节点的根节点

右单旋

在这里插入图片描述

左树新增节点,高度+1,导致父节点bf=–1,爷节点bf=–2。此时平衡被破坏,就会引发右单旋。

右单旋就是把bf=-2的节点旋转至bf=-1的节点的右子树上。此时,bf=-1的节点是否存在右子树,有两种情况但可以统一处理。
在这里插入图片描述

  1. 先把bf=-1的节点的右子树链接到bf=-2的节点的左边,
  2. 再将bf=-2的节点链接到bf=-1的节点的右边。
  3. 最后bf=-1的节点作当前树的根,和整棵树链接。

在这里插入图片描述

void rotate_r(node* parent)
{
    // 获取父节点的左子节点和左子节点的右子节点
    node* subl = parent->_left;
    node* sublr = subl->_right;

    // 将父节点的左子节点设为左子节点的右子节点
    parent->_left = sublr;
    if (sublr) sublr->_parent = parent; // 如果左子节点的右子节点存在,将其父节点设为父节点

    // 获取父节点的父节点
    node* pparent = parent->_parent;

    // 将左子节点的右子节点设为父节点,父节点设为左子节点
    subl->_right = parent;
    parent->_parent = subl;

    // 如果旋转后父节点变成根节点
    if (parent == _root)
    {
        _root = subl; // 更新根节点为左子节点
        subl->_parent = nullptr; // 左子节点的父节点设为nullptr,因为成为了根节点
    }
    else
    {
        // 否则,根据父节点是其父节点的左子节点还是右子节点,连接左子节点和父节点
        if (pparent->_left == parent)
            pparent->_left = subl;
        else
            pparent->_right = subl;
        subl->_parent = pparent; // 左子节点的父节点设为父节点的父节点
    }

    // 设置旋转后节点的平衡因子为0,因为整棵子树平衡
    parent->_bf = 0;
    subl->_bf = 0;
}

左单旋

在这里插入图片描述

左单旋和右单旋正好相反。左单旋就是把bf=2的节点旋转至bf=1的节点的左子树上

在这里插入图片描述

  1. 先把bf=1的节点的左子树链接到bf=2的节点的右边。
  2. 再将bf=2的节点链接到bf=1的节点的左边。
  3. 最后bf=-1的节点作当前树的根,和整棵树链接。

在这里插入图片描述

void rotate_l(node* parent)
{
    // 获取父节点的右子节点和右子节点的左子节点
    node* subr = parent->_right;
    node* subrl = subr->_left;

    // 将父节点的右子节点设为右子节点的左子节点
    parent->_right = subrl;
    if (subrl) subrl->_parent = parent; // 如果右子节点的左子节点存在,将其父节点设为父节点

    // 获取父节点的父节点
    node* pparent = parent->_parent;

    // 将右子节点的左子节点设为父节点,父节点设为右子节点
    subr->_left = parent;
    parent->_parent = subr;

    // 如果旋转后父节点变成根节点
    if (parent == _root)
        _root = subr; // 更新根节点为右子节点
    else
    {
        // 否则,根据父节点是其父节点的左子节点还是右子节点,连接右子节点和父节点
        if (pparent->_left == parent)
            pparent->_left = subr;
        else
            pparent->_right = subr;
    }
    subr->_parent = pparent; // 右子节点的父节点设为父节点的父节点

    // 设置旋转后节点的平衡因子为0,因为整棵子树平衡
    parent->_bf = 0;
    subr->_bf = 0;
}

左右双旋

左单旋右单旋分别是左高左旋和右高右旋。

左右双旋的情况如下图所示,对于下半部分来说左边高,对于上半部分来说右边高

在这里插入图片描述

  1. 先以bf=1的节点为轴进行左单旋
  2. 再以bf=-2的节点为轴进行右单旋
void RotateLR(Node* parent) { // 双旋就是由两个单旋组成
    RotateL(parent->_left);
    RotateR(parent);
    //...
}
更新平衡因子

两个单旋会把节点的平衡因子都变成0,显然是不正确的。根据插入节点的位置不同,左右双旋的平衡因子更新有三种情况:

可以通过subLR节点的平衡因子的值来判断三种情况。不管树有多高,我们只在乎新节点在subLR的左右。

插入情况如何判断结果
subLR本身就是新节点subLR.bf=0parent.bf=0,subL.bf=0,subLR.bf=0
新节点在subLR的左边subLR.bf=-1parent.bf=0,subL.bf=-1,subLR.bf=0
新节点在subLR的右边subLR.bf=1parent.bf=1,subL.bf=0,subLR.bf=0

在这里插入图片描述

void rotate_lr(node* parent)
{
    node* subl = parent->_left;
    node* sublr = subl->_right;

    int bf = sublr->_bf; // 记录sublr的平衡因子用以判断

    rotate_l(parent->_left);
    rotate_r(parent);

    if (bf == 0) // sublr就是新节点
    {
        parent->_bf = 0;
        subl->_bf = 0;
        sublr->_bf = 0;
    }
    else if (bf == 1) // 插入在sublr的左边
    {
        parent->_bf = 0;
        subl->_bf = -1;
        sublr->_bf = 0;
    }
    else if (bf == -1) // 插入在sublr的右边
    {
        parent->_bf = 1;
        subl->_bf = 0;
        sublr->_bf = 0;
    }
    else
    {
        assert(false);
    }
}
右左双旋

右左双旋和左右双旋正好相反,对于下半部分来说右边高,对于上半部分来说是左边高

  1. 先以bf=-1的节点为轴进行右单旋
  2. 再以bf=2的节点为轴进行左单旋

在这里插入图片描述

更新平衡因子

右左双旋同样存在如下三种情况:

插入情况如何判断结果
subRL本身就是新节点subRL.bf=0parent.bf=0,subR.bf=0,subRL.bf=0
新节点在subRL的左边subRL.bf=-1parent.bf=0,subR.bf=1,subRL.bf=0
新节点在subRL的右边subRL.bf=1parent.bf=-1,subR.bf=0,subRL.bf=0

在这里插入图片描述

void rotate_rl(node* parent)
{
    node* subr = parent->_right;
    node* subrl = subr->_left;

    int bf = subrl->_bf;

    rotate_r(parent->_right);
    rotate_l(parent);

    if (bf == 0)
    {
        parent->_bf = 0;
        subr->_bf = 0;
        subrl->_bf = 0;
    }
    else if (bf == -1)
    {
        parent->_bf = 0;
        subr->_bf = 1;
        subrl->_bf = 0;
    }
    else if (bf == 1)
    {
        parent->_bf = -1;
        subr->_bf = 0;
        subrl->_bf = 0;
    }
    else
    {
        assert(false);
    }
}

AVL树的验证

void inorder()
{
    _inorder(_root);
    cout << endl;
}
void _inorder(node* root)
{
    if (!root) return;

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

bool is_balance()
{
    return _is_balance(_root);
}
bool _is_balance(node* root)
{
    if (!root)
        return true;

    int lh  = height(root->_left);
    int rh = height(root->_right);

    if (rh - lh != root->_bf)
    {
        cout << root->_kv.first << "  but now:" << root->_bf << endl;
        cout << root->_kv.first << "should be:" << rh - lh << endl;
        return false;
    }

    return abs(rh - lh) < 2
        && _is_balance(root->_left) && _is_balance(root->_right);
}

int height(node* root)
{
    if (!root)
        return 0;

    int lh = height(root->_left);
    int rh = height(root->_right);

    return rh > lh ? rh + 1 : lh + 1;
}

 

2.2 红黑树

红黑树的定义

红黑树也是一种二叉搜索树,每个结点上都带有红或黑两种颜色。

通过限制整条从根到叶路径上的结点的着色方式,确保整棵树中最长路径的长度不超过最短路径的两倍,因而是接近平衡的

在这里插入图片描述

红黑树的性质
  1. 每个结点不是红色就是黑色。
  2. 根节点是黑色的。
  3. 红色节点的子结点必须都是黑色的。(不能出现连续的红色节点)
  4. 每条路径所含的黑色结点数量相等
  5. 每个空结点都是黑色的,空节点也认为是叶结点。
  6. 对于红黑树,我们认为从根到空算一条路径。
搜索效率推导

从红黑树的性质看,最短路径肯定全是黑色节点,最长路径肯定是黑红相间的。

假设黑节点数量为 X X X,则路径的长度满足 X ≤ p a t h _ l e n g t h ≤ 2 X X≤path\_length≤2X Xpath_length2X,即红黑树的高度满足 X ≤ h ≤ 2 X X≤h≤2X Xh2X

首先完全二叉树的高度和节点数量的关系是 2 h − 1 = N 2^h-1=N 2h1=N。推导到红黑树的节点个数满足:
2 X − 1 ≤ N ≤ 2 2 X − 1 = > 1 2 × l o g 2 N ≤ X ≤ l o g 2 N = > l o g 4 N ≤ X ≤ l o g 2 N 2^{X}-1≤N≤2^{2X}-1\\ => \frac{1}{2}×log_2N≤X≤log_2N \quad => \quad log_4N≤X≤log_2N 2X1N22X1=>21×log2NXlog2N=>log4NXlog2N

红黑树的搜索效率为 l o g N ≤ O ( N ) ≤ 2 l o g N logN≤O(N)≤2logN logNO(N)2logN

红黑树是接近平衡,AVL树是严格平衡。但CPU的速度快,二者差距不明显,且维护AVL树结构更花时间,所以红黑树应用更多。

红黑树的结构
enum COLOR {
    RED,
    BLACK
};

template <class K, class V>
struct rbtree_node {
    rbtree_node* _left;
    rbtree_node* _right;
    rbtree_node* _parent;
    pair<K, V> _kv;
    COLOR _col;
};

template <class K, class V> 
class rb_tree {
    rbtree_node<K, V>* _root;
    // ...
};

红黑树的插入

红黑树也是搜索二叉树,插入的步骤都是一样的,不同的是维护插入后树的结构。

由于红黑树的性质,每条路径的黑节点数量必须相同,故新插入节点统一采用红色,对整个树的影响最小

 bool insert(const KV& kv) {
        if (_root == nullptr) {
            // 如果根节点为空,创建新节点作为根节点,并设为黑色
            _root = new Node(kv);
            _root->_col = BLACK;
            return true;
        }

        Node* parent = nullptr;
        Node* cur = _root;

        // 寻找插入位置
        while (cur) {
            K key = cur->_kv.first;
            if (key < kv.first) {
                parent = cur;
                cur = cur->_right;
            }
            else if (key > 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;


    // 控制平衡
    // ...
}

处理红黑树我们需要确认三个节点:插入新节点 c u r cur cur、父节点 p p p、叔节点 u u u、爷节点 g g g

插入后,如果父节点是黑节点,则无需处理。只有当父节点是红节点时,就出现了连续红节点,需要处理

情况解决方案
p为红,g为黑,u为红变色
p为红,g为黑,u为黑或不存在旋转+变色
变色情况

p为红,g为黑,u为红

  • 将父节点叔节点变黑,爷节点变红。
  • cur指向爷节点,继续向上遍历。直到父节点为空或条件不满足。

在这里插入图片描述

旋转情况

p为红,g为黑,u为黑或不存在

此时我们根据爷父子三个节点呈现出的“形状”而选择旋转方式。

情况旋转方案
子是父的左,父是爷的左右单旋
子是父的右,父是爷的右左单旋
子是父的右,父是爷的左左右双旋
子是父的左,父是爷的右右左双旋

旋转后需要改色,上面的一个改为黑色,下面的两个改红色。

在这里插入图片描述

代码实现
 bool insert(const KV& kv) {
        if (_root == nullptr) {
            // 如果根节点为空,创建新节点作为根节点,并设为黑色
            _root = new Node(kv);
            _root->_col = BLACK;
            return true;
        }

        Node* parent = nullptr;
        Node* cur = _root;

        // 寻找插入位置
        while (cur) {
            K key = cur->_kv.first;
            if (key < kv.first) {
                parent = cur;
                cur = cur->_right;
            }
            else if (key > 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* grandfather = parent->_parent;
            if (parent == grandfather->_left) {
                Node* uncle = grandfather->_right;
                if (uncle && uncle->_col == RED) {
                    // Case 1: 叔叔节点是红色
                    parent->_col = uncle->_col = BLACK;
                    grandfather->_col = RED;
                    cur = grandfather; // 向上继续调整
                    parent = cur->_parent;
                }
                else {
                    // Case 2: 叔叔节点是黑色,插入节点是右孩子
                    if (cur == parent->_right) {
                        RotateL(grandfather); // 左旋
                        parent->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    else {
                        // Case 3: 叔叔节点是黑色,插入节点是左孩子
                        RotateR(grandfather); // 右旋
                        cur->_col = BLACK;
                        grandfather = RED;
                    }
                }
            }
            else {
                // 对称情况,父节点是祖父节点的右孩子
                if (parent == grandfather->_right) {
                    Node* uncle = grandfather->_left;
                    if (uncle && uncle->_col == RED) {
                        // Case 1: 叔叔节点是红色
                        parent->_col = uncle->_col = BLACK;
                        grandfather->_col = RED;
                        cur = grandfather; // 向上继续调整
                        parent = cur->_parent;
                    }
                    else {
                        // Case 2: 叔叔节点是黑色,插入节点是左孩子
                        if (cur == parent->_left) {
                            RotateR(grandfather); // 右旋
                            parent->_col = BLACK;
                            grandfather->_col = RED;
                        }
                        else {
                            // Case 3: 叔叔节点是黑色,插入节点是右孩子
                            RotateL(parent); // 左旋
                            RotateR(grandfather); // 右旋
                            cur->_col = BLACK;
                            grandfather = RED;
                        }
                    }
                    break;
                }
            }
        }
        _root->_col = BLACK;
        return true;
    }

private:
 // 左旋操作
    void RotateL(Node* parent) {
        Node* cur = parent->_right;
        Node* curleft = cur->_left;

        // 执行左旋操作
        parent->_right = curleft;
        if (curleft) {
            curleft->_parent = parent;
        }
        cur->_left = parent;

        Node* grandfather = parent->_parent;
        parent->_parent = cur;
        if (parent == _root) {
            _root = cur;
            cur->_parent = nullptr;
        }
        else {
            if (grandfather->_left == parent) {
                grandfather->_left = cur;
            }
            else {
                grandfather->_right = cur;
            }
            cur->_parent = grandfather;
        }
    }

    // 右旋操作
    void RotateR(Node* parent) {
        Node* cur = parent->_left;
        Node* curright = cur->_right;

        // 执行右旋操作
        parent->_left = curright;
        if (curright) {
            curright->_parent = parent;
        }

        Node* grandfather = parent->_parent;
        cur->_right = parent;
        parent->_parent = cur;

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

红黑树的验证

// 检查红黑树的平衡性
    bool IsBalance() {
        return IsBalance(_root);
    }

    // 递归检查红黑树的平衡性
    bool IsBalance(Node* root) {
        if (root == nullptr)
            return true;

        if (root->_col != BLACK)
            return false;

        int blackcnt = 0;
        Node* cur = root;

        // 统计从根节点到叶子节点的黑色节点数目
        while (cur) {
            if (cur->_col == BLACK)
                ++blackcnt;
            cur = cur->_left;
        }

        // 检查每条路径的黑色节点数是否相同
        return CheckColor(root, 0, blackcnt);
    }

    // 递归检查每条路径的黑色节点数是否相同,并检查是否有相邻红色节点
    bool CheckColor(Node* root, int blacknum, int blackcnt) {
        if (root == nullptr) {
            // 达到叶子节点时,检查黑色节点数是否符合预期
            if (blacknum != blackcnt)
                return false;
            return true;
        }

        // 如果当前节点是黑色,增加黑色节点数计数
        if (root->_col == BLACK) {
            ++blacknum;
        }

        // 如果当前节点是红色,并且其父节点也是红色,则违反了红黑树规则
        if (root->_col == RED && root->_parent && root->_parent->_col == RED) {
            cout << root->_kv.first << "出现连续红色节点" << endl;
            return false;
        }

        // 递归检查左右子树
        return CheckColor(root->_left, blacknum, blackcnt) && CheckColor(root->_right, blacknum, blackcnt);
    }

红黑树

 

3. 实现封装

map和set如何复用同一棵红黑树呢?

在这里插入图片描述

3.1 整体设计

template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
class map {
    typedef Key key_type;
    typedef pair<const Key, T> value_type;
    typedef rb_tree<key_type, value_type, 
                      select1st<value_type>, key_compare, Alloc> rep_type;
    rep_type t;
}

template <class Key, class Compare = less<Key>, class Alloc = alloc>
class set {
    typedef Key key_type;
    typedef Key value_type;
    typedef rb_tree<key_type, value_type, 
                  identity<value_type>, key_compare, Alloc> rep_type;
    rep_type t;  
}

template <class Key, class Value, class KeyOfValue, class Compare, class Alloc = alloc>
class rb_tree {
	typedef __rb_tree_node<Value> rb_tree_node;
}

template <class Value>
struct __rb_tree_node {
	Value value_field;
};


set -> rb_tree<K, K> -> rb_tree_node

set 中只存储键值的一部分,即键值本身就是存储在树节点中的数据。例如,当你向 set 中插入一个元素时,该元素会被作为键值对的键直接存储在红黑树的节点中。

map<K, V> -> rb_tree<K, pair<const K, V> > -> rb_tree_node<pair<const K, V> >

map 存储键值对,即每个节点除了存储键之外,还要存储值。当你插入一个键值对时,键作为红黑树节点的键存储,值则作为节点的一个额外数据存储。

模版参数作用
Key可以直接拿到Key类型,编译模版的时候需要确定Key类型
Value决定了树节点元素存储的数据的类型
KeyOfValue仿函数,用来获取Value中的Key值,如果是map就是取kv.first如果是set就是K
Compare仿函数,Key类型的比较函数
template<class V>
struct rbt_node
{};
template<class K, class V, class KeyOfValue, class Compare>
class rb_tree
{};

template<class K, class CmpOfKey = less<K>>
class set
{
    bool insert(const K& key) { t.insert(key); }
    class KeyOfVal { K& operator()(const K& key) { return key; } };
    rb_tree<K, K, KeyOfVal, CmpOfKey> t;
};

template<class K, class V, class CmpOfKey = less<K>>
class map
{
    bool insert(const pair<const K, V>& kv) { t.insert(kv); }
    class KeyOfVal { K& operator()(const pair<const K, V>& kv) { return kv.first; } };
    rb_tree<K, pair<const K, V>, KeyOfVal, CmpOfKey> t;
};

3.2 红黑树

默认函数

template<class V>
struct rbt_node
{
    rbt_node<V>* _left;
    rbt_node<V>* _right;
    rbt_node<V>* _parent;

    V _val;
    COLOR _col;

    rbt_node<V>(const V& val)
        : _left(nullptr), _right(nullptr), _parent(nullptr)
        , _val(val), _col(RED)
    {}
};

template<class K, class V, class KeyOfValue, class Compare>
class rb_tree
{
public:
    typedef rbt_node<V> node;

public:
    rb_tree()
    {}

    rb_tree(const rb_tree& t)
    {
        _root = copy(_root);
    }

    rb_tree& operator=(const rb_tree t)
    {
        if (this != &t)
        {
            swap(_root, t._root);
        }
        return *this;
    }

    ~rb_tree()
    {
        destroy(_root);
        _root = nullptr;
    }

private:
    node* copy(node* root)
    {
        if (!root)
            return nullptr;

        node* new_node = new node(root->_kv);
        new_node->_left = copy(root->_left);
        new_node->_right = copy(root->_right);
        return new_node;
    }

    void destroy(node* root)
    {
        if (!root)
            return;

        destroy(root->_left);
        destroy(root->_right);
        delete root;
    }
    
    bool insert(const V& val)
    {
        // ...
        if (_kov(cur->_val) < _kov(val))
        else if (_kov(cur->_val) > _kov(val))
        // ...
    }

private:
    KeyOfValue _kov;
    Compare _cmp;
    node* _root = nullptr;
};

插入函数

pair<iterator, bool> insert(const V& val)
{
    if (!_root)
    {
        _root = new node(val);
        _root->_col = BLACK;
        return {iterator(_root), true};
    }

    node* parent = nullptr;
    node* cur = _root;

    while (cur)
    {
        if (_cmp(_kov(cur->_val), _kov(val)))
        {
            parent = cur;
            cur = cur->_right;
        }
        else if (_cmp(_kov(val), _kov(cur->_val)))
        {
            parent = cur;
            cur = cur->_left;
        }
        else
        {
            return {iterator(cur), false};
        }
    }

    cur = new node(val);
    if (_cmp(_kov(parent->_val), _kov(val)))
        parent->_right = cur;
    else
        parent->_left = cur;
    cur->_parent = parent;

    while (parent && parent->_col == RED)
    {
        node* grandpa = parent->_parent;

        if (grandpa->_left == parent)
        {
            node* uncle = grandpa->_right;

            if (uncle && uncle->_col == RED)
            {
                parent->_col = uncle->_col = BLACK;
                grandpa->_col = RED;

                cur = grandpa;
                parent = cur->_parent;
            }
            else
            {
                if (parent->_left == cur)
                {
                    rotate_r(grandpa);
                    cur->_col = grandpa->_col = RED;
                    parent->_col = BLACK;
                }
                else
                {
                    rotate_l(parent);
                    rotate_r(grandpa);
                    grandpa->_col = parent->_col = RED;
                    cur->_col = BLACK;
                }
                break;
            }
        }
        else
        {
            node* uncle = grandpa->_left;

            if (uncle && uncle->_col == RED)
            {
                parent->_col = uncle->_col = BLACK;
                grandpa->_col = RED;

                cur = grandpa;
                parent = cur->_parent;
            }
            else
            {
                if (parent->_left == cur)
                {
                    rotate_r(parent);
                    rotate_l(grandpa);
                    grandpa->_col = parent->_col = RED;
                    cur->_col = BLACK;
                }
                else
                {
                    rotate_l(grandpa);
                    cur->_col = grandpa->_col = RED;
                    parent->_col = BLACK;
                }
                break;
            }
        }
    }

    _root->_col = BLACK;
    return {iterator(cur), true};
}

迭代器

template<class V, class Ref, class Ptr>
struct __rb_tree_iterator
{
    typedef __rb_tree_node<V> node;
    typedef __rb_tree_iterator<V, Ref, Ptr> self;
    
    node* _node = nullptr;

    __rb_tree_iterator<V, Ref, Ptr>(node* node)
        : _node(node)
    {}
    
    // support normal iter construt const iter
    __rb_tree_iterator(const __rb_tree_iterator<V, V&, V*>& it)
        : _node(it.node)
    {}

    Ref operator*()
    {
        return _node->_val;
    }
    Ptr operator->()
    {
        return &_node->_val;
    }

    bool operator==(const self& s)
    {
        return _node == s._node;
    }
    bool operator!=(const self& s)
    {
        return _node != s._node;
    }
};

库中是将根节点带个头节点,头节点的左孩子指向整个树的最左节点,右孩子指向整个树的最右节点;

在这里插入图片描述

  • 如果节点的右子树不为空,下一个位置就是节点的右子树的最左节点
  • 如果节点的右子树为空,向上遍历,找到某个节点,满足该节点是其父亲的左,该父亲就是下一个位置。
self& operator++()
{
    if (_node->_right)
    {
        node* left = _node->_right;
        while (left->_left)
        {
            left = left->_left;
        }
        _node = left;
    }
    else
    {
        node* cur = _node;
        node* parent = cur->_parent;

        while (parent && parent->_right == cur)
        {
            cur = parent;
            parent = cur->_parent;
        }
        _node = parent;
    }
    return *this;
}

self& operator--()
{
    if (!_node) assert(false); // can't do that

    if (_node->_left)
    {
        node* right = _node->_left;
        while (right->_right)
        {
            right = right->_right;
        }
        _node = right;
    }
    else
    {
        node* cur = _node;
        node* parent = cur->_parent;

        while (parent && parent->_left == cur)
        {
            cur = parent;
            parent = cur->_parent;
        }
        _node = parent;
    }
    return *this;
}

在这里插入图片描述

3.3 set

template<class K, class Compare = less<K>>
class set
{
private:
    struct KeyOfVal {
        const K& operator()(const K& key) { return key; }
    };

public:
    typedef rb_tree<K, K, KeyOfVal, Compare> rep_type;
    typedef typename rep_type::const_iterator iterator; // use const iter
    typedef typename rep_type::const_iterator const_iterator;

public:
    pair<iterator, bool> insert(const K& key) { return _t.insert(key); }
    iterator find(const K& key) { return _t.find(key); }
    void inorder() { _t.inorder(); }

public:
    iterator begin() { return _t.begin(); }
    iterator end() { return _t.end(); }

private:
    rb_tree<K, K, KeyOfVal, Compare> _t;
};

3.4 map

template<class K, class V, class Compare = less<K>>
class map
{
private:
    struct KeyOfVal {
        const K& operator()(const pair<const K, V>& kv) { return kv.first; }
    };

public:
    typedef rb_tree<K, pair<const K, V>, KeyOfVal, Compare> rep_type;
    typedef typename rep_type::iterator iterator;
    typedef typename rep_type::const_iterator const_iterator;

public:
    iterator find(const K& key) { return _t.find(key); }
    pair<iterator, bool> insert(const pair<const K, V>& kv) { return _t.insert(kv); }
    V& operator[](const K& key) { return _t.insert(make_pair(key, V())).first->second; }
    void inorder() { _t.inorder(); }

    iterator begin() { return _t.begin(); }
    iterator end() { return _t.end(); }

private:
    rep_type _t;
};
  • 23
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SuhyOvO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值