C++紅黑樹應用之手搓set和map
一、set/map的底層結(jié)構(gòu)
1、set/map的源碼

扒一扒STL庫中set和map的底層結(jié)構(gòu),不難發(fā)現(xiàn),set和map的底層用的都是紅黑樹且均為key/value模型。
只不過set的key/value均為key值填充,而map的key/value使用key和pair<const Key,T>進行填充。因此,set和map中底層雖然都是紅黑樹,但這兩種數(shù)據(jù)結(jié)構(gòu)中的紅黑樹實例化類型并不相同。
那么使用同一顆紅黑樹的模板,如何實例化出適配set和/map的對象?
2、利用模板區(qū)分set/map
template <class T>//T類型代表value
struct RBTreeNode
{
RBTreeNode(const T& data)
:_parent(nullptr)
, _left(nullptr)
, _right(nullptr)
, _data(data)
, _col(RED)
{}
RBTreeNode<T>* _parent;
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
T _data;
Color _col;
};map和set的區(qū)別在于value的不同,紅黑樹模板參數(shù)T,代表value用以區(qū)分set和map。
3、利用仿函數(shù)控制比較大小
我們會發(fā)現(xiàn)紅黑樹的插入等接口會對key值進行比較大小,像set直接對key進行比較,這沒問題,但是map中的節(jié)點裝的是pair<K,V>,pair的比較規(guī)則是first比完之后可能會再去比較second(而我們僅僅想要比較first,該比較規(guī)則不適用)。
通過源碼啟發(fā),我們可以對紅黑樹新增一個模板參數(shù):仿函數(shù)KeyOfT,在set和map類中完善該仿函數(shù)的比較對象,用于區(qū)分set和map的比較:
template <class K>
class set
{
//仿函數(shù)用于比較大小
struct SetKeyOfT
{
const K& operator()(const K& key)//傳入節(jié)點的值
{
return key;//返回key
}
};
private:
RBTree<K, K, SetKeyOfT> _t;
};
class map
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)//傳入節(jié)點的值
{
return kv.first;//返回kv.first
}
};
private:
RBTree<const K, pair<K,V>, MapKeyOfT> _t;
};
//利用模板確定傳入對象是set還是map
template <class K, class T,class KeyOfT>
class RBTree//紅黑樹
{};利用仿函數(shù),傳入節(jié)點的值,set將會返回key值,map將會的返回pair的first。這樣就解決了比較大小的規(guī)則問題。

二、set/map的迭代器(紅黑樹的迭代器)
因為紅黑樹的中序遍歷是有序的,可以根據(jù)中序遍歷作為迭代器++--的依據(jù)。
STL源碼采用下圖結(jié)構(gòu),多搞了一個頭結(jié)點。迭代器begin()可以指向header的左,迭代器end()指向header。

不過本文采用無頭結(jié)點的常規(guī)紅黑樹仿寫紅黑樹的迭代器。
1、紅黑樹的begin、end迭代器

2、紅黑樹迭代器的operator++
1、如果當前節(jié)點的右不為空,迭代器++返回右子樹的最左節(jié)點
2、如果當前節(jié)點的右為空,迭代器++返回祖先(當前節(jié)點是父親的左)(end()-1迭代器++返回nullptr即end())
template <class T>
struct __RBTreeIterator
{
typedef RBTreeNode<T> Node;
typedef __RBTreeIterator<T> Self;
Node* _node;
__RBTreeIterator(Node* node)
:_node(node)
{}
//1、右不為空,下一個節(jié)點是右樹的最小節(jié)點
//2、右為空,去找右是父親左的最近祖先
Self& operator++()//找中序的下一個節(jié)點,即根的右樹的最左節(jié)點,返回值是一個迭代器的對象
{
if (_node->_right != nullptr)
{
Node* min = _node->_right;
while (min->_left != nullptr)
{
min = min->_left;
}
_node = min;
}
else
{
Node* cur = _node;
Node* parent = cur->_parent;
while (parent != nullptr && cur == parent->_right)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
};3、紅黑樹迭代器的operator--
1、如果當前節(jié)點的左不為空,迭代器--返回左子樹的最右節(jié)點
2、如果當前節(jié)點的左為空,迭代器--返回祖先(當前節(jié)點是父親的右)
template <class T>
struct __RBTreeIterator
{
typedef RBTreeNode<T> Node;
typedef __RBTreeIterator<T> Self;
Node* _node;
__RBTreeIterator(Node* node)
:_node(node)
{}
Self& operator--()
{
if (_node->_left!=nullptr)
{
Node* max = _node;
while (max->_right)
{
max = max->_right;
}
_node = max;
}
else
{
Node* cur = _node;
Node* parent = cur->_parent;
while (parent != nullptr && cur == parent->_left)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
};三、set的const迭代器
對于set和map,它們的key都是不能改的。set的value不能修改,map的value可以修改。
因為set的value是不能改的,所以它的底層將普通迭代器和const迭代器全部封裝成const迭代器來“解決”:
//自己實現(xiàn)的,不代表STL typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator; typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;
封裝之后又會出現(xiàn)新問題,set使用迭代器其實都是在使用const迭代器,但是自己實現(xiàn)的紅黑樹的迭代器接口返回普通類型的迭代器,在Set.h中對this加上const“解決”:
iterator begin()const
{
return _t.begin();
}
iterator end()const
{
return _t.end();
}
這時使用迭代器調(diào)用上方函數(shù)會發(fā)現(xiàn)紅黑樹返回了普通迭代器類型的迭代器,類型不匹配。在紅黑樹中補齊const版本的迭代器函數(shù)解決:
const_iterator begin()const//找紅黑樹最左節(jié)點
{
Node* left = _root;
while (left != nullptr && left->_left != nullptr)
{
left = left->_left;
}
return const_iterator(left);
}
const_iterator end()const
{
return const_iterator(nullptr);
}
四、map的const迭代器
map的value是可以改的,所以要分別設計普通迭代器和const迭代器。
typedef typename RBTree<const K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename RBTree<const K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
const_iterator begin()const
{
return _t.begin();
}
const_iterator end()const
{
return _t.end();
}五、迭代器類的拷貝構(gòu)造
STL庫中的普通迭代器都可以轉(zhuǎn)換為const迭代器,這是迭代器類的拷貝構(gòu)造所支持的。
這個拷貝構(gòu)造有點特殊:
//紅黑樹的迭代器
template <class T,class Ref,class Ptr>//key/value、T&、T*
struct __RBTreeIterator
{
typedef RBTreeNode<T> Node;
typedef __RBTreeIterator<T, Ref, Ptr> Self;
typedef __RBTreeIterator<T, T&, T*> iterator;
Node* _node;
__RBTreeIterator(Node* node)
:_node(node)
{}
__RBTreeIterator(const iterator& it)//const iterator本質(zhì)還是普通迭代器
:_node(it._node)
{}
};1、當這個模板的的Ref和PTR被實例化為T&和T*時,__RBTreeIterator(const iterator& it)就是一個拷貝構(gòu)造(沒啥意義)
2、當這個模板的的Ref和PTR被實例化為const T&和const T*時,__RBTreeIterator(const iterator& it)就是一個構(gòu)造函數(shù),支持用普通迭代器去構(gòu)造const迭代器。此時const迭代器的拷貝構(gòu)造函數(shù)則由編譯器自動生成,剛好滿足迭代器值拷貝的特點。
六、整體代碼
1、RBTree.h
#pragma once
#include <iostream>
#include <map>
#include <set>
#include <string>
using namespace std;
enum Color
{
RED,
BLACK,
};
template <class T>//T類型代表value
struct RBTreeNode
{
RBTreeNode(const T& data)
:_parent(nullptr)
, _left(nullptr)
, _right(nullptr)
, _data(data)
, _col(RED)
{}
RBTreeNode<T>* _parent;
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
T _data;
Color _col;
};
//紅黑樹的迭代器
// key/value T& T*
template <class T,class Ref,class Ptr>
struct __RBTreeIterator
{
typedef RBTreeNode<T> Node;
typedef __RBTreeIterator<T, Ref, Ptr> Self;
typedef __RBTreeIterator<T, T&, T*> iterator;
Node* _node;
__RBTreeIterator(Node* node)
:_node(node)
{}
__RBTreeIterator(const iterator& it)
:_node(it._node)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()//返回類型的地址
{
return &_node->_data;
}
//1、右不為空,下一個節(jié)點是右樹的最小節(jié)點
//2、右為空,去找右是父親左的最近祖先
Self& operator++()//找中序的下一個節(jié)點,即根的右樹的最左節(jié)點,返回值是一個迭代器的對象
{
if (_node->_right != nullptr)
{
Node* min = _node->_right;
while (min->_left != nullptr)
{
min = min->_left;
}
_node = min;
}
else
{
Node* cur = _node;
Node* parent = cur->_parent;
while (parent != nullptr && cur == parent->_right)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
Self& operator--()
{
if (_node->_left!=nullptr)
{
Node* max = _node;
while (max->_right)
{
max = max->_right;
}
_node = max;
}
else
{
Node* cur = _node;
Node* parent = cur->_parent;
while (parent != nullptr && cur == parent->_left)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
bool operator!=(const Self& s)const
{
return _node != s._node;
}
bool operator==(const Self& s)const
{
return _node == s._node;
}
};
//pair的比較是如果first小還要比second,我們只要比first,所以加了仿函數(shù)KeyOfT,用以取出first進行比較
//set->RBTree<K, K, SetKeyOfT>
//map->RBTree<const K, pair<K,V>, MapKeyOfT>
template <class K, class T,class KeyOfT>
class RBTree
{
public:
typedef __RBTreeIterator<T,T&,T*> iterator;
typedef __RBTreeIterator<T, const T&, const T*> const_iterator;
iterator begin()//找紅黑樹最左節(jié)點
{
Node* left = _root;
while (left!=nullptr&&left->_left!=nullptr)
{
left = left->_left;
}
return iterator(left);
}
iterator end()
{
return iterator(nullptr);
}
const_iterator begin()const//找紅黑樹最左節(jié)點
{
Node* left = _root;
while (left != nullptr && left->_left != nullptr)
{
left = left->_left;
}
return const_iterator(left);
}
const_iterator end()const
{
return const_iterator(nullptr);
}
typedef RBTreeNode<T> Node;
pair<iterator,bool> Insert(const T& data)//對于map來說data是pair
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;//根節(jié)點給黑色
return make_pair(iterator(_root), true);//返回插入的節(jié)點
}
KeyOfT kot;//搞一個仿函數(shù)對象
//_root不為空
Node* parent = nullptr;
Node* cur = _root;
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(iterator(cur),false);//插入失敗,說明找到了,返回被查找節(jié)點的迭代器
}
//開始插入
cur = new Node(data);
Node* newNode = cur;//記錄cur的地址,make_pair返回插入節(jié)點的地址
cur->_col = RED;//新插入節(jié)點給紅色,可能違反規(guī)則。如果給黑色會導致其他路徑的黑色節(jié)點數(shù)量不相同,必定違反規(guī)則。
if (kot(parent->_data) < kot(data))
{
parent->_right = cur;
cur->_parent = parent;//維護cur的父指針
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
//調(diào)整
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;//找到祖父
if (grandfather->_left == parent)//如果父親是祖父的左孩子
{
Node* uncle = grandfather->_right;//找到叔叔
//情況一:叔叔存在且為紅
if (uncle != nullptr && uncle->_col == RED)
{
//變色
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else//情況二或情況三
{
if (cur == parent->_left)//情況二,直線
{
RotateRight(grandfather);//右單旋
parent->_col = BLACK;
grandfather->_col = RED;
}
else//情況三,折線
{
RotateLeft(parent);//左單旋
RotateRight(grandfather);//右單旋
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else//如果父親是祖父的右孩子
{
Node* uncle = grandfather->_left;
if (uncle != nullptr && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else
{
if (cur == parent->_right)//情況二,直線
{
//g
// p
// c
RotateLeft(grandfather);//左單旋
parent->_col = BLACK;
grandfather->_col = RED;
}
else//情況三,折線
{
//g
// p
//c
RotateRight(parent);//右單旋
RotateLeft(grandfather);//左單旋
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return make_pair(iterator(newNode), true);//返回插入的節(jié)點
}
void Inorder()
{
_Inorder(_root);
}
bool IsBalance()
{
return _IsBalance();
}
private:
void _Inorder(Node* root)
{
if (root == nullptr)
return;
_Inorder(root->_left);
cout << kot(root->_data) << ":" << root->_data.second << endl;
_Inorder(root->_right);
}
bool Check(Node* root, int blackNum, const int ref)//檢查有沒有連續(xù)紅節(jié)點
{
if (root == nullptr)
{
if (blackNum != ref)
{
cout << "路徑上黑節(jié)點數(shù)量不一致" << endl;
return false;
}
return true;
}
if (root->_col == BLACK)
{
++blackNum;
}
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "違反規(guī)則,父子均為紅" << endl;
return false;
}
return Check(root->_left, blackNum, ref) && Check(root->_right, blackNum, ref);
}
bool _IsBalance()
{
if (_root == nullptr)
return true;
if (_root->_col != BLACK)
{
return false;
}
//數(shù)一下一條路徑黑色節(jié)點數(shù)量
int ref = 0;//統(tǒng)計一條路上黑色節(jié)點的數(shù)量
Node* left = _root;
while (left != nullptr)
{
if (left->_col == BLACK)
{
++ref;
}
left = left->_left;
}
return Check(_root, 0, ref);
}
void RotateLeft(Node* parent)//左單旋
{
Node* grandfather = parent->_parent;
Node* cur = parent->_right;
if (parent == _root)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (grandfather->_left == parent)//需要判定parent原來屬于grandfather的哪一邊
grandfather->_left = cur;
else
grandfather->_right = cur;
cur->_parent = grandfather;
}
parent->_right = cur->_left;
if (cur->_left != nullptr)
cur->_left->_parent = parent;
cur->_left = parent;
parent->_parent = cur;
}
void RotateRight(Node* parent)//右單旋
{
Node* grandfather = parent->_parent;
Node* cur = parent->_left;
if (parent == _root)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (grandfather->_left == parent)
{
grandfather->_left = cur;
cur->_parent = grandfather;
}
else
{
grandfather->_right = cur;
cur->_parent = grandfather;
}
}
parent->_parent = cur;
parent->_left = cur->_right;
if (cur->_right != nullptr)
cur->_right->_parent = parent;
cur->_right = parent;
}
private:
Node* _root = nullptr;
};迭代器的begin(),end()接口放在紅黑樹這個類中,而operator++--放在迭代器這個類中,自己寫一下循環(huán)遍歷紅黑樹的代碼就知道為什么這樣設計了。
2、Set.h
#pragma once
#include "RBTree.h"
namespace jly
{
template <class K>
class set
{
struct SetKeyOfT
{
const K& operator()(const K& key)//傳入value
{
return key;
}
};
public:
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;
pair<iterator, bool> insert(const K& key)
{
pair<typename RBTree<K, K, SetKeyOfT>::iterator,bool> ret= _t.Insert(key);
return pair<iterator, bool>(ret.first, ret.second);
}
iterator begin()const
{
return _t.begin();
}
iterator end()const
{
return _t.end();
}
private:
RBTree<K, K, SetKeyOfT> _t;
};
void test2()
{
//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
//int a[] = { 9,8,7,6,5,4,3,2,1};
set<int> s;
for (auto e : a)
{
s.insert(e);
}
set<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
}
}3、map.h
#pragma once
#include "RBTree.h"
namespace jly
{
template <class K,class V>
class map
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)//傳入value
{
return kv.first;
}
};
public:
//typename是C++中用于指定一個類的類型的關(guān)鍵字。
//通常用于表示某個類型是一個類類型,而不是其他類型,如int等。
//這里不加typedef編譯器無法區(qū)分iterator是一個類型還是一個靜態(tài)變量。因為他倆都可以這么寫。。
//所以從類模板取出內(nèi)嵌類型就需要加typedef
typedef typename RBTree<const K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename RBTree<const K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;
pair<iterator,bool> insert(const pair<K, V>& kv)
{
return _t.Insert(kv);
}
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
const_iterator begin()const
{
return _t.begin();
}
const_iterator end()const
{
return _t.end();
}
V& operator[](const K& key)//傳入key值
{
pair<iterator,bool> ret= _t.Insert(key,V());
return ret.first->second;//找到ret(make_pair<iterator,bool>)的first,解引用找到節(jié)點value
}
private:
RBTree<const K, pair<const K,V>, MapKeyOfT> _t;
};
void test1()
{
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
//int a[] = { 9,8,7,6,5,4,3,2,1};
map<int,int> m;
for (auto e : a)
{
m.insert(make_pair(e,e));
}
map<int, int>::iterator it = m.begin();
while (it != m.end())
{
cout << (* it).first << " ";
++it;
}
cout << endl;
for (auto& e : m)
{
cout << e.first<<" ";
}
}
}到此這篇關(guān)于C++紅黑樹應用之手搓set和map的文章就介紹到這了,更多相關(guān)C++紅黑樹內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

