1.set
1.1 set的概念
set 是一种基于 平衡二叉搜索树(通常是红黑树) 实现的容器,它提供了有序集合的功能。set 用于存储唯一的元素,并且元素是按照某种顺序排列的(通常是升序)。
set 确实是一个关联式容器,底层实现使用的是类似于 红黑树 这种自平衡的二叉搜索树,元素在 set 中是以键值对的形式存储的。从底层的实现来看,可以理解为每个元素实际上是一个 pair<value, value>,其中 key 和 value 都是相同的。
set 的底层实现
- 底层结构:set 是一个关联容器,它的元素是有序的,并且每个元素都是唯一的。底层通常使用 红黑树 来实现,这是一个自平衡的二叉搜索树。在红黑树中,节点包含一个键(key),而对于 set 来说,这个键就是元素的值本身。
- 元素存储形式:尽管在 set 中我们仅插入一个值(例如一个 int),但这些值实际上存储在底层红黑树的节点中,每个节点包含一个 键 和一个 值,但对于 set 来说,键和值是相同的。C++ 标准库中使用的 pair<const T, T> 结构来表示每个元素,其中 const T 表示键值,T 表示存储的值。因此,在 C++ set 中,元素的形式是 pair<const value, value>,不过这种 pair 结构对于开发者来说是透明的,我们使用者只需要插入和访问 value,并不需要显式操作 pair。
性能
由于底层使用红黑树实现,set 中的插入、删除和查找操作的时间复杂度为 O(log n),其中 n 是集合中的元素数量。这使得 set 在需要保证元素唯一性且还要支持快速查找和排序时非常高效。
适用于那些需要存储唯一且有序数据的场景,例如去重、排序和高效查找
1.2 set常用方法
1.模板参数
Compare: 这个类型控制了集合中元素的排序方式。它必须是一个可以进行元素比较的类型,通常是一个函数对象,或是重载了 operator() 的类。最常用的默认比较器是 std::less<T>,它将元素按升序排列。
2.构造
以下是具体用法
源码:
#include<iostream>#include<set>using namespace std;void test1() {set<int> s1;int a[] = { 1,2,3,4,5,6 };for (auto ch : a) {s1.insert(ch); // 调用迭代器函数,直接放入val}set<int> s2(s1.begin(), s1.end()); // 迭代器的构造,和s1一样set<int> s3(s2); // 拷贝构造for (auto key : s1) {cout << key << " ";}cout << endl;for (auto key : s2) {cout << key << " ";}cout << endl;for (auto key : s3) {cout << key << " ";}cout << endl;}int main() {test1();return 0;}
3.set插入
以下是具体用法
源码:
void print(const set<int>& s) {for (auto val : s) {cout << val << " ";}cout << endl;}void test2() {set<int> s1;int a[] = { 1,2,3,4,5,6 };for (auto ch : a) {s1.insert(ch); }print(s1);// 插入s1.insert(s1.begin(), 0);print(s1);set<int>s2 = { 10,20,30 };s1.insert(s2.begin(), s2.end());print(s1);}
其他的使用在这里就不详细示例,自己根据cplusplus.com - The C++ Resources Network中的模板解释来实现
<set>模板里面包含两个类,还有一个是 multiset
其原理和set几乎一致,只是multiset能存多个相同的值了
注意点:是find查找时是返回第一个遇到的value,count将返回该值存在的个数
其中还要交代一个函数(set中也有不过不够实用)
最明显的区别应该是这:
set容器中的所有元素都是唯一的,因此返回的范围最多包含一个元素
他返回的是pair<iterator,iterator>,这两个迭代器分别表示的就是value值的lower_bound和upper_bound,这样就能一次性删除所有相同的元素!
2.map
map中也是类似
先来看模板
同样,compare默认为less也就是升序
我们来主要看看最重要的构造函数
来看其使用
在这里涉及到一个make_pair函数,我们还是来看看std中是怎么解释的
我们可以发现,make_pair实际上是一个template
需要注意的点:
- 用make_pair(key,value)构造出K,V类型的pair<K,V>当参数传递进去,或者写成用pair的构造pair<K,V>(key,value)
- 返回pair<iterator,bool>。
- 若返回的iterator,需要注意的是其类型是pair<K,V>型
除了构造函数,map中有一个特别重要的就是”[]”的重载
我们来详细看看
这是我自己的模拟实现,和std中还是有些许差别,但是std中,本人觉得很巧妙,但是由于适配其他东西,不免有些冗杂
operator[]中,std的底层实质就是:
(*((this->insert(make_pair(k,mapped_type()))).first)).second
其中能看到他是调用了插入函数,所以[]能实现插入功能
就像上图我自己的实现所说,自举,利用insert实现插入功能
而我们map的插入返回的类型是:pair<iterator,bool>
所以就能简化为:(*pair<iterator,bool>.first).second
//此处.的优先级高于*所以是先访问,得到iterator后,再(*iterator).second
得到其迭代器的value值,也就是插入时的第二个参数
我们再了解以下其他的一系列基本功能函数
当然,<map>的template里面,也封装了multimap这个类,与multiset类似,在此不过多赘述
我们主要来看底层的详情
主要就是对红黑树的修改:
- 模板的改变:将原本第二个参数V改成T,T代表的是K,V组成成的键值对pair<K,V>
- 添加迭代器以及begin、end函数,让map、set也能用迭代器
- 修改插入的返回值:将原本的iterator改成pair<iterator,bool>,(这是STL源码内的设计,也是为了map的[]做准备)
RBTree的源码修改,底层实现
源码:
见后文附录
运行效果
些许总结:
/*
查阅 STL,
我们知道set和map的底层均是使用红黑树实现的,
只不过set存储的是key,
而map存储的是key_value
STL明确规定,begin()与end()代表的是一段前闭后开的区间,
而对红黑树进行中序遍历后,可以得到一个有序的序列,
因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,
end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?
能否给成nullptr呢?答案是行不通的,
因为对end()位置的迭代器进行- -操作,必须要能找最后一个元素,
此处就不行,因此最好的方式是将end()放在头结点的位置
*/
/*
* 问题列表:
* 1.operator[]的重载,返回的是插入的insert的second
* 实质上就是return insert({ key, V() }).first->second
*【insert({ key, V() }).first】对应的就是insert中的key,
* 通过查看类型,我们知道key就是属于const K& key,而K是pair<const K, V>的
*
* 我们再次看定义的 pair<iterator, bool> result = insert({ key, V() });
* 也就是iterator类型,我们对该容器就能使用迭代器来操作了
*
* 2.map和set的底层都是红黑树,而map和set中存储的键不同(map是键值对<K,V>;set是键K),
* 那么如何用一棵红黑树来封装出map和set这两种容器呢?
* 为适应不同的类型(键/键值对),也就是泛型编程,
* 之前的红黑树是RBTree<class K,class V>,而现在的红黑树RBTree<class K,class T>,
* 当set调用红黑树时这个T会是K,当map调用时T则是 pair<K,V>
*
*
*/
附录(源码)模拟实现
RBTree.h
#include<iostream>
#include<cassert>
#include<initializer_list>
#include<stack>using namespace std;// 结点颜色
// 0和1
enum Color
{RED,BLACK
};// 红黑树结点
// set中v为key,map中T是pair<key,value>
template<class T>
struct RBTreeNode
{T data_; struct RBTreeNode* _left;struct RBTreeNode* _right;struct RBTreeNode* _parent;Color color;// 结点构造// 一个类型为 T 的常量引用,表示存储在节点中的数据。默认值 T() 表示使用 T 类型的默认构造函数来初始化数据成员 data_。RBTreeNode(const T& data = T(),Color color = RED):data_(data),_left(nullptr),_right(nullptr),_parent(nullptr),color(color){}
};// 红黑树迭代器
template<class T, class Ref, class Ptr>
struct RBTreeIterator {typedef RBTreeNode<T> Node;Node* node; // 当前节点// 构造函数RBTreeIterator(Node* node) : node(node) {}// 拷贝构造函数RBTreeIterator(const RBTreeIterator& other) : node(other.node) {}// 赋值运算符RBTreeIterator& operator=(const RBTreeIterator& other) {if (this != &other) {node = other.node;}return *this;}// ++itRBTreeIterator& operator++() {if (node == nullptr) return *this; // 防止空指针Node* current = node;if (current->_right) {// 如果有右子树,找到右子树中的最左边节点node = current->_right;while (node->_left) {node = node->_left;}}else {// 如果没有右子树,回溯到父节点Node* parent = current->_parent;while (parent && parent->_right == current) {current = parent;parent = current->_parent;}node = parent; // 如果 parent 为空,node 将会变为 nullptr。}return *this;}// it++RBTreeIterator operator++(int) {RBTreeIterator tmp(*this);++(*this);return tmp;}// --itRBTreeIterator& operator--() {if (node == nullptr) return *this; // 防止空指针Node* current = node;if (current->_left) {// 如果有左子树,找到左子树中的最右节点node = current->_left;while (node->_right) {node = node->_right;}}else {// 否则回溯到父节点Node* parent = current->_parent;while (parent && parent->_left == current) {current = parent;parent = current->_parent;}node = parent; // 如果 parent 为空,node 将会变为 nullptr。}return *this;}// it--RBTreeIterator operator--(int) {RBTreeIterator tmp(*this);--(*this);return tmp;}// * 运算符Ref operator*() {if (node == nullptr) {throw std::out_of_range("Dereferencing a null iterator");}return node->data_;}// -> 运算符Ptr operator->() {if (node == nullptr) {throw std::out_of_range("Dereferencing a null iterator");}return &node->data_;}// != 运算符bool operator!=(const RBTreeIterator& it) const {return node != it.node;}// == 运算符bool operator==(const RBTreeIterator& it) const {return node == it.node;}
};// 反向迭代器
// 其实就是正向的逆转,只有自增和自减才会有区别
/*
只需对自增 (operator++) 和自减 (operator--) 做适当调整。
在反向迭代器中,operator++ 和 operator-- 分别要执行与正向迭代器相反的逻辑
简化反向迭代器的实现,主要只在 operator++ 和 operator-- 中做适当的调整:自增(operator++):反向迭代器自增是向前移动一个元素,实际上是正向迭代器自减的效果。
自减(operator--):反向迭代器自减是向后移动一个元素,实际上是正向迭代器自增的效果。
*/template<class T, class Ref,class Ptr>
struct RBTreeReverseIterator
{// 简化typedef RBTreeNode<T> Node;Node* node; // 要操作的结点// 迭代器构造RBTreeReverseIterator(Node* node) : node(node) {}// 重载// ++it 正向的自减RBTreeReverseIterator& operator++() {Node* current = node;// 两种情况if (current->_left) {// 当前结点有左子树,则下一个元素就是左子树的最右结点Node* rightMost = current->_left;while (rightMost->_right){rightMost = rightMost->_right;}node = rightMost;}else {// 如果没有左子树,则回溯到父节点,直到当前节点是父节点的右子节点Node* parent = current->_parent;while (parent && parent->_left == current){current = parent;parent = current->_parent;}node = parent;}return *this;}RBTreeReverseIterator operator++(int) {RBTreeReverseIterator tmp(*this);++(*this);return tmp;}// --it,也就是正向的自增RBTreeReverseIterator& operator--() {Node* current = node;// 两种情况if (current->_right) {// 当前结点有右子树,则下一个元素就是右子树的最左结点Node* lefttMost = current->_right;while (lefttMost->_left){lefttMost = lefttMost->_left;}node = lefttMost;}else {// 如果没有右子树,则回溯到父节点,直到当前节点是父节点的左子节点Node* parent = current->_parent;while (parent && parent->_right == current){current = parent;parent = current->_parent;}node = parent;}return *this;}// it--RBTreeReverseIterator operator--(int) {RBTreeReverseIterator tmp(*this);--(*this);return tmp;}// *Ref operator*() {// 解引用,取值return node->data_;}// -> Ptr operator->() {return &node->data_;}// !=和==bool operator!=(const RBTreeReverseIterator& it) {return node != it.node;}bool operator==(const RBTreeReverseIterator& it) {return node == it.node;}
};
// 红黑树底层源码,也在RBTree.h文件中
// 红黑树底层实现// 红黑树
template<class K,class T,class KeyOfT>
class RBTree
{
private:typedef RBTreeNode<T> Node;// 当前属性Node* header_;// /*销毁树的递归函数// 最后销毁到根结点*///void destroy(Node*& root) {// /*if (root)// return;*/// // assert(root);// // 必须后序销毁结点,保证先左右,后当前,避免野指针// // 当前的先销毁了以后,左右的子结点就丢失地址,就乱指了// destroy(root->_left);// root->_left = nullptr;// destroy(root->_right);// root->_right = nullptr;// // 最后删除当前// delete root;// root = nullptr;//}void destroy(Node*& root) {if (!root) {return;}//assert(root);// 使用栈来模拟递归stack<Node*> stack;Node* current = root;// 深度优先遍历,模拟递归while (current != nullptr || !stack.empty()) {if (current != nullptr) {// 将当前节点压入栈,并遍历左子树stack.push(current);current = current->_left;}else {// 当前节点没有左子树,则处理栈顶元素,并遍历右子树current = stack.top();stack.pop();// 删除当前节点之前,处理父节点与子节点的连接Node* right = current->_right;// 断开与父节点的链接if (current->_parent) {if (current->_parent->_left == current) {current->_parent->_left = nullptr;}else if (current->_parent->_right == current) {current->_parent->_right = nullptr;}}// 删除当前节点delete current;// 继续处理右子树current = right;}}}private://调整旋转函数// 左旋void rotateLeft(Node* parent) {// 保存节点,后面链接Node* grandpa = parent->_parent;Node* sub = parent->_right;Node* subleft = parent->_left;// 重链parent->_right = subleft;if (subleft) {subleft->_parent = parent;}sub->_left = parent;parent->_parent = sub;// 与上面的链接if (grandpa == header_) {// parent 原来是 rootheader_->_parent = sub;sub->_parent = header_;}else {if (grandpa->_left == parent) {// 原来是上面节点的左子树grandpa->_left = sub;}else {grandpa->_right = sub;}sub->_parent = grandpa;}}// 右旋void rotateRight(Node* parent) {// 保存节点,后面链接Node* grandpa = parent->_parent;Node* sub = parent->_left;Node* subright = parent->_right;// 重链parent->_left = subright;if (subright) {subright->_parent = parent;}sub->_right = parent;parent->_parent = sub;// 与上面的链接if (grandpa == header_) {// parent 原来是 rootheader_->_parent = sub;sub->_parent = header_;}else {if (grandpa->_left == parent) {// 原来是上面节点的左子树grandpa->_left = sub;}else {grandpa->_right = sub;}sub->_parent = grandpa;}}// 左旋后右旋void rotateLeft_Right(Node* parent) {rotateLeft(parent->_left);rotateRight(parent);}// 右旋后左旋void rotateRight_Left(Node* parent) {rotateRight(parent->_right);rotateLeft(parent);}// 最左节点Node* leftMost() {Node* MostLeftChild = header_->_parent;while (MostLeftChild->_left){MostLeftChild = MostLeftChild->_left;}return MostLeftChild;}// 最右节点Node* rightMost() {Node* MostRightChild = header_->_parent;while (MostRightChild->_right){MostRightChild = MostRightChild->_right;}return MostRightChild;}public:// 空参构造RBTree() {// 创建空的头结点header_ = new Node(T());}// 拷贝构造RBTree(const RBTree& rbTree) {// 要先创建结点所带空间header_ = new Node(T());// 复制,以达到插入的效果for (const auto elem : rbTree) {// 调用插入函数,在基本功能函数列表中insert(elem);}}// 初始化列表构造RBTree(initializer_list<T> initList) {header_ = new Node(T());for (const auto& elem : initList) {inserter(elem);}}// 在析构函数中,确保 header_ 不会被误修改~RBTree() {// 如果树为空,直接返回if (header_->_parent == nullptr) {delete header_;header_ = nullptr;return;}// 销毁树中的节点destroy(header_->_parent);header_->_parent = nullptr; // 确保 header_->_parent 被清空// 删除头结点delete header_;header_ = nullptr; // 清空头节点指针}// 赋值重载RBTree& operator=(const RBTree& binarySTree) {// 现代C++写法,通过构造临时对象并交换RBTree tmp(binarySTree);// 使用交换方式来节省空间std::swap(header_, tmp.header_);return *this;}/* * const RBTree& bst:左值引用,* 意味着传入的对象是一个常量引用(不会修改原始对象),* 这样可以避免不必要的拷贝,并且保证赋值时不修改源对象。* * 局部的临时对象,调用拷贝构造来初始化,赋值binarySTree数据* * 使用了 std::swap * 交换当前对象(即 *this)和临时对象 temp 的成员 _header。也就是说:* 交换当前对象和临时对象的头指针 _header,* 这实际上交换了两个对象的内部状态(例如树的结构)。* 交换操作是高效的,因为它只涉及指针的交换,而不是复制大量的节点数据。这使得赋值操作更加高效。*/// 迭代器typedef RBTreeIterator<T, T&, T*> iterator;typedef RBTreeIterator<T, const T&, const T*> const_iterator;using reverse_iterator = typename RBTreeReverseIterator<T, T&, T*>;using const_reverse_iterator = typename RBTreeReverseIterator<T, const T&, const T*>;// 各类迭代器begin,end// 返回树中的最小结点(从头)iterator begin() {return iterator(header_->_left); // 头节点的左孩子是最小节点}// 常量版本,不能修改const_iterator begin() const {return const_iterator(header_->_left);}iterator end() {return iterator(header_);}const_iterator end() const {return const_iterator(header_);}// 反向reverse_iterator rbegin() {return reverse_iterator(header_->_right); // 头节点的右孩子是最小节点}const_reverse_iterator rbegin() const {return const_reverse_iterator(header_->_right);}reverse_iterator rend() {return reverse_iterator(header_);}const_reverse_iterator rend() const {return const_reverse_iterator(header_);}//此时需要注意key是set中的key,是map中pair<key,value>中的key,// 因此我们需要取出想要的key// 依据键值,以给定键的结点,返回迭代器iterator find(const K& key) const {Node* current = header_->_parent; // 根结点开始遍历查找while (current){if (key < KeyOfT()(current->data_)) {// 比较键值current = current->_left;// 小就在左边}else if (key > KeyOfT()(current->data_)) {// 比较键值current = current->_right;// 大就在右边}else {// 找到,返回return iterator(current);}}// 没找到就回头结点位置return iterator(header_);}// 插入元素,如果插入成功返回一个指向新元素的迭代器,并返回 truepair<iterator, bool> insert(const T& data) {if (header_->_parent == nullptr) {// 树空,插入新节点为根节点header_->_parent = new Node(data);// 赋色header_->_parent->color = BLACK;header_->_parent->_parent = header_;//插入成功的 pair<iterator, bool>,其中 iterator 是指向新根节点的迭代器,true 表示插入成功。return { iterator(header_->_parent),true }; }// 查找位置Node* parent = header_;Node* current = header_->_parent;while (current){//比较待插入元素 t 与当前节点 cur->_t 的值。通过这个比较来决定向左子树或右子树继续查找。if (KeyOfT()(data) < KeyOfT()(current->data_)) {parent = current;current = current->_left;}else if (KeyOfT()(data) > KeyOfT()(current->data_)) {parent = current;current = current->_right;}else {// 相同就返回迭代器,表示已经有该元素,找到该元素位置// 该iterator一定要是非const的return { iterator(current),false };}}// 插入新节点current = new Node(data);current->_parent = parent;if (KeyOfT()(data) < KeyOfT()(current->data_)) {parent->_left = current;}else {parent->_right = current;}// 对插入的节点做一下保存Node* result = current;// 红黑树平衡调整//情况一:parent存在且为黑,不需要调整//情况二:parent存在且为红,需要调整while (parent != header_ && parent->color == RED){//确定 g 和 u 节点Node* grandpa = parent->_parent;Node* uncle = nullptr;if (parent == grandpa->_left) {// 父亲为祖父左孩子,那么叔叔就为右孩子uncle = grandpa->_right;}else {uncle = grandpa->_left;}//在情况二下又有如下情况:// 叔叔节点为红色if (uncle && uncle->color == RED) {parent->color = BLACK;uncle->color = BLACK;// 祖父节点也要调整grandpa->color = RED;current = grandpa;parent = current->_parent;}// 叔叔节点不存在或者为黑色//else if (!uncle || uncle->color == BLACK) {else{// 改变颜色//parent 是 grandfather 的左孩子if (parent == grandpa->_left) {//cur 是 parent 的左孩子if (current == parent->_left) {// 右旋且变色rotateRight(grandpa);parent->color = BLACK;grandpa->color = RED;}//cur 是 parent 的右孩子else {// 左旋后右旋rotateLeft_Right(grandpa);current->color = BLACK;grandpa->color = RED;}}//parent 是 grandfather 的右孩子else {//cur 是 parent 的左孩子if (current == parent->_left) {// 右旋后左旋rotateRight_Left(grandpa);current->color = BLACK;grandpa->color = RED;}//cur 是 parent 的右孩子else {// 左旋rotateLeft(grandpa);current->color = BLACK;grandpa->color = RED;}}break;}}// 根节点为黑色header_->_parent->color = BLACK;header_->_left = leftMost(); // 更新左子树最小节点header_->_right = rightMost(); // 更新右子树最大节点return { iterator(result),true };}// 功能函数bool empty() {return header_->_parent == nullptr;}size_t size() {size_t count = 0;auto it = begin();while (it != end()){count++;++it;}return count;}void clear() {// 如果 header_ 存在且 _parent 不是 nullptr,销毁树结构if (header_ && header_->_parent) {destroy(header_->_parent); // 销毁树header_->_parent = nullptr; // 清空根节点指针}// 如果 header_ 是动态分配的并且不再需要,考虑释放 header_ 自身// 例如:如果 header_ 是 new 创建的,可以在这里 delete// delete header_; // 如果 header_ 是动态分配的,可以选择删除 header_// header_ = nullptr; // 置为 nullptr 防止悬空指针(如果需要删除)}
};
set.h
#pragma once
#include"RBTree.h"template<class K>
class set
{typedef const K T; // 与map匹配/*这是一个辅助结构体,用于告诉红黑树如何从元素中提取键(key)。在这个类中,键就是元素本身,所以 operator() 实现了返回元素本身的功能。operator()(const T& key) 让 KeyOfT 对象作为一个仿函数,可以接受 const T& 类型的元素,并返回该元素(key)。*/struct KeyOfT{const K& operator()(const T& key) {return key;}};//using reverse_iterator = typename RBTreeReverseIterator<T, T&, T*>;//using iterator = typename RBTree<K, T, KeyOfT>::iterator;typedef typename RBTree<K, T, KeyOfT>::iterator iterator;typedef typename RBTree<K, T, KeyOfT>::const_iterator const_iterator;typedef typename RBTree<K, T, KeyOfT>::reverse_iterator reverse_iterator;typedef typename RBTree<K, T, KeyOfT>::const_reverse_iterator const_reverse_iterator;private:RBTree<K, T, KeyOfT> _rbt;
public:// 空参构造set(){}set(initializer_list<T> initlist) {_rbt = { initlist };}// 迭代器iterator begin() { return _rbt.begin(); }const_iterator begin() const { return _rbt.begin(); }iterator end() { return _rbt.end(); }const_iterator end() const { return _rbt.end(); }// 反向迭代器reverse_iterator rbegin() { return _rbt.rbegin(); }const_reverse_iterator rbegin() const { return _rbt.rbegin(); }reverse_iterator rend() { return _rbt.rend(); }const_reverse_iterator rend() const { return _rbt.rend(); }// set 功能函数// 基于红黑树的实现bool empty() {return _rbt.empty();}size_t size(){return _rbt.size();}void clear(){_rbt.clear();}iterator find(const K& key) const{return _rbt.find(key);}// 这里的T就是const Kpair<iterator, bool> insert(const T& t){return _rbt.insert(t);}
};
map.h
#pragma once
#include"RBTree.h"template<class K,class V>
class map
{/*T 代表存储在 map 中的元素类型。pair<const K, V> 意味着 map 中的每个元素是一个键值对,其中键是常量(const K),值是可变的(V)。iterator 与之对应,也应该是可变的键是常量的,防止在 map 中修改键。*/typedef /*const*/ pair<const K, V> T;/*KeyOfT 是一个帮助提取键(key.first)的仿函数。RBTree 底层需要一个函数对象来提取键以便进行排序或查找。这个结构体实现了一个 operator(),接受一个 pair<const K, V> 类型的元素并返回其 first 成员,即键。*/struct KeyOfT{const K& operator()(const T& key) {return key.first;}};/*typedef typename RBTree<K, T, KeyOfT>::iterator iterator;typedef typename RBTree<K, T, KeyOfT>::const_iterator const_iterator;typedef typename RBTree<K, T, KeyOfT>::reverse_iterator reverse_iterator;*/using iterator = typename RBTree<K, T, KeyOfT>::iterator;using const_iterator = typename RBTree<K, T, KeyOfT>::const_iterator;using reverse_iterator = typename RBTree<K, T, KeyOfT>::reverse_iterator;using const_reverse_iterator = typename RBTree<K, T, KeyOfT>::const_reverse_iterator;private:RBTree<K, pair<const K, V>, KeyOfT> _rbt; // key不能修改
public:// 空参构造map() {}map(initializer_list<T> initlist) {_rbt = { initlist };}// 迭代器iterator begin() { return _rbt.begin();}const_iterator begin() const { return _rbt.begin(); }iterator end() { return _rbt.end(); }const_iterator end() const { return _rbt.end(); }// 反向迭代器reverse_iterator rbegin() { return _rbt.rbegin(); }const_reverse_iterator rbegin() const { return _rbt.rbegin(); }reverse_iterator rend() { return _rbt.rend(); }const_reverse_iterator rend() const { return _rbt.rend(); }// map 功能函数// 基于红黑树的实现bool empty() {return _rbt.empty();}size_t size(){return _rbt.size();}void clear(){_rbt.clear();}iterator find(const K& key) const{return _rbt.find(key);}// 这里的T就是const pair<const K,V>/*将一个 pair<const K, V> 插入到 map 中。返回一个 pair,其中:first 是插入的元素的迭代器。second 是一个布尔值,表示是否成功插入。如果键已经存在,则插入失败,返回 false;如果键不存在,则插入成功,返回 true。*/pair<iterator, bool> insert(const T& t){return _rbt.insert(t);}/*如果指定的键存在,则返回对应的值。如果键不存在,会插入一个 *默认构造的值(V())* 并返回该值。注意,插入是通过 insert 完成的。*/V& operator[](const K& key) {// 使用插入操作,插入一个空的 V 值pair<iterator, bool> result = insert({ key, V() });iterator it = result.first; // 获取对应的迭代器//iterator it = insert({ key, V() }).first;return it->second; // 返回非 const 引用}};
test.cpp
#include"mymap.h"
#include"myset.h"
#include<iostream>
#include<string>using namespace std;void TestMap_Set() {// mapcout << "map测试:" << endl;map<int, string> m;// 插入m.insert({ 1,"One" });m.insert({ 2,"Two" });m.insert({ 3,"Three" });m.insert({ 4,"Four" });m.insert({ 10,"Ten" });// 重载的[]m[6] = "Six"; // 使用 [] 来插入// 查找auto mapfind = m.find(3);if (mapfind != m.end()) {cout << "找到的键 3 对应的值:" << mapfind->second << endl;}else {cout << "键 3 对应的值没找到!" << endl;}auto ifind = m.find(5);if (ifind != m.end()) {cout << "找到的键 5 对应的值:" << ifind->second << endl;}else {cout << "键 5 对应的值没找到!" << endl;}// 遍历cout << "map 遍历:" << endl;for (auto& key_val : m) {cout << key_val.first << "->" << key_val.second << endl;}// 测试 map 的 size 和 emptycout << "Map 大小: " << m.size() << endl;cout << "map 为空? " << (m.empty() ? "Yes" : "No") << endl;// 清空 map/* m.clear();cout << "清空过后, map 大小: " << m.size() << endl;*/// 测试 setcout << "\nset测试:" << endl;set<int> s;// 插入数据s.insert(10);s.insert(20);s.insert(30);s.insert(40);// 查找if (s.find(20) != s.end()) {cout << "找到 20 在set中!" << endl;}else {cout << "20 没有在set中!" << endl;}// 遍历 setcout << "set 遍历:" << endl;for (auto& key : s) {cout << key << endl;}// 测试 set 的 size 和 emptycout << "set 大小: " << s.size() << endl;cout << "set 是否为空? " << (s.empty() ? "Yes" : "No") << endl;// 清空 set/* s.clear();cout << "清空过后,set 大小: " << s.size() << endl;*/
}int main() {TestMap_Set();return 0;
}
对照测试函数,再来看运行效果
总结:
set
:基于红黑树实现的set
是一个有序的集合,确保每个元素唯一,并且按升序排列。主要操作有插入、查找和删除,复杂度均为 O(logn)O(logn)。map
:基于红黑树实现的map
是一个有序的映射,存储键值对,按照键排序,提供高效的插入、查找、更新和删除操作。- 红黑树的优点:通过自平衡的特性,红黑树保证了高效的查找、插入和删除操作,同时能保持数据的有序性。
总的来说,set
和 map
基于红黑树的实现使得它们在 STL 中成为非常强大的数据结构,既能保证操作的高效性,又能提供有序的元素访问。