C++進階-封裝紅黑樹模擬實現map和set(難度較高)

目錄

1.預備知識

2.初步代碼

3.對紅黑樹實現的代碼進行改造

4.對map和set的改造

5.對RBTree::insert的改造

6.對RBTree::Find函數的改造

7.實現iterator(最重要)

8.實現const_iterator

9.完成set和map的key不能修改

10.實現map的operator[]

11.代碼匯總

12.總結



1.預備知識

我們要了解一下map和set這兩個容器,可以到C++官網中搜索map和set進行了解,以下是C++官網鏈接:

Reference - C++ Reference

如果想了解更多,可以去看我寫的兩篇關于map和set的博客:

C++進階-set-CSDN博客文章瀏覽閱讀608次,點贊23次,收藏17次。本文系統介紹了C++ STL中的set容器的特性與使用方法。set作為關聯式容器,基于紅黑樹實現,具有自動去重和排序功能,增刪查效率為O(logN)。文章詳細講解了set的構造函數、常用成員函數(如emplace_hint、key_comp等)以及邊界查找函數(lower_bound、upper_bound等),并通過代碼示例演示了其具體用法。同時對比了set與multiset的差異,指出后者允許重復元素。最后通過兩個力扣編程題(查找數組交集和環形鏈表入口)展示了set的實際應用場景,體現了其在數據去重和快 https://blog.csdn.net/2401_86446710/article/details/149322360?spm=1011.2415.3001.10575&sharefrom=mp_manage_linkC++進階-map-CSDN博客文章瀏覽閱讀835次,點贊26次,收藏14次。本文介紹了C++ STL中的map容器。map是基于二叉搜索樹實現的鍵值對容器,具有自動排序特性。文章詳細講解了map的聲明方式、構造函數、operator[]操作符(可查找/插入元素)、at方法(安全訪問)、emplace_hint高效插入等核心功能。特別強調了map與set的區別在于存儲鍵值對,以及operator[]的獨特行為(找不到鍵時會自動插入)。還介紹了輔助類pair的用法,以及key_comp/value_comp比較函數的使用。通過多個代碼示例演示了map的基本操作,包括元素訪問、修改和異常 https://blog.csdn.net/2401_86446710/article/details/149344636?spm=1011.2415.3001.10575&sharefrom=mp_manage_link非常建議去了解一下C++的紅黑樹的內容,以方便我們實現map和set:

C++進階-紅黑樹(難度較高)-CSDN博客文章瀏覽閱讀1.7k次,點贊52次,收藏56次。本文詳細講解了紅黑樹的實現原理和代碼實現。主要內容包括: 紅黑樹的基本概念和五大規則,確保最長路徑不超過最短路徑的兩倍; 紅黑樹的插入操作,重點分析了不同情況下的變色和旋轉處理(單旋、雙旋); 完整代碼實現,包括結點定義、旋轉操作、插入邏輯和驗證方法; 紅黑樹查找的實現和測試用例; 簡要介紹了刪除操作的思路。 紅黑樹通過顏色約束和旋轉操作保持近似平衡,效率與AVL樹相當但旋轉次數更少。本文強調理解旋轉邏輯的重要性,建議通過畫圖輔助理解。代碼經過測試驗證,為后續用紅黑樹實現map和set打下基礎。 https://blog.csdn.net/2401_86446710/article/details/149436642?spm=1011.2415.3001.10575&sharefrom=mp_manage_link

map和set的iterator和list差不多,建議各位參考一下list的模擬實現,可以見以下博客:

C++初階-list的模擬實現(難度較高)-CSDN博客文章瀏覽閱讀916次,點贊27次,收藏24次。這篇文章詳細講解了C++中list容器的模擬實現過程。主要內容包括:1) list基本結構的定義,使用雙向鏈表實現;2) 無參構造函數和節點構造函數的實現;3) push_back等基本操作的實現;4) 迭代器類的設計與實現,包括普通迭代器和const迭代器;5) list類迭代器相關函數的實現;6) 增刪查改等操作的實現;7) 拷貝控制成員函數和特殊成員函數的實現;8) 測試用例的編寫。文章通過代碼示例詳細展示了list容器的底層實現原理,包括哨兵位的使用、迭代器封裝等技術細節,并提供了完整的測試代碼驗證 https://blog.csdn.net/2401_86446710/article/details/148548215?spm=1011.2415.3001.10575&sharefrom=mp_manage_link

2.初步代碼

該代碼是實現紅黑樹的實現時,去掉注釋后的代碼:

#define _CRT_SECURE_NO_WARNINGS 1
#include <assert.h>
#include<vector>
#include<iostream>
using namespace std;
enum Color
{Red,Black
};
template<class K,class V>
struct RBTreeNode
{pair<K, V> _kv;RBTreeNode<K, V>* _parent;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;Color _col;RBTreeNode(const pair<K,V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr){ }
};
template<class K,class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:void RotateR(Node* parent){assert(parent != nullptr);Node* subL = parent->_left;assert(subL != nullptr);Node* subLR = subL->_right;parent->_left = subLR;subL->_right = parent;if (subLR){subLR->_parent = parent;}Node* parentParent = parent->_parent;parent->_parent = subL;if (parent == _root){subL->_parent = nullptr;_root = subL;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}void RotateL(Node* parent){assert(parent != nullptr);Node* subR = parent->_right;assert(subR != nullptr);Node* subRL = subR->_left;parent->_right = subRL;if (subRL){subRL->_parent = parent;}subR->_left = parent;Node* parentParent = parent->_parent;parent->_parent = subR;if (parent == _root){subR->_parent = nullptr;_root = subR;}else{if (parentParent->_left == parent){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}}bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = Black;return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);cur->_parent = parent;if (parent->_kv.first > kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_col = Red;while (parent && parent->_col == Red){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == Red){uncle->_col = parent->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{if (parent->_left == cur){RotateR(grandfather);parent->_col = Black;grandfather->_col = Red;}else{RotateL(parent);RotateR(grandfather);cur->_col = Black;grandfather->_col = Red;}break;}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == Red){uncle->_col = parent->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{if (parent->_right == cur){parent->_col = Black;grandfather->_col = Red;}else{RotateR(parent);RotateL(grandfather);grandfather->_col = Red;cur->_col = Black;}break;}}}_root->_col = Black;return true;}Node* Find(const K& key){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 Check(Node* root, int blackNum, const int refNum){if (root == nullptr){if (refNum != blackNum){cout << "存在??結點的數量不相等的路徑" << endl;return false;}return true;}if (root->_col == Red && root->_parent->_col == Red){cout << root->_kv.first << "存在連續的紅?結點" << endl;return false;}if (root->_col == Black){blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);}bool IsBalance(){if (_root == nullptr)return true;if (_root->_col == Red)return false;int refNum = 0;Node* cur = _root;while (cur){if (cur->_col == Black){++refNum;}cur = cur->_left;}return Check(_root, 0, refNum);}void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}
private:Node* _root = nullptr;
};

以上代碼將會放到RBTree.h文件中。

3.對紅黑樹實現的代碼進行改造

改造紅黑樹代碼是為了實現我們的set和map兩個容器。

由于set是只存放了一個值,故我們要把紅黑樹的結點結構改成key結構:

template<class T>
struct RBTreeNode
{T _data;//存放數據,可以傳遞T為pair類型RBTreeNode<T>* _parent;RBTreeNode<T>* _left;RBTreeNode<T>* _right;Color _col;RBTreeNode(const T& data):_data(data), _left(nullptr), _right(nullptr), _parent(nullptr){}
};

我們還需要把RBTree.h中改一部分:

template<class K,class T>
class RBTree
{
public:typedef RBTreeNode<T> Node;bool Insert(const T& kv){//……}
//……
};

Insert函數里面結構我們先不要改,之后再進行集中改變。

4.對map和set的改造

首先我們需要把map和set的基本結構寫出來,這里map的模擬實現放到mymap.h中,set的模擬實現放到myset.h中:

//myset.h
#include"RBTree.h"
template<class K>
class myset
{
public://以后代碼增加的地方bool insert(const K& key){return _t.Insert(key);}
private:RBTree<K, K> _t;
};

為了不破壞太多的RBTree結構,我們這里的set就構造成key-key結構,保持和set的源碼的方式一樣(源碼各位可以自行搜索,如果想改造成一個參數的結構就把RBTree第二個模板參數置為缺省參數,缺省值為K)。

//mymap.h
#include"RBTree.h"
template<class K,class V>
class mymap
{
public://以后代碼增加的地方bool insert(const pair<K,V>& kv){return _t.Insert(kv);}
private:RBTree<K, pair<K,V>> _t;
};

這樣寫的原因是:我們的RBTree的第二個模板參數是T,相當于我們的鍵值對,這樣寫主要是為了適應RBTree的insert函數。

5.對RBTree::insert的改造

改造該函數之前的代碼:

bool Insert(const T& kv)
{if (_root == nullptr){_root = new Node(kv);_root->_col = Black;return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);cur->_parent = parent;if (parent->_kv.first > kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_col = Red;while (parent && parent->_col == Red){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == Red){uncle->_col = parent->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{if (parent->_left == cur){RotateR(grandfather);parent->_col = Black;grandfather->_col = Red;}else{RotateL(parent);RotateR(grandfather);cur->_col = Black;grandfather->_col = Red;}break;}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == Red){uncle->_col = parent->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{if (parent->_right == cur){parent->_col = Black;grandfather->_col = Red;}else{RotateR(parent);RotateL(grandfather);grandfather->_col = Red;cur->_col = Black;}break;}}}_root->_col = Black;return true;
}

由于參數改變,我們需要把_kv.first直接變成_data,所以有:

bool Insert(const T& data)
{if (_root == nullptr){_root = new Node(data);_root->_col = Black;return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_data > data){parent = cur;cur = cur->_left;}else if (cur->_data < data){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(data);cur->_parent = parent;if (parent->_data > data){parent->_left = cur;}else{parent->_right = cur;}cur->_col = Red;while (parent && parent->_col == Red){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == Red){uncle->_col = parent->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{if (parent->_left == cur){RotateR(grandfather);parent->_col = Black;grandfather->_col = Red;}else{RotateL(parent);RotateR(grandfather);cur->_col = Black;grandfather->_col = Red;}break;}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == Red){uncle->_col = parent->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{if (parent->_right == cur){parent->_col = Black;grandfather->_col = Red;}else{RotateR(parent);RotateL(grandfather);grandfather->_col = Red;cur->_col = Black;}break;}}}_root->_col = Black;return true;
}

這里主要是要注意一下:如果我們是插入一個鍵值對即pair類型時,如果我們直接用parent->_data<data能不能比較出正確的結果,實際上,我們在C++官網中也很容易得到結論:

但是我們就想要只用first進行比較,所以我們不能直接這樣寫。

如果我們使用仿函數來進行比較邏輯,那么在這個時候也是不行的,因為如果用仿函數的話,我們要Find,而Find是用key來比較的,如果用仿函數,我們如何取得map的key值從而使比較邏輯正確?

實際上,我們可以用仿函數進行,但是這個仿函數不能單單用來比較大小,還要取得key,這個時候,我們可以這樣寫:

//RBTree.h
template<class K,class T,class KeyOfT>
class RBTree
{};

通過給RBTree增加一個模板參數來接收仿函數進行比較的邏輯,同時要改:

//myset.h
template<class K>
class myset
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};
private:RBTree<K, K, SetKeyOfT> _t;
};
//mymap.h
template<class K,class V>
class mymap
{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};
private:RBTree<K, pair<K, V>, MapKeyOfT> _t;
};

同時,我們還需要把比較邏輯改成二者key的比較,所以可以這樣寫:

	bool Insert(const T& data){if (_root == nullptr){_root = new Node(kv);_root->_col = Black;return true;}KeyOfT kot;Node* cur = _root;Node* parent = nullptr;while (cur){if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(data);cur->_parent = parent;if (kot(parent->_data) > kot(data)){parent->_left = cur;}else{parent->_right = cur;}
//……
}

6.對RBTree::Find函數的改造

和insert函數類似,我們也可以對Find函數進行改造:

//RBTree.h
bool Find(const K& key)
{KeyOfT kot;Node* cur = _root;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return true;}}return false;
}

7.實現iterator(最重要)

實現iterator的思路:

(1)iterator實現的?框架跟list的iterator思路是?致的,??個類型封裝結點的指針,再通過重載運算符實現,迭代器像指針?樣訪問的?為。

這里我先把iterator基本結構寫出來:

//RBTree.h
template<class T>
struct _TreeIterator
{typedef RBTreeNode<T> Node;typedef _TreeIterator<T> Self;Node* _node;_TreeIterator(Node* node):_node(node){ }T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node!=s._node;}
};

我們這里主要要理解一下如何實現operator++和operator--。

首先map和set走的是中序遍歷,所以返回的第一個結點的iterator是begin()。

第一個結點就是10這個結點,也就是說我們需要從根結點開始走起,一直往左子樹遍歷,直到左子樹為空時。

(2)迭代器++的核?邏輯就是不看全局,只看局部,只考慮當前中序局部要訪問的下?個結點。

我們不用管其他結點,就看這個子樹,如果這棵子樹遍歷到左孩子,就看根;如果遍歷到根,就看右孩子。

(3)迭代器++時,如果it指向的結點的右?樹不為空,代表當前結點已經訪問完了,要訪問下?個結點是右?樹的中序第?個,?棵樹中序第?個是最左結點,所以直接找右?樹的最左結點即可。

(4)迭代器++時,如果it指向的結點的右?樹空,代表當前結點已經訪問完了且當前結點所在的?樹也訪問完了,要訪問的下?個結點在當前結點的祖先??,所以要沿著當前結點到根的祖先路徑向上找。

(5)如果當前結點是?親的左,根據中序左?樹->根結點->右?樹,那么下?個訪問的結點就是當前結點的?親;

(6)如果當前結點是?親的右,根據中序左?樹->根結點->右?樹,當前結點所在的?樹訪問完了,當前結點所在?親的?樹也訪問完了,那么下?個訪問的需要繼續往根的祖先中去找,直到找到孩?是?親左的那個祖先就是中序要訪問的下?個結點。

這上面都是有些難以理解的,我這里就直接用畫圖的方式加以講解:

我按照中序遍歷的順序進行講解。

首先從根結點開始往左子樹遍歷,發現18的左孩子不為空,則10作為左子樹的根結點繼續遍歷,然后發現10沒有左孩子,這個時候就遍歷到10這個結點。

遍歷到10結點后,++,我們發現10為根結點,所以要繼續遍歷右子樹,遍歷到15結點,發現15結點沒有左孩子,所以直接返回到15這個結點。

遍歷到15結點后,++,我們發現15結點沒有右子樹,所以往上遞歸,然后發現10為18的左孩子,所以是一定遍歷過的,所以就繼續往上;發現18是根結點,這個時候就返回18這個結點了。

遍歷到18結點后,++,發現30為右孩子,而且30還有左孩子,所以遍歷到25,發現25沒有左孩子,所以返回25。

遍歷到25結點后,++,發現25沒有右孩子,所以返回到25的父親,到30結點,返回30。

遍歷到30結點后,………………

直到到達50結點(已經被訪問過),如果++,發現沒有右孩子,然后往上,發現父親都為爺爺的右孩子,證明該子樹遍歷完,所以繼續往上訪問,直到沒有父親或者該孩子為父親的左結點為止。這個時候我們發現父親為空,則這個時候返回nullptr,所以這個時候直接就是用nullptr構造一個結點進行返回。

//_TreeIterator
Self& operator++()
{//中序遍歷,這個時候就需要訪問右孩子if (_node->_right){//找右子樹的最左結點_node = _node->_right;while (_node->_left){_node = _node->_left;}}else{//右子樹為空//對應15結點或者50結點Node* cur = _node;Node* parent = cur->_parent;//當parent不為空,且cur為parent的右孩子是繼續往上//因為這代表該子樹已經遍歷完了while (parent && cur == parent->_right){cur = parent;parent = cur->_parent;}//要么cur是parent的左孩子,這個時候就可以直接用_node=parent//這兩種情況都可以這樣寫_node = parent;}return *this;
}

以上邏輯需要結合畫圖理解,最重要的是我們要理解為什么當右孩子沒有時要往上遞歸,因為這個時候該子樹都遍歷完了(如:15結點,為10的右孩子,根據左根右的遍歷方式,右孩子都被遍歷完了,那么也就代表根也被遍歷完了,所以我們需要往上,此外,如果cur(當前結點)是父親的左孩子,代表父親(根)還沒有被遍歷,所以直接用父親作為結點返回)。

相似的邏輯我們還可以寫出--,其邏輯和++完全相反,不過還有特殊處理一下當_node==nullptr的情況,這個時候就是end(),我們需要得到root以遍歷子樹:

//_TreeIterator
Node* _root;
_TreeIterator(Node* node,Node* root):_node(node),_root(root)
{ }
Self& operator--()
{if (_node == nullptr) // end(){// --end(),特殊處理,?到中序最后?個結點,整棵樹的最右結點Node* rightMost = _root;while (rightMost && rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else if (_node->_left){//代表該子樹還沒遍歷完//找左子樹的最右結點_node = _node->_left;while (_node->_right){_node = _node->_right;}}else{//該子樹被遍歷完//向上查找,直到查找到根或者該孩子為父親的右孩子Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = cur->_parent;}_node = parent;}return *this;
}

我們的目的是為了得到Bgein和End,所以在RBTree中加:

//RBTree
public:
typedef _TreeIterator<T> Iterator;
Iterator Begin()
{Node* minleft = _root;while (minleft && minleft->_left){minleft = minleft->_left;}//調用_TreeIterator//相當于_TreeIterator(minleft)return Iterator(minleft, _root);
}
Iterator End()
{return Iterator(nullptr, _root);
}

同樣,我們還需要在mymap和myset中進行修改:

//myset
public:
typedef RBTree<K, K, SetKeyOfT>::Iterator iterator;
iterator begin()
{return _t.Begin();
}
iterator end()
{return _t.End();
}
//mymap
public:
typedef RBTree<K, pair<K, V>, MapKeyOfT>::Iterator iterator;
iterator begin()
{return _t.Begin();
}
iterator end()
{return _t.End();
}

這個時候要注意:我們現在在測試文件中隨便寫一個主函數都會報錯:

這主要是因為:如果我們要取模板里面的類型(含內嵌類型、內部類)都要加typename,因為取模板里的東西,現在的模板可能還沒有實例化,此時取的模板里面的類型編譯器分不清楚它是靜態成員變量還是內嵌類型,而我們這樣寫:

typedef typename RBTree<K, K, SetKeyOfT>::Iterator iterator;
typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::Iterator iterator;

那個typename就是告訴編譯器這是一個類型,是合乎語法的,而確定這個類型要等到類模板被實例化后就可以了,這樣就沒問題了。

不過我們也可以改造成這樣也是沒問題的:

using iterator = typename RBTree<K, K, SetKeyOfT>::Iterator;
using iterator = typename RBTree<K, pair<K, V>, MapKeyOfT>::Iterator;

這種寫法更好一些,以后見到這種寫法也不要奇怪。

以上是iterator基本的實現,不過如果你覺得可以再進行完善功能的話也可以,以下代碼可以選擇性加到_TreeIterator中:

//_TreeIterator
bool operator==(const Self& s)
{return _node == s._node;
}
//后置++
Self operator++(int)
{Self tmp = *this;  // 保存當前狀態++(*this);         // 調用前置++實現return tmp;        // 返回舊值
}
//后置--
Self operator--(int)
{Self tmp = *this;  // 保存當前狀態--(*this);         // 調用前置--實現return tmp;        // 返回舊值
}

8.實現const_iterator

和list一樣,我們最好還是不要增加一個一樣的類來實現const_iterator,可以在_TreeIterator模板參數后加兩個模板參數,以下是_TreeIterator的改造:

//_TreeIterator
template<class T,class Ref,class Ptr>
struct _TreeIterator
{
typedef _TreeIterator<T, Ref, Ptr> Self;//Ref:引用,Ptr:指針
Ref operator*()
{return _node->_data;
}
Ptr operator->()
{return &_node->_data;
}
};

以下是對RBTree的改造:

template<class K,class T,class KeyOfT>
class RBTree
{
public:
typedef _TreeIterator<T, T&, T*> Iterator;
typedef _TreeIterator<T, const T&, const T*> ConstIterator;
ConstIterator Begin() const
{Node* minleft = _root;while (minleft && minleft->_left){minleft = minleft->_left;}return ConstIterator(minleft, _root);
}
ConstIterator End() const
{return ConstIterator(nullptr, _root);
}
};

同理,我們還需要在mymap和myset中進行改造:

//myset
public:
//下面兩種隨意選:
//typedef typename RBTree<K, K, SetKeyOfT>::ConstIterator const_iterator;
using const_iterator = typename RBTree<K, K, SetKeyOfT>::ConstIterator;
const_iterator begin() const
{return _t.Begin();
}
const_iterator end() const
{return _t.End();
}
//mymap
//下面兩種隨便選一種
//typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::ConstIterator const_iterator;
using const_iterator = typename RBTree<K, pair<K, V>, MapKeyOfT>::ConstIterator;
const_iterator begin() const
{return _t.Begin();
}
const_iterator end() const
{return _t.End();
}

9.完成set和map的key不能修改

這個是改造代碼比較少的部分,但是還是需要注意,我們不支持key修改不是代表value不支持修改,所以需要改造成這樣:

//myset
public:
//下面兩種隨意選
//typedef typename RBTree<K,const K, SetKeyOfT>::Iterator iterator;
using iterator = typename RBTree<K, const K, SetKeyOfT>::Iterator;
//下面兩種隨意選
//typedef typename RBTree<K,const K, SetKeyOfT>::ConstIterator const_iterator;
using const_iterator = typename RBTree<K,const K, SetKeyOfT>::ConstIterator;
private:RBTree<K, const K, SetKeyOfT> _t;

需要注意的是RBTree的第二個參數才是所存儲的東西,所以是第二個參數加const,第一個參數代表的是類型,第二個參數代表的是所存儲的值。

類似,我們還可以對mymap進行改造:

//mymap
public:
//下面兩種隨便選一種
//typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;
using iterator = typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator;
//下面兩種隨便選一種
//typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator const_iterator;
using const_iterator = typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator;
private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;

10.實現map的operator[]

首先,實際上,map的Insert函數返回值是pair<Iterator,bool>類型,map的operator[]會根據insert的返回值的bool來判斷是否需要插入,當insert成功時,第一個參數是為新插入位置的結點地址,當insert失敗時,第一個參數是為原結點的地址。

而這個時候operator[]就返回了該結點對應的值(value)。

所以關鍵是對Insert的改造,我們可以改造成這樣:

//RBTree
pair<Iterator,bool> Insert(const T& data)
{if (_root == nullptr){_root = new Node(data);_root->_col = Black;return { Iterator(_root,_root),true };//隱式類型轉換}KeyOfT kot;Node* cur = _root;Node* parent = nullptr;while (cur){if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else{return { Iterator(cur,_root),true };}}cur = new Node(data);Node* newnode = cur;cur->_parent = parent;if (kot(parent->_data) > kot(data)){parent->_left = cur;}else{parent->_right = cur;}cur->_col = Red;while (parent && parent->_col == Red){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == Red){uncle->_col = parent->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{if (parent->_left == cur){RotateR(grandfather);parent->_col = Black;grandfather->_col = Red;}else{RotateL(parent);RotateR(grandfather);cur->_col = Black;grandfather->_col = Red;}break;}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == Red){uncle->_col = parent->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{if (parent->_right == cur){parent->_col = Black;grandfather->_col = Red;}else{RotateR(parent);RotateL(grandfather);grandfather->_col = Red;cur->_col = Black;}break;}}}_root->_col = Black;return { Iterator(newnode,_root),true };
}

再對myset和mymap進行改造:

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

如果對于mymap則不僅需要改造insert函數,還需要完成operator[]的實現這個時候我們需要返回值的類型是V&,我們需要接收一下insert的返回值,如:pair<iterator,bool> ret=_t.Insert({key,V()});我們這樣寫的目的是mymap對RBTree的模板參數T實例化為pair<const K,V>了,所以需要構造一個pair對象進行傳入,然后我們需要返回這個返回值的iterator里的值,因為iterator實際上存儲的還是一個pair對象,所以要返回的是值,為second,所以寫成了如下奇怪的形式:

ret.first->second;

再解釋一遍是Insert返回的是插入的值的迭代器作為鍵,是否插入成功的結果作為值,我們想得到的是鍵,所以用對象.first訪問到,而iterator本質上是存儲了一個鍵值對的,而訪問只能通過迭代器->second或者->first進行,(也可以*(對象).first和*(對象).second進行),但是為了方便理解,這里就用->形式了。

總之我們用operator[]的結果就是為了得到鍵對應的值,而我們調用Insert的目的是避免重復插入并且返回最終該值對應的迭代器,而該迭代器是存儲了一個鍵值對的,我們要得到傳入的鍵所對應的值,所以要這樣寫,所以在mymap中:

//mymap
pair<iterator,bool> insert(const pair<K,V>& kv)
{return _t.Insert(kv);
}
V& operator[](const K& key)
{pair<iterator, bool> ret = _t.Insert({ key,V() });//調用V對應的類型的構造函數初始化該鍵對應的值return ret.first->second;
}

11.代碼匯總

在map和set中重要的函數已經實現了,像什么:

剩余的函數感興趣的可以自行實現一下,掌握到這個地步已經可以了。

以下是三個文件的代碼:

//myset.h
#include"RBTree.h"
template<class K>
class myset
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};
public://typedef typename RBTree<K, K, SetKeyOfT>::Iterator iterator;//using iterator = typename RBTree<K, K, SetKeyOfT>::Iterator;//下面兩種隨意選//typedef typename RBTree<K,const K, SetKeyOfT>::Iterator iterator;using iterator = typename RBTree<K, const K, SetKeyOfT>::Iterator;//下面兩種隨意選://typedef typename RBTree<K, K, SetKeyOfT>::ConstIterator const_iterator;//using const_iterator = typename RBTree<K, K, SetKeyOfT>::ConstIterator;//下面兩種隨意選//typedef typename RBTree<K,const K, SetKeyOfT>::ConstIterator const_iterator;using const_iterator = typename RBTree<K,const K, SetKeyOfT>::ConstIterator;pair<iterator,bool> insert(const K& key){return _t.Insert(key);}iterator begin(){return _t.Begin();}iterator end(){return _t.End();}const_iterator begin() const{return _t.Begin();}const_iterator end() const{return _t.End();}
private://RBTree<K, K, SetKeyOfT> _t;RBTree<K, const K, SetKeyOfT> _t;
};
//mymap.h
#include"RBTree.h"
template<class K,class V>
class mymap
{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};
public://typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::Iterator iterator;//using iterator = typename RBTree<K, pair<K, V>, MapKeyOfT>::Iterator;//下面兩種隨便選一種//typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;using iterator = typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator;//下面兩種隨便選一種//typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::ConstIterator const_iterator;//using const_iterator = typename RBTree<K, pair<K, V>, MapKeyOfT>::ConstIterator;//下面兩種隨便選一種//typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator const_iterator;using const_iterator = typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator;pair<iterator,bool> insert(const pair<K,V>& kv){return _t.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = _t.Insert({ key,V() });//調用V對應的類型的構造函數初始化該鍵對應的值return ret.first->second;}iterator begin(){return _t.Begin();}iterator end(){return _t.End();}const_iterator begin() const{return _t.Begin();}const_iterator end() const{return _t.End();}
private://RBTree<K, pair<K, V>, MapKeyOfT> _t;RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
//RBTree.h
#define _CRT_SECURE_NO_WARNINGS 1
#include <assert.h>
#include<vector>
#include<iostream>
using namespace std;
enum Color
{Red,Black
};
template<class T>
struct RBTreeNode
{T _data;//存放數據,可以傳遞T為pair類型RBTreeNode<T>* _parent;RBTreeNode<T>* _left;RBTreeNode<T>* _right;Color _col;RBTreeNode(const T& data):_data(data), _left(nullptr), _right(nullptr), _parent(nullptr){}
};
template<class T,class Ref,class Ptr>
struct _TreeIterator
{typedef RBTreeNode<T> Node;typedef _TreeIterator<T, Ref, Ptr> Self;//Ref:引用,Ptr:指針Node* _node;Node* _root;_TreeIterator(Node* node,Node* root):_node(node),_root(root){ }Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}//后置++Self operator++(int){Self tmp = *this;  // 保存當前狀態++(*this);         // 調用前置++實現return tmp;        // 返回舊值}//后置--Self operator--(int){Self tmp = *this;  // 保存當前狀態--(*this);         // 調用前置--實現return tmp;        // 返回舊值}//前置++Self& operator++(){//中序遍歷,這個時候就需要訪問右孩子if (_node->_right){//找右子樹的最左結點_node = _node->_right;while (_node->_left){_node = _node->_left;}}else{//右子樹為空//對應15結點或者50結點Node* cur = _node;Node* parent = cur->_parent;//當parent不為空,且cur為parent的右孩子是繼續往上//因為這代表該子樹已經遍歷完了while (parent && cur == parent->_right){cur = parent;parent = cur->_parent;}//要么cur是parent的左孩子,這個時候就可以直接用_node=parent//這兩種情況都可以這樣寫_node = parent;}return *this;}//前置--Self& operator--(){if (_node == nullptr) // end(){// --end(),特殊處理,?到中序最后?個結點,整棵樹的最右結點Node* rightMost = _root;while (rightMost && rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else if (_node->_left){//代表該子樹還沒遍歷完//找左子樹的最右結點_node = _node->_left;while (_node->_right){_node = _node->_right;}}else{//該子樹被遍歷完//向上查找,直到查找到根或者該孩子為父親的右孩子Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}
};
template<class K,class T,class KeyOfT>
class RBTree
{
public:typedef RBTreeNode<T> Node;typedef _TreeIterator<T, T&, T*> Iterator;typedef _TreeIterator<T, const T&, const T*> ConstIterator;void RotateR(Node* parent){assert(parent != nullptr);Node* subL = parent->_left;assert(subL != nullptr);Node* subLR = subL->_right;parent->_left = subLR;subL->_right = parent;if (subLR){subLR->_parent = parent;}Node* parentParent = parent->_parent;parent->_parent = subL;if (parent == _root){subL->_parent = nullptr;_root = subL;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}void RotateL(Node* parent){assert(parent != nullptr);Node* subR = parent->_right;assert(subR != nullptr);Node* subRL = subR->_left;parent->_right = subRL;if (subRL){subRL->_parent = parent;}subR->_left = parent;Node* parentParent = parent->_parent;parent->_parent = subR;if (parent == _root){subR->_parent = nullptr;_root = subR;}else{if (parentParent->_left == parent){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}}pair<Iterator,bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = Black;return { Iterator(_root,_root),true };//隱式類型轉換}KeyOfT kot;Node* cur = _root;Node* parent = nullptr;while (cur){if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else{return { Iterator(cur,_root),true };}}cur = new Node(data);Node* newnode = cur;cur->_parent = parent;if (kot(parent->_data) > kot(data)){parent->_left = cur;}else{parent->_right = cur;}cur->_col = Red;while (parent && parent->_col == Red){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_col == Red){uncle->_col = parent->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{if (parent->_left == cur){RotateR(grandfather);parent->_col = Black;grandfather->_col = Red;}else{RotateL(parent);RotateR(grandfather);cur->_col = Black;grandfather->_col = Red;}break;}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == Red){uncle->_col = parent->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{if (parent->_right == cur){parent->_col = Black;grandfather->_col = Red;}else{RotateR(parent);RotateL(grandfather);grandfather->_col = Red;cur->_col = Black;}break;}}}_root->_col = Black;return { Iterator(newnode,_root),true };}Iterator Begin(){Node* minleft = _root;while (minleft && minleft->_left){minleft = minleft->_left;}//調用_TreeIterator//相當于_TreeIterator(minleft)return Iterator(minleft, _root);}Iterator End(){return Iterator(nullptr, _root);}ConstIterator Begin() const{Node* minleft = _root;while (minleft && minleft->_left){minleft = minleft->_left;}return ConstIterator(minleft, _root);}ConstIterator End() const{return ConstIterator(nullptr, _root);}bool Find(const K& key){KeyOfT kot;Node* cur = _root;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return true;}}return false;}bool Check(Node* root, int blackNum, const int refNum){if (root == nullptr){if (refNum != blackNum){cout << "存在??結點的數量不相等的路徑" << endl;return false;}return true;}if (root->_col == Red && root->_parent->_col == Red){cout << root->_kv.first << "存在連續的紅?結點" << endl;return false;}if (root->_col == Black){blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);}bool IsBalance(){if (_root == nullptr)return true;if (_root->_col == Red)return false;int refNum = 0;Node* cur = _root;while (cur){if (cur->_col == Black){++refNum;}cur = cur->_left;}return Check(_root, 0, refNum);}void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}
private:Node* _root = nullptr;
};

12.總結

map和set和紅黑樹在C++進階中都算比較重要的知識,如果能學會這些就比較厲害了,封裝紅黑樹實現map和set的邏輯性比較強,建議各位加以理解。

好了,該講內容就到這里,下講將講解C++進階-哈希(非常重要),哈希理解起來不是很難,但是實現的時候可能難度比較高,所以各位做好心理準備哦。

喜歡的可以一鍵三連哦,下講再見!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/917565.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/917565.shtml
英文地址,請注明出處:http://en.pswp.cn/news/917565.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

安裝MySQL可視化管理工具DBeaver教程

系統&#xff08;kelin&#xff09;上安裝MySQL可視化管理工具DBeaver教程 背景說明 在國產操作系統麒麟&#xff08;基于Debian/Ubuntu&#xff09;環境下&#xff0c;MySQL數據庫管理常依賴命令行&#xff0c;效率較低且不便于直觀操作。借助 DBeaver 這類跨平臺的圖形化數據…

非機動車亂停放識別準確率↑37%:陌訊多特征融合算法實戰解析

一、行業痛點&#xff1a;非機動車治理的技術瓶頸根據《2023 城市靜態交通治理報告》顯示&#xff0c;國內一線城市非機動車亂停放占用人行道比例超 60%&#xff0c;傳統監控方案存在三大技術難點&#xff1a;遮擋干擾&#xff1a;共享單車與私人電動車堆疊導致目標完整性缺失&…

Eclipse 安裝插件指南

Eclipse 安裝插件指南 引言 Eclipse 是一款強大的集成開發環境(IDE),廣泛應用于Java、C/C++、PHP等多種編程語言。為了提高開發效率,Eclipse 支持通過插件來擴展其功能。本文將詳細介紹如何在Eclipse中安裝插件,幫助您快速提升開發體驗。 插件概述 Eclipse 插件是用于…

區塊鏈 和 一致性哈希的結合

怎么結合呢&#xff1f; 我們先來回顧一下一致性哈希代碼實現里面的結構 // Consistent holds the information about the members of the consistent hash circle. type Consistent struct {mu sync.RWMutex // 讀寫鎖&#xff0c;用于保護并發訪問共享數據config Con…

使用yolo11訓練智慧醫療-孤獨癥兒童行為檢測數據集VOC+YOLO格式7295張34類別步驟和流程

【數據集介紹】數據集中有很多增強圖片&#xff0c;也有很多視頻連續截取圖片請查看圖片預覽數據集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路徑的txt文件&#xff0c;僅僅包含jpg圖片以及對應的VOC格式xml文件和yolo格式txt文件)圖片數量(jpg文件個數)&#xff1a;…

vim 組件 使用pysocket進行sock連接

vim組件實現 以下是使用 Vim 插件架構實現 Python Socket 客戶端的完整方案&#xff0c;支持集成到 Vim 控件并實現雙向通信&#xff1a; ~/.vim/plugin/socket_client.vim" 定義全局命令和快捷鍵 command! -nargs* SocketConnect call s:StartSocketClient(<f-args>…

FFmpeg+javacpp中純音頻播放

FFmpegjavacpp中純音頻播放1. Java Sound播放2、整合音頻信息AudioInfo3、添加ExecutorService執行播放FFmpegjavacppjavacv使用 FFmpegjavacpp中FFmpegFrameGrabber FFmpegjavacpp中仿ffplay播放 JavaCV 1.5.12 API JavaCPP Presets for FFmpeg 7.1.1-1.5.12 API1. Java Soun…

洛谷P1036 [NOIP 2002 普及組] 選數

P1036 [NOIP 2002 普及組] 選數 題目描述 已知 nnn 個整數 x1,x2,??,xnx_1,x_2,\cdots,x_nx1?,x2?,?,xn?&#xff0c;以及 111 個整數 kkk&#xff08;k<nk<nk<n&#xff09;。從 nnn 個整數中任選 kkk 個整數相加&#xff0c;可分別得到一系列的和。例如當 n4n…

Linux學習記錄(八)文件共享

本文記錄在Vmware中啟用文件共享時的一些注意事項&#xff1a;1.提前安裝vmware-tools&#xff0c;可以通過Vmware的虛擬機菜單欄中拿到文件&#xff0c;然后直接運行vmware-install.pl文件進行安裝&#xff1b;也可以通過指令sudo apt-get install open-vm-tools進行安裝。推薦…

洛谷 火燒赤壁 差分/貪心

題目背景曹操平定北方以后&#xff0c;公元 208 年&#xff0c;率領大軍南下&#xff0c;進攻劉表。他的人馬還沒有到荊州&#xff0c;劉表已經病死。他的兒子劉琮聽到曹軍聲勢浩大&#xff0c;嚇破了膽&#xff0c;先派人求降了。孫權任命周瑜為都督&#xff0c;撥給他三萬水軍…

Linux 用戶與組管理全解析

Linux 用戶與組管理一、用戶和組的基本概念 1. 用戶賬號類型 超級用戶&#xff08;root&#xff09;&#xff1a;默認擁有系統最高權限&#xff08;UID0&#xff09;&#xff0c;僅建議用于系統管理與維護&#xff0c;日常操作應使用普通用戶。普通用戶&#xff1a;由管理員創建…

開疆智能ModbusTCP轉Profient網關連接ER機器人配置案例

本案例時西門子1200PLC通過ModbusTCP轉Profinet網關連接埃斯頓機器人的配置案例&#xff0c;網關作為ModbusTCP的客戶端連接機器人。配置過程&#xff1a;首先打開機器人通訊手冊。查詢機器人支持的功能碼及默認IP和端口號打開網關配置軟件“Gateway Configuration Studio”新建…

Docker換源加速(更換鏡像源)詳細教程(2025.3最新可用鏡像,全網最詳細)

文章目錄前言可用鏡像源匯總換源方法1-臨時換源換源方法2-永久換源&#xff08;推薦&#xff09;常見問題及對應解決方案1.換源后&#xff0c;可以成功pull&#xff0c;但是search會出錯補充1.如何測試鏡像源是否可用2.Docker內的Linux換源教程換源速通版&#xff08;可以直接無…

機器學習【三】SVM

本文系統介紹了支持向量機(SVM)的理論與實踐。理論部分首先區分了線性可分與不可分問題&#xff0c;闡述了SVM通過尋找最優超平面實現分類的核心思想&#xff0c;包括支持向量、間隔最大化等關鍵概念。詳細講解了硬間隔與軟間隔SVM的數學原理&#xff0c;以及核函數(線性核、多…

DevOps平臺大比拼:Gitee、Jenkins與CircleCI如何選型?

DevOps平臺大比拼&#xff1a;Gitee、Jenkins與CircleCI如何選型&#xff1f; 在數字化轉型浪潮席卷全球的當下&#xff0c;DevOps已成為企業提升研發效能的關鍵引擎。面對市場上紛繁復雜的DevOps工具鏈&#xff0c;如何選擇最適合自身業務需求的平臺成為技術決策者的重要課題。…

開源醫院信息管理系統:基于若依框架的智慧醫療解決方案

引言在數字化浪潮的推動下&#xff0c;醫療行業正加速向信息化、智能化轉型。醫院信息管理系統&#xff08;HIS&#xff09;作為醫療管理的核心工具&#xff0c;直接影響醫院的運營效率和服務質量。近期&#xff0c;一款基于 若依框架 Vue 的開源醫院管理系統&#xff08;hosp…

我的世界進階模組開發教程——附魔(2)

EnchantmentHelper 類詳解 EnchantmentHelper 是 Minecraft 中處理物品附魔邏輯的核心工具類,提供附魔的存儲、查詢、計算和應用等功能。以下是對其字段和方法的逐行詳細解釋: 關鍵字段 private static final String TAG_ENCH_ID = "id"; // NBT標簽鍵:附…

深度學習零基礎入門(4)-卷積神經網絡架構

許久不見~ 本節我們延續上一節的話題來看看卷積神經網絡的架構&#xff0c;看看具體的卷積、池化等操作卷積神經網絡詳解&#xff1a;從基礎操作到整體架構 一、卷積操作&#xff1a;特征提取的核心 卷積是卷積神經網絡&#xff08;CNN&#xff09;的核心操作&#xff0c;靈感來…

C語言的控制語句

C的控制語句 控制語句是C語言中用于控制程序執行流程的結構。通過控制語句,可以根據條件執行不同的代碼塊,或者重復執行某些操作,從而實現復雜的邏輯和功能。掌握控制語句是編寫有效和高效C程序的關鍵。 1 條件控制 條件控制語句用于根據某些條件來決定程序的執行路徑。C語…

Mac電腦基本功能快捷鍵

1. 個性化桌面 將喜愛照片添加為桌面墻紙。前往“系統設置”&#xff0c;然后點按邊欄中的“墻紙”。點按“添加照片”&#xff0c;然后從文件或“照片”App選取一張照片。 2. 截屏 按下鍵盤上的Shift &#xfffc; Command ? 5&#xff0c;然后選取捕捉整個屏幕、App窗口或…