STL容器之set类

img

STL容器之set类

1、序列式容器和关联式容器

C++中的容器是用来存储和组织数据的数据结构。容器可以分为序列式容器和关联式容器两大类。

  • 序列式容器:序列式容器按照元素在容器中的位置进行组织和访问。常见的序列式容器包括:

    1. vector: 动态数组,支持快速随机访问,插入和删除操作稍慢,尾部插入和删除操作速度较快。

    2. list: 双向链表,支持快速的插入和删除操作,但是随机访问较慢。

    3. deque: 双端队列,支持在头尾进行快速插入和删除操作,也支持随机访问。

    4. array: 静态数组,大小固定,不支持动态增长,但是支持随机访问。

    5. forward_list: 单向链表,只支持单向的迭代器,插入和删除操作快,但是无法进行逆向遍历。

  • 关联式容器:关联式容器使用键值对即< Key , Value >的形式来存储和访问数据,按照键值进行组织和查找。在数据检索时比序列式容器效率更高。常见的关联式容器包括:

    1. map: 键值对集合,按照键的大小进行排序,每个键都是唯一的,支持快速的查找操作。

    2. multimap: 键值对集合,允许键重复出现。

    3. set: 关键字集合,按照键的大小进行排序,每个键都是唯一的,不允许重复。

    4. multiset: 关键字集合,允许关键字重复出现。

    5. unordered_map: 哈希表实现的键值对集合,查找速度比map快,但是不保证元素的顺序。

    6. unordered_set: 哈希表实现的关键字集合,查找速度比set快,但是不保证元素的顺序。

    注意:1,2,3,4的结构是树形结构的关联式容器,用的是红黑树作为底层结构。5,6的结构是哈希结构的关联式容器,用的是哈希表作为底层结构(后面会讲)


2、键值对

在C++中,键值对是一种常见的数据结构,通常用于表示一对相关联的数据项。键(key)是一个唯一的标识符,用于查找和访问数据,而值(value)则是与键相关联的数据。键值对在关联式容器中被广泛使用,例如在map、multimap、set、multiset等容器中。

我们在二叉搜索树中有见到过键值对的概念,KV模型就是键值对模型。

有个专门存键值对的结构:pair

template<typename T1, typename T2>
struct pair {
    T1 first; // 一般用来存key
    T2 second; // 一般用来存Value

    // 构造函数等成员函数...
};

pair的用法:

  • 创建 std::pair 对象:
#include <iostream>
#include <utility>

int main() {
    // 使用构造函数创建 pair 对象
    std::pair<std::string, int> student("John", 20);

    // 或者使用 make_pair 函数创建 pair 对象
    auto teacher = std::make_pair("Alice", 30);

    return 0;
}
  • 访问 std::pair 的成员:
#include <iostream>
#include <utility>

int main() {
    std::pair<std::string, int> student("John", 20);

    // 使用 first 和 second 成员访问 pair 的元素
    std::cout << "Name: " << student.first << ", Age: " << student.second << std::endl;

    return 0;
}
  • 修改 std::pair 的成员:
#include <iostream>
#include <utility>

int main() {
    std::pair<std::string, int> student("John", 20);

    // 修改 pair 的元素
    student.first = "Bob";
    student.second = 25;

    std::cout << "Name: " << student.first << ", Age: " << student.second << std::endl;

    return 0;
}
  • 使用 std::pair 作为函数的返回值:
#include <iostream>
#include <utility>

std::pair<std::string, int> getStudentInfo() {
    return std::make_pair("Alice", 30);
}

int main() {
    auto student = getStudentInfo();
    std::cout << "Name: " << student.first << ", Age: " << student.second << std::endl;

    return 0;
}
  • 使用 std::pair 存储在容器中:
#include <iostream>
#include <utility>
#include <vector>

int main() {
    // 使用 pair 存储多个键值对
    std::vector<std::pair<std::string, int>> students = { {"John", 20}, {"Alice", 25}, {"Bob", 30} };

    // 遍历容器中的 pair 元素
    for (const auto& student : students) {
        std::cout << "Name: " << student.first << ", Age: " << student.second << std::endl;
    }

    return 0;
}

std::pair 提供了一种简单而灵活的方式来处理需要存储和传递两个相关联的值的场景。

现在不懂没关系,后面讲完set和map就懂了。


3、set

3.1、set的介绍

set的文档介绍

  1. set集合是按照特定顺序存储独特元素的容器。

  2. set在集合中,元素的value也标识它(该值本身就是T类型的key),每个值必须是唯一的。set中元素的值不能在容器中修改(元素总是const),但它们可以从容器中插入或删除。

  3. 在内部,set中的元素总是按照其内部比较对象(比较类型)指示的特定严格弱排序标准进行排序。

  4. set容器通常比unordered_set容器通过其key访问单个元素的速度慢,但它使用迭代器会得到一个有序序列。

  5. set集合底层是用二叉搜索树(红黑树)实现。

注意:

  1. map/multimap 不同,map/multimap 中存储的是真正的键值对 <key, value>,而 set 中只存放 value,但在底层实际存放的是由 <value, value> 构成的键值对。

  2. set 中插入元素时,只需要插入 value 即可,不需要构造键值对。

  3. set 中的元素不可以重复(因此可以使用 set 进行去重)。

  4. 使用 set 的迭代器遍历 set 中的元素,可以得到有序序列。

  5. set 中的元素默认按照小于来比较。

  6. set 中查找某个元素的时间复杂度为: log ⁡ 2 n \log_2 n log2n

  7. set 中的元素不允许修改,这是因为 set 中的元素必须保持有序,如果修改元素可能会破坏有序性,因此不允许修改。

  8. set 中的底层使用二叉搜索树(红黑树)来实现。


3.2、set的使用

3.2.1、set的常见构造
(constructor )函数名称接口说明
set (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type())构造空的set
set (InputIterator first, InputIterator last, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type())用[first,last)迭代区间构造set
set (const set& x)set的拷贝构造
#include <iostream>
#include <set>

using namespace std;


void test_set1() {
    set<int> s;
    s.insert(4);
    s.insert(6);
    s.insert(8);
    s.insert(3);
    s.insert(1);
    for (auto e: s) {
        cout << e << " ";
    }
    cout << endl;

    cout << "=============" << endl;

    set<int> s1(s.begin(), s.end());
    for (auto e: s1) {
        cout << e << " ";
    }
    cout << endl;
}

int main() {
    test_set1();
    return 0;
}

3.2.2、set的iterator的使用
函数名称接口说明
begin()+end()获取第一个元素位置的iterator和获取最后一个元素的后一个位置的iterator
rbegin()+rend()获取最后一个元素位置的reverse_iterator和获取第一个元素的后一个位置的reverse_iterator
void test_set2() {
    set<int> s;
    s.insert(4);
    s.insert(6);
    s.insert(8);
    s.insert(3);
    s.insert(1);

    set<int>::iterator it = s.begin();
    while (it != s.end()) {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    set<int>::reverse_iterator rit = s.rbegin();
    while (rit != s.rend()) {
        cout << *rit << " ";
        ++rit;
    }
    cout << endl;
    
}

3.2.3、set的容量
函数名称接口说明
empty判断当前set是否为空
size获取set的元素个数
void test_set3() {
    set<int> s;
    s.insert(4);
    s.insert(6);
    s.insert(8);
    s.insert(3);
    s.insert(1);

    cout << s.size() << endl;
    cout << s.empty() << endl;

    s.clear();

    cout << s.size() << endl;
    cout << s.empty() << endl;
}

3.2.4、set的增删查
函数名称接口说明
insert在set中插入元素x,实际上插入的是<x,x>键值对,如果插入成功,返回<x插入位置,true>,插入失败说明set中已经有x,返回<x在set的位置,false>
erase删除set中pos位置的元素,或者删除值为val的元素
swap交换两个set的元素
clear将set的元素置空
find返回set中值为x的元素位置
count返回set中值为x的元素个数
void test_set4() {
    set<int> s;
    s.insert(4);
    s.insert(6);
    s.insert(8);
    s.insert(3);
    s.insert(1);

    for (auto e: s) {
        cout << e << " ";
    }
    cout << endl;

    auto pos = s.find(6);
    s.erase(pos);

    for (auto e: s) {
        cout << e << " ";
    }
    cout << endl;
    cout << s.count(6) << endl;
    cout << s.count(1) << endl;

    set<int> s1;
    s1.insert(1000);
    s1.insert(100);
    s1.insert(10);
    s1.insert(1);

    s1.swap(s);
    for (auto e: s1) {
        cout << e << " ";
    }
    cout << endl;
    s1.clear();

    for (auto e: s1) {
        cout << e << " ";
    }
    cout << endl;
}

4、multiset

4.1、multiset的介绍

multiset的文档介绍

  1. multiset是按照特定顺序存储元素的容器,其中多个元素可以具有相同值。

  2. 在multiset中,元素的value也标识它(该值本身就是T类型的key)。multiset中元素的值不能在容器中修改(元素总是const),但它们可以从容器中插入或删除。

  3. 在内部,multiset中的元素总是按照其内部比较对象(比较类型)指示的特定严格弱排序标准进行排序

  4. multiset容器通常比unordered_multiset容器通过key访问单个元素的速度慢,但它使用迭代器会得到一个有序序列。

  5. multiset通常作为二叉搜索树(红黑树)实现。

注意:

  1. map/multimap 不同,map/multimap 中存储的是真正的键值对 <key, value>,而 multiset 中只存放 value,但在底层实际存放的是由 <value, value> 构成的键值对。

  2. multiset 中插入元素时,只需要插入 value 即可,不需要构造键值对。

  3. multiset 中的元素可以重复。

  4. 使用 multiset 的迭代器遍历 multiset 中的元素,可以得到有序序列。

  5. multiset 中的元素默认按照小于来比较。

  6. multiset 中查找某个元素的时间复杂度为: log ⁡ 2 n \log_2 n log2n

  7. multiset 中的元素不允许修改,这是因为 multiset 中的元素必须保持有序,如果修改元素可能会破坏有序性,因此不允许修改。

  8. multiset 中的底层使用二叉搜索树(红黑树)来实现。


4.2、multiset的使用

multiset的使用和set差不多,就是能存重复的值,其他用法就不重复演示了

void test_multiset1() {
   multiset<int> ms;

   ms.insert(6);
   ms.insert(1);
   ms.insert(5);
   ms.insert(6);
   ms.insert(8);
   ms.insert(9);
   ms.insert(1);
   ms.insert(1);

   for (auto e: ms) {
       cout << e << " ";
   }
   cout << endl;
}

5、set的模拟实现

前面有说到,set的底层是有红黑树实现的。红黑树的讲解在这个链接:红黑树(RED-BLACK TREE)

其实set的函数基本都是基于调用红黑树的函数来使用的。因此我们需要对红黑树的功能进行完善(主要是迭代器)。

5.1、改造一下之前的红黑树

我们查看一下set底层的红黑树代码发现,set和map用的是同一个红黑树模版。如下简化版本:

template <typename Key, typename Value>
struct Node {
    Key key;
    Value value;
    Color color;
    std::shared_ptr<Node<Key, Value>> left;
    std::shared_ptr<Node<Key, Value>> right;
    std::shared_ptr<Node<Key, Value>> parent;

    Node(const Key& k, const Value& v, Color c = Color::RED)
        : key(k), value(v), color(c), left(nullptr), right(nullptr), parent(nullptr) {}
};

我们发现它这里的value的类型是Value,并不是我们之前写的pair。那么这样可以set和map用同一个红黑树模版,比如set就是 RBTree<K,K>,map就是RBT<K,pair<K,V>>

那么还有一个问题:当Value的类型是RBT<K,pair<K,V>时候,我们在Insert中比较的时候,不符合我们的要求(pair自带的比较规则不符合:first相等的时候,它就按second的大小比较),因此我们得自己写一个仿函数。源码中写的仿函数很巧妙,如果类型是pair,直接取出pair中的key,类型是K,就直接返回key

set中的仿函数:

// 用来取出key
struct SetKeyofT {
  K operator()(const K &key) {
      return key;
    }
};

map的仿函数:

// 用来取出key
struct MapKeyofT {
  K operator()(const pair<K, V> &kv) {
      return kv.first;
    }
};

改造后的红黑树:

enum Colour {
      RED,
      BLACK
};

template<class T>
struct RBTreeNode {
    typedef RBTreeNode<T> Node;
    Node *_left;
    Node *_right;
    Node *_parent;
    Colour _col;

    // 这里的T用的模版参数,可能传的是K,也可能传的pair<>,
    // 那么在下面插入的时候,存在比较,pair<>的自带的比较不符合我们的要求,得自己取出pair<>的key出来比较
    T _data;

    RBTreeNode(const T &data) : _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED), _data(data) {}
};

template<class K, class T, class KeyofT>
class RBTree {
    typedef RBTreeNode<T> Node;
public:
  
  // 红黑树的查找
    Node *Find(const K &key) const {
        Node *cur = _root;
        while (cur) {
            if (cur->_kv.first < key)
                cur = cur->_right;
            else if (cur->_kv.first > key)
                cur = cur->_left;
            else
                return cur; // 找到了目标结点
        }
        return nullptr; // 没找到目标结点
    }


    // 红黑树的删除

    bool Remove(const K &key) {
        Node *target = Find(key);
        if (!target) // 如果没找到目标结点,则返回false
            return false;

        // 执行删除操作
        Node *to_fix = nullptr; // 用于记录需要修正的结点
        Node *parent = target->_parent;
        Colour target_original_colour = target->_col;
        Node *child;
        if (!target->_left) {
            child = target->_right;
            Transplant(target, target->_right);
        } else if (!target->_right) {
            child = target->_left;
            Transplant(target, target->_left);
        } else {
            Node *successor = Minimum(target->_right); // 找到右子树中的最小结点作为后继结点
            target_original_colour = successor->_col;
            child = successor->_right;
            if (successor->_parent == target)
                child->_parent = successor;
            else {
                Transplant(successor, successor->_right);
                successor->_right = target->_right;
                successor->_right->_parent = successor;
            }
            Transplant(target, successor);
            successor->_left = target->_left;
            successor->_left->_parent = successor;
            successor->_col = target->_col;
        }
        delete target;

        // 修正红黑树性质
        if (target_original_colour == BLACK)
            FixAfterDelete(parent, child);
        return true;
    }


    void FixAfterDelete(Node *parent, Node *node) {
        while (node != _root && (!node || node->_col == BLACK)) {
            if (node == parent->_left) {
                Node *sibling = parent->_right;
                if (sibling->_col == RED) {
                    sibling->_col = BLACK;
                    parent->_col = RED;
                    RotateL(parent);
                    sibling = parent->_right;
                }
                if ((!sibling->_left || sibling->_left->_col == BLACK) &&
                    (!sibling->_right || sibling->_right->_col == BLACK)) {
                    sibling->_col = RED;
                    node = parent;
                    parent = parent->_parent;
                } else {
                    if (!sibling->_right || sibling->_right->_col == BLACK) {
                        sibling->_left->_col = BLACK;
                        sibling->_col = RED;
                        RotateR(sibling);
                        sibling = parent->_right;
                    }
                    sibling->_col = parent->_col;
                    parent->_col = BLACK;
                    sibling->_right->_col = BLACK;
                    RotateL(parent);
                    node = _root;
                }
            } else {
                Node *sibling = parent->_left;
                if (sibling->_col == RED) {
                    sibling->_col = BLACK;
                    parent->_col = RED;
                    RotateR(parent);
                    sibling = parent->_left;
                }
                if ((!sibling->_right || sibling->_right->_col == BLACK) &&
                    (!sibling->_left || sibling->_left->_col == BLACK)) {
                    sibling->_col = RED;
                    node = parent;
                    parent = parent->_parent;
                } else {
                    if (!sibling->_left || sibling->_left->_col == BLACK) {
                        sibling->_right->_col = BLACK;
                        sibling->_col = RED;
                        RotateL(sibling);
                        sibling = parent->_left;
                    }
                    sibling->_col = parent->_col;
                    parent->_col = BLACK;
                    sibling->_left->_col = BLACK;
                    RotateR(parent);
                    node = _root;
                }
            }
        }
        if (node)
            node->_col = BLACK;
    }


    Node *Minimum(Node *node) const {
        while (node->_left)
            node = node->_left;
        return node;
    }


    void Transplant(Node *u, Node *v) {
        if (!u->_parent)
            _root = v;
        else if (u == u->_parent->_left)
            u->_parent->_left = v;
        else
            u->_parent->_right = v;
        if (v)
            v->_parent = u->_parent;
    }


    // 红黑树的插入
    pair<iterator, bool> Insert(const T &data) {
        if (_root == nullptr) {
            _root = new Node(data);
            _root->_col = BLACK;
            return make_pair(_root, true);
        }

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

        // 取key
        KeyofT kot;

        while (cur) {
            if (kot(cur->_data) < kot(data)) {
                parent = cur;
                cur = cur->_right;
            } else if (kot(cur->_data) > kot(data)) {
                parent = cur;
                cur = cur->_left;
            } else {
                return make_pair(cur, false);
            }
        }

        cur = new Node(data);
        if (kot(parent->_data) > kot(data))
            parent->_left = cur;
        else
            parent->_right = cur;

        cur->_parent = parent;

        Node *newnode = cur;//记录当前cur位置,后面旋转可能会改变位置

        while (parent && parent->_col == RED) {

            // 记录祖父结点
            Node *grandfather = parent->_parent;

            // 记录祖父结点
            if (parent == grandfather->_left) {
                Node *uncle = grandfather->_right;

                // 情况一:叔叔存在且为红 --- 当前不用旋转调整,只需要改变g、u、p的颜色
                // 因为如果叔叔为红,那么祖父和父亲的颜色是确定的!
                if (uncle && uncle->_col == RED) {
                    parent->_col = uncle->_col = BLACK;
                    grandfather->_col = RED;

                    //继续往上检查
                    cur = grandfather;
                    parent = cur->_parent;// 注意别写反了
                } else {
                    // 情况二:叔叔不存在或者叔叔存在且为黑

                    //  形状一
                    //               g
                    //        p               u
                    //  cur
                    if (cur == parent->_left) {
                        // 这时候只需要右旋
                        RotateR(grandfather);
                        //只要父亲和祖父变色
                        parent->_col = BLACK;
                        grandfather->_col = RED;
                    } else {
                        //形状三
                        //               g
                        //        p               u
                        //            cur

                        // 这时候需要先左旋再右旋
                        // p位置左旋
                        // 再g位置右旋
                        RotateL(parent);
                        RotateR(grandfather);

                        //p和g位置变色
                        cur->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    break; // 旋转完加变色,不需要再调整
                }

            } else {
                Node *uncle = grandfather->_left;
                // 情况一:叔叔存在且为红 --- 当前不用旋转调整,只需要改变g、u、p的颜色
                // 因为如果叔叔为红,那么祖父和父亲的颜色是确定的!
                if (uncle && uncle->_col == RED) {
                    parent->_col = uncle->_col = BLACK;
                    grandfather->_col = RED;

                    //继续往上检查
                    cur = grandfather;
                    parent = cur->_parent;// 注意别写反了
                } else {
                    // 情况二:叔叔不存在或者叔叔存在且为黑

                    //  形状二
                    //               g
                    //        u               p
                    //                              cur
                    if (cur == parent->_right) {
                        // 这时候只需要左旋
                        RotateL(grandfather);
                        //只要父亲和祖父变色
                        parent->_col = BLACK;
                        grandfather->_col = RED;
                    } else {
                        //形状四
                        //               g
                        //        u               p
                        //                   cur

                        // 这时候需要先右旋再左旋
                        // p位置右旋
                        // 再g位置左旋
                        RotateR(parent);
                        RotateL(grandfather);

                        //p和g位置变色
                        cur->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    break; // 旋转完加变色,不需要再调整
                }
            }
        }
        _root->_col = BLACK; // 根结点始终为黑
        return make_pair(newnode, true);
    }

private:
    Node *_root = nullptr;


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

        parent->_right = subRL;
        subR->_left = parent;
        Node *ppnode = parent->_parent;

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

        // 如果当前parent本身就是跟结点的话,我们得把根结点更新
        if (parent == _root) {
            _root = subR;
            subR->_parent = nullptr;
        } else {
            if (parent == ppnode->_left) {
                // subR链接上之前的爷爷结点
                ppnode->_left = subR;
            } else {
                // subR链接上之前的爷爷结点
                ppnode->_right = subR;
            }
            subR->_parent = ppnode;
        }
    }

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

        parent->_left = subLR;
        subL->_right = parent;
        Node *ppnode = parent->_parent;

        if (subLR)
            subLR->_parent = parent;
        parent->_parent = subL;

        // 如果当前parent本身就是跟结点的话,我们得把根结点更新
        if (parent == _root) {
            _root = subL;
            subL->_parent = nullptr;
        } else {
            if (parent == ppnode->_left) {
                // subR链接上之前的爷爷结点
                ppnode->_left = subL;
            } else {
                // subR链接上之前的爷爷结点
                ppnode->_right = subL;
            }
            subL->_parent = ppnode;
        }
    }
};

5.2、红黑树的迭代器

红黑树的迭代器和list迭代器差不多,它们的值都是存在结点中,并且结点指针++并不能直接找到下一个结点,因此我们需要对结点指针进行封装。红黑树的迭代器++到下一个结点,这个结点的值是比前一个结点大的值,比后一个结点的值小,也就是得按中序遍历的顺序来往后++

迭代器的起始位置是在根结点下的最左结点。

根据中序遍历的规则:左根右。也就是如果当前结点是父结点的左孩子,则父结点肯定没有访问过,如果当前结点是父结点的右孩子,则父结点肯定已经访问过。

从这个结点开始,找下一个结点的位置的规则:如果当前结点的右子树存在,那么就去找右子树的最左结点;如果右子树不存在,则往上查找,若当前结点是父结点的左孩子,则查找结束,即找到了下一个位置为这个父结点的位置,若当前结点是父结点的右孩子,继续向上查找。

因此迭代器中的operator++函数的代码为:

Self &operator++() {
        if (_node->_right) {
            // 找右子树的最左结点
            Node *subLeft = _node->_right;
            while (subLeft->_left) {
                subLeft = subLeft->_left;
            }
            _node = subLeft;
        } else {
            // 右子树为空,说明右子树访问过了,那么当前结点也访问过了,继续往上找
            Node *cur = _node;
            Node *parent = cur->_parent;

            // 如果当前结点是父结点的左子树,说明需要访问父结点
            // 如果当前结点是父结点的右子树,说明需要往上查找当前结点是父亲的左孩子(找到这个父亲并访问)
            while (parent && parent->_right == cur) {
                cur = parent;
                parent = cur->_parent;
            }
            _node = parent;
        }
        return *this;
    }

再完善迭代器的其他代码:

// 迭代器
template<class T, class Ptr, class Ref>
struct RBTreeIterator {
    typedef RBTreeNode<T> Node;

    typedef RBTreeIterator<T, Ptr, Ref> Self;

    // 用结点构造迭代器
    RBTreeIterator(Node *node) : _node(node) {}

    Node *_node;

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

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

    Self &operator++() {
        if (_node->_right) {
            // 找右子树的最左结点
            Node *subLeft = _node->_right;
            while (subLeft->_left) {
                subLeft = subLeft->_left;
            }
            _node = subLeft;
        } else {
            // 右子树为空,说明右子树访问过了,那么当前结点也访问过了,继续往上找
            Node *cur = _node;
            Node *parent = cur->_parent;

            // 如果当前结点是父结点的左子树,说明需要访问父结点
            // 如果当前结点是父结点的右子树,说明需要往上查找当前结点是父亲的左孩子(找到这个父亲并访问)
            while (parent && parent->_right == cur) {
                cur = parent;
                parent = cur->_parent;
            }
            _node = parent;
        }
        return *this;
    }

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

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

红黑树的迭代器begin():根结点的最左结点

typedef RBTreeIterator<T, T *, T &> iterator;
iterator begin() {
        // 最左结点是起始位置
        Node *subLeft = _root;
        while (subLeft && subLeft->_left) {
            subLeft = subLeft->_left;
        }
        return iterator(subLeft);
}

红黑树的迭代器end():根结点的最右结点的下一个位置,我们这里让它为空

typedef RBTreeIterator<T, T *, T &> iterator;
iterator end() {
        return iterator(nullptr); // 单参数的构造函数能够隐式类型转换
}

这里还得注意一个问题:红黑树的结点中的值是不支持修改的!因此在set和map中,得限制K和pair<K,V>中的K不支持修改!

有两种方案:

  1. 在K前面加上const

  2. 迭代器和const迭代器都用红黑树的const迭代器。

源码底层set用的第二中方案,map用的第一种方案。


5.3、官方红黑树的迭代器

我们实现的红黑树没有头结点,因为再加上一个头结点,实现的难度又会增加。比如新增一个结点,如果这个结点最后是最左或者最右结点,还得更新头结点的左右指针的指向。

我们来看一下官方红黑树的迭代器:

这个迭代器找最左结点和最右结点都很快。


5.4、set的模拟实现代码

  • RBTree.h文件
//
// Created by 徐鹏 on 2024/3/30.
//

#ifndef DEMO_04_RBTREE_H
#define DEMO_04_RBTREE_H

#endif //DEMO_04_RBTREE_H

#include <iostream>

using namespace std;

enum Colour {
    RED,
    BLACK
};

template<class T>
struct RBTreeNode {
    typedef RBTreeNode<T> Node;
    Node *_left;
    Node *_right;
    Node *_parent;
    Colour _col;

    // 这里的T用的模版参数,可能传的是K,也可能传的pair<>,
    // 那么在下面插入的时候,存在比较,pair<>的自带的比较不符合我们的要求,得自己取出pair<>的key出来比较
    T _data;

    RBTreeNode(const T &data) : _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED), _data(data) {}
};


// 迭代器
template<class T, class Ptr, class Ref>
struct RBTreeIterator {
    typedef RBTreeNode<T> Node;

    typedef RBTreeIterator<T, Ptr, Ref> Self;

    // 用结点构造迭代器
    RBTreeIterator(Node *node) : _node(node) {}

    Node *_node;

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

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

    Self &operator++() {
        if (_node->_right) {
            // 找右子树的最左结点
            Node *subLeft = _node->_right;
            while (subLeft->_left) {
                subLeft = subLeft->_left;
            }
            _node = subLeft;
        } else {
            // 右子树为空,说明右子树访问过了,那么当前结点也访问过了,继续往上找
            Node *cur = _node;
            Node *parent = cur->_parent;

            // 如果当前结点是父结点的左子树,说明需要访问父结点
            // 如果当前结点是父结点的右子树,说明需要往上查找当前结点是父亲的左孩子(找到这个父亲并访问)
            while (parent && parent->_right == cur) {
                cur = parent;
                parent = cur->_parent;
            }
            _node = parent;
        }
        return *this;
    }

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

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

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 *subLeft = _root;
        while (subLeft && subLeft->_left) {
            subLeft = subLeft->_left;
        }
        return iterator(subLeft);
    }

    iterator end() {
        return iterator(nullptr); // 单参数的构造函数能够隐式类型转换
    }

    const_iterator cbegin() const {
        // 最左结点是起始位置
        Node *subLeft = _root;
        while (subLeft && subLeft->_left) {
            subLeft = subLeft->_left;
        }
        return const_iterator(subLeft);
    }

    const_iterator cend() const {
        return const_iterator(nullptr); // 单参数的构造函数能够隐式类型转换
    }

    // 红黑树的查找
    Node *Find(const K &key) const {
        Node *cur = _root;
        while (cur) {
            if (cur->_kv.first < key)
                cur = cur->_right;
            else if (cur->_kv.first > key)
                cur = cur->_left;
            else
                return cur; // 找到了目标结点
        }
        return nullptr; // 没找到目标结点
    }


    // 红黑树的删除

    bool Remove(const K &key) {
        Node *target = Find(key);
        if (!target) // 如果没找到目标结点,则返回false
            return false;

        // 执行删除操作
        Node *to_fix = nullptr; // 用于记录需要修正的结点
        Node *parent = target->_parent;
        Colour target_original_colour = target->_col;
        Node *child;
        if (!target->_left) {
            child = target->_right;
            Transplant(target, target->_right);
        } else if (!target->_right) {
            child = target->_left;
            Transplant(target, target->_left);
        } else {
            Node *successor = Minimum(target->_right); // 找到右子树中的最小结点作为后继结点
            target_original_colour = successor->_col;
            child = successor->_right;
            if (successor->_parent == target)
                child->_parent = successor;
            else {
                Transplant(successor, successor->_right);
                successor->_right = target->_right;
                successor->_right->_parent = successor;
            }
            Transplant(target, successor);
            successor->_left = target->_left;
            successor->_left->_parent = successor;
            successor->_col = target->_col;
        }
        delete target;

        // 修正红黑树性质
        if (target_original_colour == BLACK)
            FixAfterDelete(parent, child);
        return true;
    }


    void FixAfterDelete(Node *parent, Node *node) {
        while (node != _root && (!node || node->_col == BLACK)) {
            if (node == parent->_left) {
                Node *sibling = parent->_right;
                if (sibling->_col == RED) {
                    sibling->_col = BLACK;
                    parent->_col = RED;
                    RotateL(parent);
                    sibling = parent->_right;
                }
                if ((!sibling->_left || sibling->_left->_col == BLACK) &&
                    (!sibling->_right || sibling->_right->_col == BLACK)) {
                    sibling->_col = RED;
                    node = parent;
                    parent = parent->_parent;
                } else {
                    if (!sibling->_right || sibling->_right->_col == BLACK) {
                        sibling->_left->_col = BLACK;
                        sibling->_col = RED;
                        RotateR(sibling);
                        sibling = parent->_right;
                    }
                    sibling->_col = parent->_col;
                    parent->_col = BLACK;
                    sibling->_right->_col = BLACK;
                    RotateL(parent);
                    node = _root;
                }
            } else {
                Node *sibling = parent->_left;
                if (sibling->_col == RED) {
                    sibling->_col = BLACK;
                    parent->_col = RED;
                    RotateR(parent);
                    sibling = parent->_left;
                }
                if ((!sibling->_right || sibling->_right->_col == BLACK) &&
                    (!sibling->_left || sibling->_left->_col == BLACK)) {
                    sibling->_col = RED;
                    node = parent;
                    parent = parent->_parent;
                } else {
                    if (!sibling->_left || sibling->_left->_col == BLACK) {
                        sibling->_right->_col = BLACK;
                        sibling->_col = RED;
                        RotateL(sibling);
                        sibling = parent->_left;
                    }
                    sibling->_col = parent->_col;
                    parent->_col = BLACK;
                    sibling->_left->_col = BLACK;
                    RotateR(parent);
                    node = _root;
                }
            }
        }
        if (node)
            node->_col = BLACK;
    }


    Node *Minimum(Node *node) const {
        while (node->_left)
            node = node->_left;
        return node;
    }


    void Transplant(Node *u, Node *v) {
        if (!u->_parent)
            _root = v;
        else if (u == u->_parent->_left)
            u->_parent->_left = v;
        else
            u->_parent->_right = v;
        if (v)
            v->_parent = u->_parent;
    }


    // 红黑树的插入
    pair<iterator, bool> Insert(const T &data) {
        if (_root == nullptr) {
            _root = new Node(data);
            _root->_col = BLACK;
            return make_pair(_root, true);
        }

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

        // 取key
        KeyofT kot;

        while (cur) {
            if (kot(cur->_data) < kot(data)) {
                parent = cur;
                cur = cur->_right;
            } else if (kot(cur->_data) > kot(data)) {
                parent = cur;
                cur = cur->_left;
            } else {
                return make_pair(cur, false);
            }
        }

        cur = new Node(data);
        if (kot(parent->_data) > kot(data))
            parent->_left = cur;
        else
            parent->_right = cur;

        cur->_parent = parent;

        Node *newnode = cur;//记录当前cur位置,后面旋转可能会改变位置

        while (parent && parent->_col == RED) {

            // 记录祖父结点
            Node *grandfather = parent->_parent;

            // 记录祖父结点
            if (parent == grandfather->_left) {
                Node *uncle = grandfather->_right;

                // 情况一:叔叔存在且为红 --- 当前不用旋转调整,只需要改变g、u、p的颜色
                // 因为如果叔叔为红,那么祖父和父亲的颜色是确定的!
                if (uncle && uncle->_col == RED) {
                    parent->_col = uncle->_col = BLACK;
                    grandfather->_col = RED;

                    //继续往上检查
                    cur = grandfather;
                    parent = cur->_parent;// 注意别写反了
                } else {
                    // 情况二:叔叔不存在或者叔叔存在且为黑

                    //  形状一
                    //               g
                    //        p               u
                    //  cur
                    if (cur == parent->_left) {
                        // 这时候只需要右旋
                        RotateR(grandfather);
                        //只要父亲和祖父变色
                        parent->_col = BLACK;
                        grandfather->_col = RED;
                    } else {
                        //形状三
                        //               g
                        //        p               u
                        //            cur

                        // 这时候需要先左旋再右旋
                        // p位置左旋
                        // 再g位置右旋
                        RotateL(parent);
                        RotateR(grandfather);

                        //p和g位置变色
                        cur->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    break; // 旋转完加变色,不需要再调整
                }

            } else {
                Node *uncle = grandfather->_left;
                // 情况一:叔叔存在且为红 --- 当前不用旋转调整,只需要改变g、u、p的颜色
                // 因为如果叔叔为红,那么祖父和父亲的颜色是确定的!
                if (uncle && uncle->_col == RED) {
                    parent->_col = uncle->_col = BLACK;
                    grandfather->_col = RED;

                    //继续往上检查
                    cur = grandfather;
                    parent = cur->_parent;// 注意别写反了
                } else {
                    // 情况二:叔叔不存在或者叔叔存在且为黑

                    //  形状二
                    //               g
                    //        u               p
                    //                              cur
                    if (cur == parent->_right) {
                        // 这时候只需要左旋
                        RotateL(grandfather);
                        //只要父亲和祖父变色
                        parent->_col = BLACK;
                        grandfather->_col = RED;
                    } else {
                        //形状四
                        //               g
                        //        u               p
                        //                   cur

                        // 这时候需要先右旋再左旋
                        // p位置右旋
                        // 再g位置左旋
                        RotateR(parent);
                        RotateL(grandfather);

                        //p和g位置变色
                        cur->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    break; // 旋转完加变色,不需要再调整
                }
            }
        }
        _root->_col = BLACK; // 根结点始终为黑
        return make_pair(newnode, true);
    }

private:
    Node *_root = nullptr;


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

        parent->_right = subRL;
        subR->_left = parent;
        Node *ppnode = parent->_parent;

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

        // 如果当前parent本身就是跟结点的话,我们得把根结点更新
        if (parent == _root) {
            _root = subR;
            subR->_parent = nullptr;
        } else {
            if (parent == ppnode->_left) {
                // subR链接上之前的爷爷结点
                ppnode->_left = subR;
            } else {
                // subR链接上之前的爷爷结点
                ppnode->_right = subR;
            }
            subR->_parent = ppnode;
        }
    }

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

        parent->_left = subLR;
        subL->_right = parent;
        Node *ppnode = parent->_parent;

        if (subLR)
            subLR->_parent = parent;
        parent->_parent = subL;

        // 如果当前parent本身就是跟结点的话,我们得把根结点更新
        if (parent == _root) {
            _root = subL;
            subL->_parent = nullptr;
        } else {
            if (parent == ppnode->_left) {
                // subR链接上之前的爷爷结点
                ppnode->_left = subL;
            } else {
                // subR链接上之前的爷爷结点
                ppnode->_right = subL;
            }
            subL->_parent = ppnode;
        }
    }
};

  • MySet.h文件
//
// Created by 徐鹏 on 2024/4/1.
//

#ifndef DEMO_04_MYSET_H
#define DEMO_04_MYSET_H

#endif //DEMO_04_MYSET_H


namespace xp {
    template<class K>
    class set {
        // 用来取出key
        struct SetKeyofT {
            K operator()(const K &key) {
                return key;
            }
        };

    public:

        // 在类模版里取iterator需要使用到typename,指定这个iterator是类型
        // 否则不知道是静态成员变量还是类型,因为他们的定义语法是一样的
        typedef typename RBTree<const K, K, SetKeyofT>::iterator iterator;
        typedef typename RBTree<const K, K, SetKeyofT>::iterator const_iterator;

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


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

        const_iterator begin() const {
            return _t.cbegin();
        }


        const_iterator end() const {
            return _t.cend();
        }

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

    private:
        RBTree<const K, K, SetKeyofT> _t;
    };


    void test_set1() {
        set<int> s;
        s.insert(9);
        s.insert(3);
        s.insert(5);
        s.insert(2);
        s.insert(8);
        s.insert(0);
        s.insert(-1);

        set<int>::iterator it = s.begin();
        while (it != s.end()) {
//            *it += 10;
            cout << *it << " ";
            ++it;
        }

//        for (auto e: s) {
//            cout << e << " ";
//        }
        cout << endl;

    }
}


OKOK,C++ STL容器之set类就到这里。如果你对Linux和C++也感兴趣的话,可以看看我的主页哦。下面是我的github主页,里面记录了我的学习代码和leetcode的一些题的题解,有兴趣的可以看看。

Xpccccc的github主页

  • 14
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Xpccccc

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

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

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

打赏作者

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

抵扣说明:

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

余额充值