<C++> 红黑树模拟实现map和set

news2024/11/25 1:45:21

使用一颗红黑树同时封装map和set。

红黑树源码

#pragma once
#include <cassert>
#include <iostream>
#include <utility>
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;//结点颜色
    // 在单参数构造函数中使用 explicit 关键字是一种好的编程习惯,可以提高代码的可读性和健壮性。
    // 加上 explicit 关键字,以避免出现不必要的隐式类型转换。如果没有加上 explicit 关键字,那么可以使用该构造函数创建一个 RBTreeNode 对象时,会发生隐式类型转换,将一个 pair<K, V> 类型的对象转换为 RBTreeNode 对象,这可能导致程序行为出现意外的结果。
    explicit RBTreeNode(const pair<K, V> &kv)
        : _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED) {
    }
};

template<class K, class V>
class RBTree {
    typedef RBTreeNode<K, V> Node;

public:
    // 析构函数
    ~RBTree() {
        Destroy(this->_root);
        this->_root = nullptr;
    }

    Node *find(const K &key) {
        Node *cur = this->_root;
        while (cur) {
            if (key > cur->_kv.first) {
                cur = cur->_right;
            } else if (key < cur->_kv.first) {
                cur = cur->_left;
            } else {
                return cur;
            }
        }
        return nullptr;
    }

    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 (kv.first > cur->_kv.first) {
                parent = cur;
                cur = cur->_right;
            } else if (kv.first < cur->_kv.first) {
                parent = cur;
                cur = cur->_left;
            } else {
                // 相等则不插入
                return false;
            }
        }
        // cur走到了合适的位置
        cur = new Node(kv);
        // 选择插入到parent的左边还是右边
        if (kv.first < parent->_kv.first) {
            parent->_left = cur;
        } else {
            parent->_right = cur;
        }
        // cur链接parent
        cur->_parent = parent;
        // parent存在且parent的节点为红色的(意味着,循环往上调整到parent不存在或者parent为黑就不用调整了)
        // 红黑树性质:红色结点的孩子必须是黑色的
        while (parent && parent->_col == RED) {
            Node *grandfather = parent->_parent;
            // 如果爷爷的左边是父亲,那么爷爷的右边就是叔叔
            if (grandfather->_left == parent) {
                Node *uncle = grandfather->_right;
                // 情况1:u存在且为红,变色处理,并继续往上处理
                if (uncle && uncle->_col == RED) {
                    // 调整parent变黑,uncle变黑,grandfather变红
                    parent->_col = BLACK;
                    uncle->_col = BLACK;
                    grandfather->_col = RED;

                    // 继续往上调整,此时的grandfather作为新插入结点,继续判断他的父亲是否是红色结点
                    // 重置parent,先将grandfather位置看成新增结点cur
                    cur = grandfather;
                    parent = cur->_parent;
                } else {// 情况2+3  u不存在/u存在且为黑,旋转+变色
                    //插入结点是父亲的左孩子
                    if (cur == parent->_left) {
                        //     g
                        //   p   u
                        // c
                        // g p c成为一条直线,并且cur在parent的左边
                        // 需要右旋+变色,右旋后parent成为根,需要变黑,grandfather变为parent的右孩子,需要变红
                        RotateRight(grandfather);
                        parent->_col = BLACK;
                        grandfather->_col = RED;
                    } else {
                        //     g
                        //   p   u
                        //     c
                        // 当cur为parent的右边时,需要左旋+右旋+变色
                        RotateLeft(parent);
                        RotateRight(grandfather);// 右旋cur成为新的根,变为黑色,grandfather变为cur孩子,变为红色
                        cur->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    break;
                }
            } else {//(grandfather->_right == parent)  如果爷爷的右边是父亲,那么爷爷的左边就是叔叔
                //    g
                //  u   p
                //        c
                Node *uncle = grandfather->_left;
                // 情况1:u存在且为红,变色处理,并继续往上处理
                if (uncle && uncle->_col == RED) {
                    parent->_col = BLACK;
                    uncle->_col = BLACK;
                    grandfather->_col = RED;

                    // 继续往上调整
                    cur = grandfather;
                    parent = cur->_parent;
                } else// 情况2+3:u不存在/u存在且为黑,旋转+变色
                {
                    //    g
                    //  u   p
                    //        c
                    if (cur == parent->_right) {
                        RotateLeft(grandfather);
                        parent->_col = BLACK;
                        grandfather->_col = RED;
                    } else {
                        //     g
                        //  u     p
                        //     c
                        RotateRight(parent);
                        RotateLeft(grandfather);
                        //parent的位置没变不需要变色
                        cur->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    break;
                }
            }
        }
        // 最后的根变为黑节点
        _root->_col = BLACK;
        return true;
    }

    void InOrder() {
        InOrder(this->_root);
    }

    bool IsBalance() {
        if (_root && _root->_col == RED) {
            cout << "根节点颜色是红色" << endl;
            return false;
        }

        int benchmark = 0;// 基准值,任选一条做,用于比较每条节点黑色节点相同,如果不相同则说明不平衡
        Node *cur = this->_root;
        while (cur) {
            if (cur->_col == BLACK)
                benchmark++;
            cur = cur->_left;
        }
        // 连续红色节点
        return _check(this->_root, 0, benchmark);
    }

    int Height() {
        return Height(this->_root);
    }

private:
    void Destroy(Node *root) {
        if (root == nullptr) {
            return;
        }
        // 后序销毁
        Destroy(root->_left);
        Destroy(root->_right);
        delete root;
    }

    int Height(Node *root) {
        if (root == nullptr)
            return 0;

        int leftHeight = Height(root->_left);
        int rightHeight = Height(root->_right);

        return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
    }

    bool _check(Node *root, int BlackNum, int benchmark) {
        // 检查不能存在连续的红色节点
        // benchmark基准值
        if (root == nullptr) {
            if (benchmark != BlackNum) {
                cout << "某条路径黑色节点的数量不相等" << endl;
                return false;
            }

            return true;
        }

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

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

        return _check(root->_left, BlackNum, benchmark) && _check(root->_right, BlackNum, benchmark);
    }

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

        InOrder(root->_left);
        cout << root->_kv.first << " ";
        InOrder(root->_right);
    }

    // 左单旋
    void RotateLeft(Node *parent) {
        Node *subR = parent->_right;// 要旋转的parent的右子树
        Node *subRL = subR->_left;  // 子树的左子树

        // 旋转链接
        parent->_right = subRL;
        if (subRL)
            subRL->_parent = parent;

        // 需要记录要旋转的树还有没有父亲
        Node *ppnode = parent->_parent;

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

        // 如果ppnode为nullptr,说明parent一开始为根,旋转后subR为根
        if (ppnode == nullptr) {
            // 更新根节点
            _root = subR;
            _root->_parent = nullptr;
        } else {
            if (ppnode->_left == parent) {
                ppnode->_left = subR;
            } else {
                ppnode->_right = subR;
            }
            subR->_parent = ppnode;
        }
    }

    // 右单旋
    void RotateRight(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 (ppnode == nullptr) {
            _root = subL;
            _root->_parent = nullptr;
        } else {
            if (ppnode->_left == parent) {
                ppnode->_left = subL;
            } else {
                ppnode->_right = subL;
            }
            subL->_parent = ppnode;
        }
    }

private:
    Node *_root;
};

红黑树模板参数的控制

我们都知道,set是K模型的容器,而map是KV模型的容器,那我们如何用一棵KV模型的红黑树同时实现map和set呢?

这里我们就需要控制map和set传入底层红黑树的模板参数,为了与原红黑树的模板参数进行区分,我们将红黑树第二个模板参数的名字改为T。

template<class K, class T>
class RBTree

T模板参数可能只是键值Key,也可能是由Key和Value共同构成的键值对。如果是set容器,那么它传入底层红黑树的模板参数就是Key和Key:

template<class K>
class set{
public:
	//...
private:
	RBTree<K, K> _t;
};

但如果是map容器,那么它传入底层红黑树的模板参数就是Key以及Key和Value构成的键值对:

template<class K, class V>
class map{
public:
	//...
private:
	RBTree<K, pair<const K, V>> _t;
};

那能不能不要红黑树的第一个模板参数,只保留第二个模板参数呢?

乍眼一看好像是可以的,因为此时红黑树的第二个模板参数当中也是有键值Key的,但实际上红黑树的第一个模板参数是不能省略的。

对于set容器来说,省略红黑树的第一个参数当然没问题,因为set传入红黑树的第二个参数与第一个参数是一样的。但是对于map容器来说就不行了,因为map容器所提供的接口当中有些是只要求给出键值Key的,比如find和erase。

红黑树结点当中存储的数据

现在红黑树的模板参数变成了K和T,那么红黑树结点当中存储的应该是什么呢?

前面说到,由于上层容器的不同,底层红黑树当中的K和T也是不同的:

  • set容器:K和T都代表键值Key。
  • map容器:K代表键值Key,T代表由Key和Value构成的键值对。

对于set容器来说,底层红黑树结点当中存储K和T都是一样的,但是对于map容器来说,底层红黑树就只能存储T了。由于底层红黑树并不知道上层容器到底是map还是set,因此红黑树的结点当中直接存储T就行了。

这样一来,当上层容器是set的时候,结点当中存储的是键值Key;当上层容器是map的时候,结点当中存储的就是<Key, Value>键值对。
在这里插入图片描述

模板参数中仿函数的增加

现在由于结点当中存储的是T,这个T可能是Key,也可能是<Key, Value>键值对。那么当我们需要进行结点的键值比较时,应该如何获取结点的键值呢?

当上层容器是set的时候T就是键值Key,直接用T进行比较即可,但当上层容器是map的时候就不行了,此时我们需要从<Key, Value>键值对当中取出键值Key后,再用Key值进行比较。

因此,上层容器map需要向底层红黑树提供一个仿函数,用于获取T当中的键值Key,这样一来,当底层红黑树当中需要比较两个结点的键值时,就可以通过这个仿函数来获取T当中的键值了。

map中的仿函数

template<class K, class V>
class map {
    //用于比较方式
    struct MapKeyOfT {
        const K &operator()(const pair<const K, V> &key) {
            return key.first;
        }
    };
    
private:
    RBTree<K, pair<const K, V>, MapKeyOfT> _t;//const K 控制k不可修改 传给模板T
};

但是对于底层红黑树来说,它并不知道上层容器是map还是set,因此当需要进行两个结点键值的比较时,底层红黑树都会通过传入的仿函数来获取键值Key,进而进行两个结点键值的比较。

因此,set容器也需要向底层红黑树传入一个仿函数,虽然这个仿函数单独看起来没什么用,但却是必不可少的。

template<class K>
class set {
    //用于比较方式
    struct SetKeyOfT {
        const K &operator()(const K &key) {
            return key;
        }
    };

private:
    RBTree<K, K, SetKeyOfT> _t;//封装的红黑树 ,第二个K是没用的,为的是匹配map
};

在这里插入图片描述

这样一来,当底层红黑树需要进行两个结点之间键值的比较时,都会通过传入的仿函数来获取相应结点的键值,然后再进行比较,下面以红黑树的查找函数为例:

Node *Find(const T &key) {
    Node *cur = this->_root;
    KeyOfT kot;
    while (cur) {
        if (key > kot(cur->_data)) {
            cur = cur->_right;
        } else if (key < kot(cur->_data)) {
            cur = cur->_left;
        } else {
            return cur;
        }
    }
    return nullptr;
}

注意: 所有进行结点键值比较的地方,均需要通过仿函数获取对应结点的键值后再进行键值的比较。

正向迭代器的实现

红黑树的正向迭代器实际上就是对结点指针进行了封装,因此在正向迭代器当中实际上就只有一个成员变量,那就是正向迭代器所封装结点的指针。

template<class T, class Ref, class Ptr>
struct __RBTreeIterator {
    typedef RBTreeNode<T> Node;
    typedef __RBTreeIterator<T, Ref, Ptr> Self;
    
    Node *_node;// 正向迭代器所封装结点的指针
};

因此,我们通过一个结点的指针便可以构造出一个正向迭代器。

//构造函数
__RBTreeIterator(Node *node)
    : _node(node) 
}

当对正向迭代器进行解引用操作时,我们直接返回对应结点数据的引用即可。

Ref operator*() {
    return _node->_data;
}

当对正向迭代器进行->操作时,我们直接返回对应结点数据的指针即可。

Ptr operator->(){
	return &_node->_data;
}

当然,正向迭代器当中至少还需要重载==!=运算符,实现时直接判断两个迭代器所封装的结点是否是同一个即可。

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

bool operator==(const Self &s) const {
    return _node == s._node;//判断两个正向迭代器所封装的结点是否是同一个
}

红黑树正向迭代器实现时,真正的难点实际上++--运算符的重载。

在这里插入图片描述

实现红黑树的正向迭代器时,一个结点的正向迭代器进行++操作后,应该根据红黑树中序遍历的序列找到当前结点的下一个结点。

具体逻辑如下

  1. 如果当前结点的右子树不为空,则++操作后应该找到其右子树当中的最左结点。
  2. 如果当前结点的右子树为空,则++操作后应该在该结点的祖先结点中,找到孩子不在父亲右的祖先。

代码如下

Self &operator++() {
    if (_node->_right) {
        // 1、右不为空,下一个就是右子树的最左节点
        Node *subLeft = _node->_right;
        while (subLeft->_left) {
            subLeft = subLeft->_left;
        }

        _node = subLeft;
    } else {
        // 2、右为空,沿着到根的路径,找孩子是父亲左的那个祖先
        Node *cur = _node;
        Node *parent = cur->_parent;
        // 如果parent不为NULL,或者cur是parent的右子树
        while (parent && cur == parent->_right) {
            // 继续走
            cur = parent;
            parent = parent->_parent;
        }
        _node = parent;// 下一个节点就是parent
    }
    return *this;
}

实现红黑树的正向迭代器时,一个结点的正向迭代器进行--操作后,应该根据红黑树中序遍历的序列找到当前结点的前一个结点。

具体逻辑如下:

  1. 如果当前结点的左子树不为空,则--操作后应该找到其左子树当中的最右结点。
  2. 如果当前结点的左子树为空,则--操作后应该在该结点的祖先结点中,找到孩子不在父亲左的祖先。

代码如下

// 反向 右子树 根 左子树
Self &operator--() {
    //++找左子树,--找右子树
    if (_node->_left) {
        // 1、左不为空,找左子树的最右节点
        Node *subRight = _node->_left;
        while (subRight->_right) {
            subRight = subRight->_right;
        }
        _node = subRight;
    } else {
        // 2、左为空,找孩子是父亲右的祖先
        Node *cur = _node;
        Node *parent = cur->_parent;
        // 如果parent!=null ,cur==parent的左就继续走
        while (parent && cur == parent->_left) {
            cur = parent;
            parent = parent->_parent;
        }
        _node = parent;
    }
    return *this;
}

正向迭代器实现后,我们需要在红黑树的实现当中进行迭代器类型的typedef。需要注意的是,为了让外部能够使用typedef后的正向迭代器类型iterator,我们需要在public区域进行typedef。

然后在红黑树当中实现成员函数begin和end:

  • begin函数返回中序序列当中第一个结点的正向迭代器,即最左结点。
  • end函返回中序序列当中最后一个结点下一个位置的正向迭代器,这里直接用空指针构造一个正向迭代器。
template<class K, class T, class KeyOfT>
class RBTree {
    typedef RBTreeNode<T> Node;

public:// 迭代器相关
    typedef __RBTreeIterator<T, T &, T *> iterator;
    typedef __RBTreeIterator<T, const T &, const T *> const_iterator;
    // 迭代器最开始应该是树的最左边(中序)
    iterator begin() {
        Node *cur = this->_root;
        while (cur && cur->_left) {
            cur = cur->_left;
        }

        return iterator(cur);
    }

    iterator end() {
        return iterator(nullptr);
    }

    const_iterator begin() const {
        Node *cur = _root;
        while (cur && cur->_left) {
            cur = cur->_left;
        }

        return const_iterator(cur);
    }

    const_iterator end() const {
        return const_iterator(nullptr);
    }

private:
    Node *_root;//红黑树的根结点
};

在C++STL中,底层红黑树实现正向迭代器时所用的结构。

实际上,上述所实现的迭代器是有缺陷的,因为理论上我们对end()位置的正向迭代器进行--操作后,应该得到最后一个结点的正向迭代器,但我们实现end()时,是直接返回由nullptr构造得到的正向迭代器的,因此上述实现的代码无法完成此操作。

下面我们来看看C++SLT库当中的实现逻辑:

在这里插入图片描述

C++STL库当中实现红黑树时,在红黑树的根结点处增加了一个头结点,该头结点的左指针指向红黑树当中的最左结点,右指针指向红黑树当中的最右结点,父指针指向红黑树的根结点。

在该结构下,实现begin()时,直接用头结点的左孩子构造一个正向迭代器即可,实现rbegin()时,直接用头结点的右孩子构造一个反向迭代器即可(实际是先用该结点构造一个正向迭代器,再用正向迭代器构造出反向迭代器),而实现end()和rend()时,直接用头结点构造出正向和反向迭代器即可。此后,通过对逻辑的控制,就可以实现end()进行–操作后得到最后一个结点的正向迭代器。

但实现该结构需要更改当前很多函数的逻辑,例如插入结点时,若插入到了红黑树最左结点的左边,或最右结点的右边,此时需要更新头结点左右指针的指向,这里就不进行实际实现了。

反向迭代器的实现

红黑树的反向迭代器实际上就是正向迭代器的一个封装,因此红黑树的反向迭代器就是一个迭代器适配器。

在反向迭代器当中只有一个成员变量,那就是反向迭代器封装的正向迭代器。反向迭代器的中成员函数,都是通过调用正向迭代器对应的函数来完成相应功能的。

//反向迭代器---迭代器适配器
template<class Iterator>
struct ReverseIterator {
    typedef ReverseIterator<Iterator> Self;  //反向迭代器的类型
    typedef typename Iterator::reference Ref;//结点指针的引用
    typedef typename Iterator::pointer Ptr;  //结点指针

    Iterator _it;//反向迭代器所封装的正向迭代器

    //构造函数
    ReverseIterator(Iterator it)
        : _it(it)//根据所给正向迭代器构造一个反向迭代器
    {}

    Ref operator*() {
        return *_it;//通过调用正向迭代器的operator*返回结点数据的引用
    }
    Ptr operator->() {
        return _it.operator->();//通过调用正向迭代器的operator->返回结点数据的指针
    }

    //前置++
    Self &operator++() {
        --_it;//调用正向迭代器的前置--
        return *this;
    }
    //前置--
    Self &operator--() {
        ++_it;//调用正向迭代器的前置++
        return *this;
    }

    bool operator!=(const Self &s) const {
        return _it != s._it;//调用正向迭代器的operator!=
    }
    bool operator==(const Self &s) const {
        return _it == s._it;//调用正向迭代器的operator==
    }
};

需要注意的是,反向迭代器只接收了一个模板参数,即正向迭代器的类型,也就是说,反向迭代器不知道结点的引用类型和结点的指针类型,因此我们需要在正向迭代器当中对这两个类型进行typedef,这样反向迭代器才能通过正向迭代器获取结点的引用类型和结点的指针类型。

//正向迭代器
template<class T, class Ref, class Ptr>
struct __TreeIterator{
	typedef Ref reference; //结点指针的引用
	typedef Ptr pointer; //结点指针
};

反向迭代器实现后,我们也需要在红黑树的实现当中进行迭代器类型的typedef,并在红黑树当中实现成员函数rbegin和rend:

  • rbegin函数返回中序序列当中最后一个结点的反向迭代器,即最右结点。
  • rend函数返回中序序列当中第一个结点前一个位置的反向迭代器,这里直接用空指针构造一个反向迭代器。
template<class K, class T, class KeyOfT>
class RBTree {
    typedef RBTreeNode<T> Node;//结点的类型
public:
    typedef ReverseIterator<iterator> reverse_iterator;//反向迭代器

    reverse_iterator rbegin() {
        //寻找最右结点
        Node *right = _root;
        while (right && right->_right) {
            right = right->_right;
        }
        //返回最右结点的反向迭代器
        return reverse_iterator(iterator(right));
    }
    reverse_iterator rend() {
        //返回由nullptr构造得到的反向迭代器(不严谨)
        return reverse_iterator(iterator(nullptr));
    }

private:
    Node *_root;//红黑树的根结点
};

红黑树改造完整代码

//RBTree.h
#pragma once
#include <iostream>
#include <utility>
using namespace std;

// 红黑树节点颜色
enum Colour {
    RED,
    BLACK,
};

template<class T>
struct RBTreeNode {
    RBTreeNode<T> *_left;
    RBTreeNode<T> *_right;
    RBTreeNode<T> *_parent;
    T _data;
    Colour _col;
    // 在单参数构造函数中使用 explicit 关键字是一种好的编程习惯,可以提高代码的可读性和健壮性。
    // 加上 explicit 关键字,以避免出现不必要的隐式类型转换。如果没有加上 explicit 关键字,那么可以使用该构造函数创建一个 RBTreeNode 对象时,会发生隐式类型转换,将一个 pair<K, V> 类型的对象转换为 RBTreeNode 对象,这可能导致程序行为出现意外的结果。
    RBTreeNode(const T &data)
        : _left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED) {}
};

// 迭代器
template<class T, class Ref, class Ptr>
struct __RBTreeIterator {
    typedef Ref reference;//结点指针的引用
    typedef Ptr pointer;  //结点指针

    typedef RBTreeNode<T> Node;
    typedef __RBTreeIterator<T, Ref, Ptr> Self;
    Node *_node;// 成员变量

    __RBTreeIterator(Node *node)
        : _node(node) {
    }

    // 根据RBTree的模板实例化传参,构造出普通迭代器和const迭代器
    //  1、typedef __RBTreeIterator<T, T&, T*> itertaor;  拷贝构造
    //  2、typedef __RBTreeIterator<T, const T&, const T*> const_itertaor;
    //  支持普通迭代器构造const迭代器的构造函数

    // 用普通模板T 构造出const类型的
    __RBTreeIterator(const __RBTreeIterator<T, T &, T *> &it)
        : _node(it._node) {
    }

    // Ref为引用
    Ref operator*() {
        return _node->_data;
    }

    // Ptr指针
    Ptr operator->() {
        return &_node->_data;
    }

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

    bool operator==(const Self &s) const {
        return _node == s._node;//判断两个正向迭代器所封装的结点是否是同一个
    }

    Self &operator++() {
        if (_node->_right) {
            // 1、右不为空,下一个就是右子树的最左节点
            Node *subLeft = _node->_right;
            while (subLeft->_left) {
                subLeft = subLeft->_left;
            }

            _node = subLeft;
        } else {
            // 2、右为空,沿着到根的路径,找孩子是父亲左的那个祖先
            Node *cur = _node;
            Node *parent = cur->_parent;
            // 如果parent不为NULL,或者cur是parent的右子树
            while (parent && cur == parent->_right) {
                // 继续走
                cur = parent;
                parent = parent->_parent;
            }
            _node = parent;// 下一个节点就是parent
        }
        return *this;
    }

    // 反向 右子树 根 左子树
    Self &operator--() {
        //++找左子树,--找右子树
        if (_node->_left) {
            // 1、左不为空,找左子树的最右节点
            Node *subRight = _node->_left;
            while (subRight->_right) {
                subRight = subRight->_right;
            }
            _node = subRight;
        } else {
            // 2、左为空,找孩子是父亲右的祖先
            Node *cur = _node;
            Node *parent = cur->_parent;
            // 如果parent!=null ,cur==parent的左就继续走
            while (parent && cur == parent->_left) {
                cur = parent;
                parent = parent->_parent;
            }
            _node = parent;
        }
        return *this;
    }
};

//反向迭代器---迭代器适配器
template<class Iterator>
struct ReverseIterator {
    typedef ReverseIterator<Iterator> Self;  //反向迭代器的类型
    typedef typename Iterator::reference Ref;//结点指针的引用
    typedef typename Iterator::pointer Ptr;  //结点指针

    Iterator _it;//反向迭代器所封装的正向迭代器

    //构造函数
    ReverseIterator(Iterator it)
        : _it(it)//根据所给正向迭代器构造一个反向迭代器
    {}

    Ref operator*() {
        return *_it;//通过调用正向迭代器的operator*返回结点数据的引用
    }
    Ptr operator->() {
        return _it.operator->();//通过调用正向迭代器的operator->返回结点数据的指针
    }

    //前置++
    Self &operator++() {
        --_it;//调用正向迭代器的前置--
        return *this;
    }
    //前置--
    Self &operator--() {
        ++_it;//调用正向迭代器的前置++
        return *this;
    }
    bool operator!=(const Self &s) const {
        return _it != s._it;//调用正向迭代器的operator!=
    }
    bool operator==(const Self &s) const {
        return _it == s._it;//调用正向迭代器的operator==
    }
};


// 仿函数,用于pair的比较
template<class K, class T, class KeyOfT>
class RBTree {
    typedef RBTreeNode<T> Node;//结点的类型

public:
    // 析构函数
    ~RBTree() {
        _Destroy(_root);
        _root = nullptr;
    }

public:// 迭代器相关
    typedef __RBTreeIterator<T, T &, T *> iterator;
    typedef __RBTreeIterator<T, const T &, const T *> const_iterator;

    typedef ReverseIterator<iterator> reverse_iterator;            //反向迭代器
    typedef ReverseIterator<const_iterator> reverse_const_iterator;//反向迭代器
    // 迭代器最开始应该是树的最左边(中序)
    iterator begin() {
        Node *cur = this->_root;
        while (cur && cur->_left) {
            cur = cur->_left;
        }

        return iterator(cur);
    }

    iterator end() {
        return iterator(nullptr);
    }

    const_iterator begin() const {
        Node *cur = _root;
        while (cur && cur->_left) {
            cur = cur->_left;
        }

        return const_iterator(cur);
    }

    const_iterator end() const {
        return const_iterator(nullptr);
    }

    reverse_iterator rbegin() {
        //寻找最右结点
        Node *right = _root;
        while (right && right->_right) {
            right = right->_right;
        }
        //返回最右结点的反向迭代器
        return reverse_iterator(iterator(right));
    }
    reverse_iterator rend() {
        //返回由nullptr构造得到的反向迭代器(不严谨)
        return reverse_iterator(iterator(nullptr));
    }

    reverse_const_iterator rbegin() const {
        //寻找最右结点
        Node *right = _root;
        while (right && right->_right) {
            right = right->_right;
        }
        //返回最右结点的反向迭代器
        return reverse_const_iterator(const_iterator(right));
    }
    reverse_const_iterator rend() const {
        //返回由nullptr构造得到的反向迭代器(不严谨)
        return reverse_const_iterator(const_iterator(nullptr));
    }

public:
    Node *Find(const T &key) {
        Node *cur = this->_root;
        KeyOfT kot;
        while (cur) {
            if (key > kot(cur->_data)) {
                cur = cur->_right;
            } else if (key < kot(cur->_data)) {
                cur = cur->_left;
            } else {
                return cur;
            }
        }
        return nullptr;
    }

    pair<iterator, bool> Insert(const T &data) {
        if (_root == nullptr) {
            _root = new Node(data);
            _root->_col = BLACK;

            return make_pair(iterator(_root), true);
        }

        KeyOfT kot;// 用于map和set的比较方式,KeyOft在上层传值,一个是key,一个是pair
        Node *parent = nullptr;
        Node *cur = _root;
        while (cur) {
            if (kot(data) > kot(cur->_data)) {
                parent = cur;
                cur = cur->_right;
            } else if (kot(data) < kot(cur->_data)) {
                parent = cur;
                cur = cur->_left;
            } else {
                // 相等则不插入
                return make_pair(iterator(cur), false);
            }
        }
        // cur走到了合适的位置
        cur = new Node(data);
        Node *newnode = cur;// 用于返回插入节点
        // 选择插入到parent的左边还是右边
        if (kot(data) < kot(parent->_data)) {
            parent->_left = cur;
        } else {
            parent->_right = cur;
        }
        // cur链接parent
        cur->_parent = parent;
        // parent存在且parent的节点为红色的(意味着,循环往上调整到parent不存在或者parent为黑就不用调整了)
        while (parent && parent->_col == RED) {
            Node *grandfather = parent->_parent;
            // 如果爷爷的左边是父亲,那么爷爷的右边就是叔叔
            if (grandfather->_left == parent) {
                Node *uncle = grandfather->_right;
                // 情况1:u存在且为红,变色处理,并继续往上处理
                if (uncle && uncle->_col == RED) {
                    // 调整parent变黑,uncle变黑,grandfather变红
                    parent->_col = BLACK;
                    uncle->_col = BLACK;
                    grandfather->_col = RED;

                    // 继续网上调整
                    // 重置parent,先将grandfather位置看成新增结点cur
                    cur = grandfather;
                    parent = cur->_parent;
                } else// 情况2+3  u不存在/u存在且为黑,旋转+变色
                {
                    if (cur == parent->_left) {
                        //     g
                        //   p   u
                        // c
                        // 如果cur在parent的左边,需要右旋+变色,右旋后parent成为根,需要变黑,grandfather变为parent的孩子,需要变红
                        RotateRight(grandfather);
                        parent->_col = BLACK;
                        grandfather->_col = RED;
                    } else {
                        //     g
                        //   p   u
                        //     c
                        // 当cur为parent的右边时,需要左旋+右旋+变色
                        RotateLeft(parent);
                        RotateRight(grandfather);// 右旋cur成为新的根,变为黑色,grandfather变为cur孩子,变为红色
                        cur->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    break;
                }
            } else//(grandfather->_right == parent)  如果爷爷的右边是父亲,那么爷爷的左边就是叔叔
            {
                //    g
                //  u   p
                //        c
                Node *uncle = grandfather->_left;
                // 情况1:u存在且为红,变色处理,并继续往上处理
                if (uncle && uncle->_col == RED) {
                    parent->_col = BLACK;
                    uncle->_col = BLACK;
                    grandfather->_col = RED;

                    // 继续往上调整
                    cur = grandfather;
                    parent = cur->_parent;
                } else// 情况2+3:u不存在/u存在且为黑,旋转+变色
                {
                    //    g
                    //  u   p
                    //        c
                    if (cur == parent->_right) {
                        RotateLeft(grandfather);
                        parent->_col = BLACK;
                        grandfather->_col = RED;
                    } else {
                        //    g
                        //  u   p
                        //    c
                        RotateRight(parent);
                        RotateLeft(grandfather);
                        cur->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    break;
                }
            }
        }
        // 最后的根变为黑节点
        _root->_col = BLACK;
        return make_pair(iterator(newnode), true);
    }

    void InOrder() {
        _InOrder(this->_root);
    }

    bool IsBalance() {
        if (_root && _root->_col == RED) {
            cout << "根节点颜色是红色" << endl;
            return false;
        }

        int benchmark = 0;
        Node *cur = _root;
        while (cur) {
            if (cur->_col == BLACK)
                ++benchmark;
            cur = cur->_left;
        }

        // 连续红色节点
        return _Check(_root, 0, benchmark);
    }

    int Height() {
        return Height(this->_root);
    }

private:
    void _Destroy(Node *root) {
        if (root == nullptr) {
            return;
        }
        // 后序销毁
        _Destroy(root->_left);
        _Destroy(root->_right);
        delete root;
    }

    int _Height(Node *root) {
        if (root == nullptr)
            return 0;

        int leftHeight = _Height(root->_left);
        int rightHeight = _Height(root->_right);

        return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
    }

    bool check(Node *root, int BlackNum, int benchmark) {
        // 检查不能存在连续的红色节点
        // benchmark基准值
        if (root == nullptr) {
            if (benchmark != BlackNum) {
                cout << "某条路径黑色节点的数量不相等" << endl;
                return false;
            }

            return true;
        }

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

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

        return check(root->_left, BlackNum, benchmark) && check(root->_right, BlackNum, benchmark);
    }

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

        KeyOfT kot;
        _InOrder(root->_left);
        cout << kot(root->_data) << " ";
        _InOrder(root->_right);
    }

    // 左单旋
    void RotateLeft(Node *parent) {
        Node *subR = parent->_right;// 要旋转的parent的右子树
        Node *subRL = subR->_left;  // 子树的左子树

        // 旋转链接
        parent->_right = subRL;
        if (subRL)
            subRL->_parent = parent;

        // 需要记录要旋转的树还有没有父亲
        Node *ppnode = parent->_parent;

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

        // 如果ppnode为nullptr,说明parent一开始为根,旋转后subR为根
        if (ppnode == nullptr) {
            // 更新根节点
            _root = subR;
            _root->_parent = nullptr;
        } else {
            if (ppnode->_left == parent) {
                ppnode->_left = subR;
            } else {
                ppnode->_right = subR;
            }
            subR->_parent = ppnode;
        }
    }

    // 右单旋
    void RotateRight(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 (parent == _root) {
            _root = subL;
            _root->_parent = nullptr;
        } else {
            if (ppnode->_left == parent) {
                ppnode->_left = subL;
            } else {
                ppnode->_right = subL;
            }
            subL->_parent = ppnode;
        }
    }

private:
    Node *_root = nullptr;
};

set的模拟实现

完成上述操作后,set的模拟实现也就很简单了,其中的成员函数都是调用底层红黑树的对应接口就行了,只不过需要注意将插入函数和查找函数返回值当中的结点指针改为迭代器即可。

#include "RBTree.h"
namespace phw {
    template<class K>
    class set {
        //用于比较方式
        struct SetKeyOfT {
            const K &operator()(const K &key) {
                return key;
            }
        };

    public:
        //typename告诉编译器iterator是一个类型,而不是一个成员变量
        typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;
        typedef typename RBTree<K, K, SetKeyOfT>::reverse_iterator reverse_iterator;//反向迭代器

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

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

        reverse_iterator rbegin() {
            return _t.rbegin();
        }
        reverse_iterator rend() {
            return _t.rend();
        }

        pair<iterator, bool> Insert(const K &key) {
            return _t.Insert(key);
        }

        //删除函数
        void erase(const K &key) {
            _t.Erase(key);
        }
        //查找函数
        iterator find(const K &key) {
            return _t.Find(key);
        }
        void InOrder() {
            _t.InOrder();
        }

    private:
        RBTree<K, K, SetKeyOfT> _t;//封装的红黑树 ,第二个K是没用的,为的是匹配map
    };
}// namespace phw

map的模拟实现

map的模拟实现也是相同的道理,其成员函数也是调用底层红黑树的对应接口,但对于map来说,除了将插入函数和查找函数返回值当中的结点指针改为迭代器之外,还需要实现[]运算符的重载函数。

#pragma once
#include "RBTree.h"


namespace phw {
    template<class K, class V>
    class map {
        //用于比较方式
        struct MapKeyOfT {
            const K &operator()(const pair<const K, V> &key) {
                return key.first;
            }
        };

    public:
        typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
        typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::reverse_iterator reverse_iterator;//反向迭代器

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

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

        reverse_iterator rbegin() {
            return _t.rbegin();
        }

        reverse_iterator rend() {
            return _t.rend();
        }

        V &operator[](const K &key) {
            pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));
            return ret.first->second;//first是迭代器
        }

        pair<iterator, bool> Insert(const pair<const K, V> &kv) {

            return _t.Insert(kv);
        }
        //删除函数
        void erase(const K &key) {
            _t.Erase(key);
        }
        //查找函数
        iterator find(const K &key) {
            return _t.Find(key);
        }

        void InOrder() {
            _t.InOrder();
        }

    private:
        RBTree<K, pair<const K, V>, MapKeyOfT> _t;//const K 控制k不可修改 传给模板T
    };
}// namespace phw

测试代码:

#include "map.h"
#include "set.h"
int main() {
    phw::set<int> s;
    s.Insert(1);
    s.Insert(5);
    s.Insert(3);
    s.Insert(8);
    s.Insert(8);
    s.InOrder();//1 3 5 8


    phw::map<string, int> map;
    map["a"] = 1;
    map["b"] = 2;
    map["c"] = 3;
    map["d"] = 4;
    map["e"] = 5;
    map.InOrder();// a b c d e
    return 0;
}

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

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

相关文章

分析常见数据结构在内存中的存储形式

本文会在x64dbg中分析vector,list,map的内存存储特点 目录 分析vector在内存中的存储形式 x32dbg分析vector数组 总结一下vector的内存布局 分析 list 在内存中的存储形式 x32dbg分析 list 数组 总结一下 list 的内存布局 分析map在内存中的存储形式 x32dbg分析map 总…

python爬虫爬取电影数据并做可视化

思路&#xff1a; 1、发送请求&#xff0c;解析html里面的数据 2、保存到csv文件 3、数据处理 4、数据可视化 需要用到的库&#xff1a; import requests,csv #请求库和保存库 import pandas as pd #读取csv文件以及操作数据 from lxml import etree #解析html库 from …

内网穿透工具 Cpolar 帮您实现用友U8 Cloud 的外网部署,一键畅享云端ERP

文章目录 前言1. 用户需求2. Cpolar内网穿透的安装和注册2.1 Cpolar云端设置2.2 Cpolar Web UI本地设置 3. 公网访问测试 前言 用友U8 Cloud是用友公司推出的一款云端ERP解决方案。它以云计算技术为基础&#xff0c;为企业提供全面的企业资源管理解决方案&#xff0c;涵盖了财…

主机存活检测脚本

原理演示 在命令行下用下面命令安装scap模块&#xff1a; python -m pip install scapyscapy与scrapy 有非常大的区别。 scapy 是一个Python 的第三方模块&#xff0c;被称为“网络神器”。scapy 模块能够发送、捕获、分析和铸造网络数据 sr1发送接收函数 如图&#xff0c;安…

AI绘画变现渠道:日入100+,推荐一个本人实操的方法

关于AI绘画变现&#xff0c;之前写了几篇相关的文章&#xff0c;需要的自己查阅&#xff1a; AI绘画&#xff1a;如何让图片开口说话生成视频&#xff1f;变现渠道有哪些&#xff1f; 无私分享我的AI绘画变现之路&#xff0c;普通人可实操可模仿 AI壁纸号一周增加上千粉丝&a…

二叉树题目:层数最深叶子结点的和

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;层数最深叶子结点的和 出处&#xff1a;1302. 层数最深叶子结点的和 难度 4 级 题目描述 要求 给定一个二叉树…

有效的括号(栈的高频面试题)

一、题目描述 题目连接&#xff1a;有效的括号 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺…

我的创作纪念日 · 开始创作的第128天~

我的创作纪念日 开始创作的第128天 1️⃣ 机缘2️⃣ 收获3️⃣ 日常4️⃣ 憧憬 1️⃣ 机缘 时光匆匆&#xff0c;春去秋来&#xff0c;2023年在CSDN下笔的128天已去&#xff0c;回想当初成为创作者的初心&#xff0c;现在的心境已截然不同。当时正值上家公司工作变动&#xf…

【大数据】Doris 构建实时数仓落地方案详解(二):Doris 核心功能解读

Doris 构建实时数仓落地方案详解&#xff08;二&#xff09;&#xff1a;Doris 核心功能解读 1.Doris 发展历程2.Doris 三大模型3.Doris 数据导入4.Doris 多表关联5.Doris 核心设计6.Doris 查询优化7.Doris 应对实时数仓的痛点 1.Doris 发展历程 Apache Doris 是由 百度 研发并…

华为云云耀云服务器L实例评测|用Python的Flask框架加Nginx实现一个通用的爬虫项目

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;CSDN领军人物&#xff0c;全栈领域优质创作者✌&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;2023年6月CSDN上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;AWS/阿里云资深使用…

QUIC协议报文解析(三)

在前面的两篇文字里我们简单介绍了QUIC的发展历史&#xff0c;优点以及QUIC协议的连接原理。本篇文章将会以具体的QUIC报文为例&#xff0c;详细介绍QUIC报文的结构以及各个字段的含义。 早期QUIC版本众多&#xff0c;主要有谷歌家的gQUIC&#xff0c;以及IETF致力于将QUIC标准…

数据结构之堆的结构与实现

目录 一、堆的概念及结构 1.1堆的概念 1.2堆的性质 1.3堆的结构 二、堆的实现 2.1堆向下调整算法&#xff08;父亲与孩子做比较&#xff09; 2.2堆的向上调整算法&#xff08;孩子与父亲做比较&#xff09; 2.3堆的创建&#xff08;向下建堆&#xff09; 2.4向下建堆的时…

26 WEB漏洞-XSS跨站之订单及Shell箱子反杀记

目录 xss平台及工具使用session与Cookie获取问题演示案例某营销订单系统XSS盲打_平台某Shell箱子系统XSS盲打_工具其他参考应用案例-后台权限维持工具Http/s数据包提交Postman使用 xss平台及工具使用 凡是有数据交互的地方&#xff0c;前端是接收数据的&#xff0c;后端是要把…

Android Kotlin 高阶详解

前言 本文主要讲述kotlin高阶相关的内容&#xff0c;如果对kotlin基础还不了解的&#xff0c; 可以参考文章Android Kotlin 基础详解_袁震的博客-CSDN博客 1&#xff0c;与Java的相互调用 1.1在kotlin中调用java代码 大多数的java代码都可以直接在kotlin中调用&#xff0c…

Spring Cloud Alibaba Nacos注册中心(单机)

文章目录 Spring Cloud Alibaba Nacos注册中心&#xff08;单机&#xff09;1. docker 安装 nacos&#xff08;先别着急&#xff09;2. 配置nacos持久化到mysql、2.1 properties 文件 3. java注册3.1 POM文件3.2 properties文件3.3 测试配置中心 4.注册中心4.1 配置文件4.2测试…

【八大经典排序算法】选择排序

【八大经典排序算法】选择排序 一、概述二、思路解读三、代码实现&#xff08;升序&#xff09;四、优化&#xff08;升序&#xff09; 一、概述 选择排序作为一种简单直观的排序算法&#xff0c;最早由美国计算机科学家 Donald Knuth 在1968年提出。 选择排序的思想是将数组…

小程序从无到有教学教程-- 01.重置华为云服务器Huawei Cloud EulerOS 2.0版本并且设置安全组

概述 专门拿了专栏来讲解&#xff0c;所以目录结构就比较简单了 文章目录 概述修改华为云操作系统选择Huawei Cloud EulerOS 2.0 镜像顺便配置华为安全组 修改华为云操作系统 这里选择华为最新的系统&#xff0c;不过也就2.0~ 选择Huawei Cloud EulerOS 2.0 镜像 这里记住密…

企业架构LNMP学习笔记61

Nginx作为tomcat的前段反向代理&#xff1a; 在实际业务环境中&#xff0c;用户是直接通过域名访问&#xff0c;基于协议一般是http、https等。默认tomcat运行在8080端口。一般会通过前端服务器反向代理到后端的tomcat的方式&#xff0c;来实现用户可以通过域名访问tomcat的we…

bat写的git命令大全(适合初学者)掌握命令行下的Git操作!

欢迎来到Git&#xff01;无论你是一位Git初学者&#xff0c;这个在命令大全将帮助你在命令行下熟练运用Git&#xff0c;提高版本控制和团队协作的效率。从基本的仓库管理到分支操作&#xff0c;从提交修改到远程仓库同步&#xff0c;这个命令大全涵盖了Git的各种常用功能和技巧…