關聯式容器
STL中關聯式容器有以下幾種map/set/multimap/multiset/unordered_map/unordered_set/unordered_multimap/unordered_multiset
,所謂關聯式容器即他們內部存儲的是具有關聯性的key-value
形式的鍵值對。本文先從他們的基礎使用開始講起,逐漸深入到底層實現原理,並且最後從二叉搜索樹到紅黑樹再到哈希桶逐步手動實現各個版本的關聯式容器,我已經預感到了這將是一篇非常之長的博客,但是爲了知識的連貫性我並不打算將他們分開。
pair
關聯式容器中諸如map
都可以通過一個key
來查找value
,十分類似於查字典,這就得益於關聯式容器中所存儲的數據結構都是由一個一個鍵值對組成,因此我們對其的操作就更像是在查字典,要了解關聯式容器我們就得先了解其內部存儲的鍵值對的結構pair
。
我們首先看一下SGI版本中對鍵值對pair
的定義。
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair()
:first(T1())
,second(T2())
{}
pair(const T1 &a, const T2 &b)
:first(a)
,second(b)
{}
};
pair
的結構非常簡單,簡單來說其中就是存儲了一個鍵值對,first
對應key
值,second
對應value
,同時提供了兩個構造函數,僅此而已,不過不要忘記這個結構,在關聯式容器的使用中,pair
的使用也是必不可少。通常我們可以使用make_pair()
函數快速幫助我們構造一個pair
。
map
map
方便我們存儲映射,並且會自動幫助我們根據key
排序。
模板參數
template <class Key, // key的類型
class T, // value的類型
class Compare = less<Key>, // 比較器
class Alloc = allocator<pair<const Key, T>> // 空間配置器
>
class map;
Key
和T
很好理解,那麼這裏的比較器和空間配置器是什麼呢?Compare
比較器是用來使用在容器內部輔助完成數據排序的,默認按照小於來比較,一般情況下不需要特殊比較方式的話不需要傳入,我們也可以利用仿函數自定義比較方式。對於比較器我們之前在priority_queue
中也有使用過。關於空間配置器我們會發現很多容器模板中都存在這個參數,容器通過使用空間配置器申請底層空間,我們會在之後的章節進行整理,一般情況下也不需要我們手動傳入。
構造函數
explicit map(const key_compare &comp = key_compare(),
const allocator_type &alloc = allocator_type());//創建一個空map
template <class InputIterator>
map(InputIterator first, InputIterator last,
const key_compare &comp = key_compare(),
const allocator_type &alloc = allocator_type());//用一個迭代器區間中的數據構造map
map (const map& x);//拷貝構造
迭代器相關
iterator begin(); //返回第一個元素的位置
iterator end(); //返回最後一個元素的下一個位置
const_iterator begin() const; //返回第一個元素的const迭代器
const_iterator end() const; //返回最後一個元素下一個位置的const迭代器
reverse_iterator rbegin(); //返回第一個元素位置的反向迭代器即rend
reverse_iterator rend(); //返回最後一個元素下一個位置的反向迭代器即 rbegin
const_reverse_iterator rbegin() const; //返回第一個元素位置的const反向迭代器即rend
const_reverse_iterator rend() const; //返回最後一元素下一個位置的反向迭代器即rbegin
元素修改
pair<iterator, bool> insert(const value_type &x); //在map中插入鍵值對x,注意x是一個鍵值對,返回 值也是鍵值對:iterator代表新插入元素的位置, bool代表釋放插入成功
iterator insert(iterator position, const value_type &x); //在position位置插入值爲x的鍵值對,返回該鍵值對 在map中的位置,注意:元素不一定必須插在 position位置,該位置只是一個參考
template <class InputIterator>
void insert(InputIterator first, InputIterator last); //在map中插入[first, last)區間中的元素
void erase(iterator position); //刪除position位置上的元素
size_type erase(const key_type &x); //刪除鍵值爲x的元素
void erase(iterator first, iterator last); //刪除[first, last)區間中的元素
void swap(map<Key, T, Compare, Allocator> &mp); //交換兩個map中的元素
void clear(); //將map中的元素清空
iterator find(const key_type &x); //在map中插入key爲x的元素,找到返回該元素的位 置的迭代器,否則返回end
const_iterator find(const key_type &x) const; //在map中插入key爲x的元素,找到返回該元素的位 置的const迭代器,否則返回cend
size_type count(const key_type &x) const; //返回key爲x的鍵值在map中的個數,注意map中 key是唯一的,因此該函數的返回值要麼爲0,要麼 爲1,因此也可以用該函數來檢測一個key是否在 map中
要注意map
中一個key
值僅存在一份,如果我們重複插入key
相同的鍵值對,則會插入失敗,並且insert
的第一個重載要求插入的元素是一個pair
並且返回的也是一個pair
,返回的pair的類型爲std::pair<std::map::iterator, bool>
,第一個迭代器參數標識了結點插入的位置,如果已經存在該節點,則返回map
中該節點的迭代器,如果不存在則插入新結點後返回新插入的結點的迭代器,bool
標識了插入是否成功,也就是說無論如何當我們插入一個節點時返回的pair
中都會給我們返回一個key
對應的節點的迭代器。
容量及元素訪問
bool empty() const; //檢測map中的元素是否爲空,是返回true,否則 返回false
size_type size() const; //返回map中有效元素的個數
mapped_type &operator[](const key_type &k); //返回去key對應的value
這裏要注意map
也重載了operator[]
,那麼如果我們去訪問一個不存在的值會怎樣呢?
#include <iostream>
#include <string>
#include <map>
int main()
{
std::map<std::string, int> map;//空map
//訪問不存在的key值
map["1"] = 1;
map["2"] = 2;
for(auto e : map)
{
std::cout << e.first << " " << e.second << std::endl;
}
}
1 1
2 2
我們會發現map
自動在找不到這個節點的時候會自動插入新的節點,並且返回其value
的應用,這裏貼出其底層實現。
(*((this->insert(make_pair(k,mapped_type()))).first)).second
對只有這麼一句話,我們應該就已經明白了爲什麼它可以幫助我們插入新節點了,因爲它在底層調用了insert
方法,insert
方法之前已經說過他的返回值是一個特定的pair
,其中的key
包含了對應結點的迭代器,因此我們也就可以方便的取到對應結點的value
並將其返回出來。
總結
1、map
中的元素是鍵值對的形式存在的。
2、map
中key
值是唯一的,並且不能修改key
只。
3、map
底層是一個紅黑樹,因此查找效率較高OlogN
。
4、map
會自動根據key
進行排序,當然這也是因爲底層是一個紅黑樹。
5、默認按照小於的方式進行排序,排序結果升序。
6、支持[]
操作,在底層調用insert
方法。
multimap
map
中的元素key
值不允許相同,由此便延伸出了multimap
,multimap
中允許元素key
值相同,其他與map
並沒有太大區別。因此接口不再介紹,不過唯一要注意的是insert
相關的接口永遠都會插入新的元素,並不會存在因爲key
值存在而插入失敗的情況。
set
set
在上層使用時我們很容易將其看成是一個會自動排序和去重的vector
但是實際上在底層實現大有不同,這也是它被稱爲關聯式容器的原因。其底層依然存儲着一個pair
不過其中key
值和value
值都是相同的即<value, value>
,並且和map
一樣它的key
一樣是不允許修改的,這也就導致了set
中的元素是不可修改的,只允許插入和刪除。set
插入元素時也是隻需提供value
即可。
模板參數
template <class T, // value類型
class Compare = less<T>, // 比較器
class Alloc = allocator<T> // 空間配置器
>
class set;
構造函數
set(const Compare &comp = Compare(), const Allocator & = Allocator()); //構造空的set
set(InputIterator first, InputIterator last, const Compare &comp = Compare(), const Allocator & = Allocator()); //用[first, last)區間 中的元素構造set
set(const set<Key, Compare, Allocator> &x);
迭代器相關
iterator begin(); //返回set中起始位置元素的迭代器
iterator end(); //返回set中最後一個元素後面的迭代器
const_iterator cbegin() const; //返回set中起始位置元素的const迭代器
const_iterator cend() const; //返回set中最後一個元素後面的const迭代器
reverse_iterator rbegin(); //返回set第一個元素的反向迭代器,即end
reverse_iterator rend(); //返回set最後一個元素下一個位置的反向迭代器,即 rbegin
const_reverse_iterator crbegin() const; //返回set第一個元素的反向const迭代器,即cend
const_reverse_iterator crend() const; //返回set最後一個元素下一個位置的反向const迭代器, 即crbegin
容量和修改
bool empty() const; //檢測set是否爲空,空返回true,否則返回true
size_type size() const; //返回set中有效元素的個數
pair<iterator, bool> insert(const value_type &x); //在set中插入元素x,實際插入的是<x, x>構成的鍵值對, 如果插入成功,返回<該元素在set中的位置,true>,如果 插入失敗,說明x在set中已經存在,返回<x在set中的位 置,false>
iterator insert(iterator position, const value_type &x); //在set的position位置插入x,實際插入的是<x, x>構成的 鍵值對,注意:position位置只是參考,x最終不一定在該 位置
template <class InputIterator>
void insert(InputIterator first, InputIterator last); //在set中插入[first, last)區間中的元素
void erase(iterator position); //刪除set中position位置上的元素
size_type erase(const key_type &x); //刪除set中值爲x的元素,返回刪除的元素的個數
void erase(iterator first, iterator last); //刪除set中[first, last)區間中的元素
void swap(set<Key, Compare, Allocator> &st); //交換set中的元素
void clear(); //將set中的元素清空
iterator find(const key_type &x) const; //返回set中值爲x的元素的位置
size_type count(const key_type &x) const; //返回set中值爲x的元素的個數
set
的接口與map
的接口幾乎沒有差別,不同的是set
的迭代器取值直接*it
即可,並沒有map
中first
和second
的取值方式了。取消了[]
操作,因爲set
的key
和value
一致並且不允許修改於是這個接口也不再有存在的必要。
總結
1、set
和map
幾乎一致,不同是value
和key
值相同,因此不能修改。
2、底層依然是一個紅黑樹進行實現,因此可以實現自動排序,查找速度也較快能夠達到OlogN
>
3、set
插入元素只需提供value
即可。
4、set
中的元素不可重複,因此可以達到驅蟲的效果。
5、set
中的元素默認按照小於的方式排序,結果升序。
6、set
中的依然存儲鍵值對pair
,知識key
值與value
值相同。
multiset
multiset
與set
的區別與multimap
與map
的區別相同,set
中不允許存儲相同的元素而multiset
中允許存儲,僅此而已。
底層實現
以上介紹的幾種關聯式容器底層都是一顆紅黑樹,什麼是紅黑樹呢,這就得從BS樹說起,於是接下來我們着重底層實現,從BS Tree
開始逐步升級直到最終實現紅黑樹,並用其封裝成map/set
。
BS Tree
什麼是BS Tree
BS Tree
即二叉搜索樹,在一個二叉樹的基礎上使其滿足某些要求,讓其更加便於搜索。那麼一棵二叉搜索樹要滿足哪些要求呢?
1、若它的左子樹不爲空,則左子樹上所有節點的值都小於根節點的值 。
2、若它的右子樹不爲空,則右子樹上所有節點的值都大於根節點的值。
3、它的左右子樹也分別爲二叉搜索樹。
中序遍歷二叉搜索樹結果即爲有序序列。如下即爲一顆二叉搜搜樹。
二叉搜索樹的插入思路就是用一個cur
結點和parent
結點找到目標插入位置和其夫結點插入即可,位置的找法就是key
值比cur
的key
大則往左子樹走,小則往右子樹走,相等則失敗,查找也是相同的思路,而刪除略微麻煩一些,要分爲三種情況進行刪除,有兩個孩子的結點較難刪除,需要找到合適的值進行替換再刪除。
實現
#pragma once
#include <iostream>
#include <utility>
template<class K, class V>
//結點
struct BSTreeNode
{
BSTreeNode(const std::pair<K, V>& kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_kv(kv)
{
}
std::pair<K, V> _kv;
BSTreeNode<K, V>* _left;
BSTreeNode<K, V>* _right;
//BSTree可以暫時先不使用_parent
BSTreeNode<K, V>* _parent;
};
//二叉搜索樹
template<class K, class V>
class BSTree
{
typedef BSTreeNode<K, V> Node;
public:
//這裏先暫時忽略拷貝構造,析構,賦值的問題
BSTree()
:_root(nullptr)
{
}
//插入結點
bool Insert(const std::pair<K, V>& kv)
{
//插入第一個結點
if(_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
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);
if(parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
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 Remove(const K& key)
{
//1、葉子結點,直接刪除,父結點對應指針指向空
//2、有一個孩子,如果左爲空,父結點對應指針指向右,如果右爲空,父結點對應指針指向左
//3、找右子樹最左結點或左子樹最右結點替代他,然後刪除替代結點
Node* cur = _root;
Node* parent = nullptr;
while(cur)
{
if(cur->_kv.first < key)
{
parent = cur;
cur = cur->_right;
}
else if(cur->_kv.first > key)
{
parent = cur;
cur = cur->_left;
}
//找到相等可以刪除此結點了
else
{
//只有0/1個孩子
//對於只有一個孩子或者沒有孩子的結點來說要刪除的結點就是cur
Node* del = cur;
//要刪除的結點左孩子爲空
if(cur->_left == nullptr)
{
//如果要刪除的是根結點,父結點爲空
if(parent == nullptr)
{
//直接改變根
_root = cur->_right;
return true;
}
//判斷要刪除的結點是其父結點的左孩子還是右孩子
if(cur == parent->_left)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
//要刪除的結點右孩子爲空
else if(cur->_right == nullptr)
{
//如果要刪除的是根結點,父結點爲空
if(parent == nullptr)
{
_root = cur->_left;
return true;
}
//判斷要刪除的結點是其父結點的左孩子還是右孩子
if(cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
//有兩個孩子,找右樹的最左結點
else
{
//找到右樹的最左結點和其父結點
Node* rpParent = cur;
Node* replace = cur->_right;
while(replace->_left)
{
rpParent = replace;
replace = replace->_left;
}
//此時要釋放的結點是替代結點
del = replace;
//找到後開始替換
cur->_kv = replace->_kv;
//替換結點不一定一定是其父結點的左結點,因此做一次判斷
if(rpParent->_left == replace)
{
rpParent->_left = replace->_right;
}
else
{
rpParent->_right = replace->_right;
}
}
//釋放結點
delete del;
std::cout << "del success" << std::endl;
return true;
}
}
return false;
}
//中序遍歷
void InOrder()
{
_InOrder(_root);
std::cout << std::endl;
}
private:
void _InOrder(Node* root)
{
if(root == nullptr)
{
return;
}
_InOrder(root->_left);
std::cout << root->_kv.first << " ";
_InOrder(root->_right);
}
private:
Node* _root;
};
二叉搜索樹的搜索速度在理想狀況下可以達到OlogN
,此時二叉樹爲一棵完全二叉樹,如果是一顆滿二叉樹更好,因爲每次搜索都可以淘汰一半的數據。但是往往並不是理想的,如果插入的樹本身就是有序的,則使二叉樹可能會變成一條鏈,二叉樹會完全失去平衡,此時的搜索便是ON
的。爲了讓二叉樹儘可能保持平衡,使其儘可能成爲一顆完全二叉樹,所有狀況下都能達到理想狀態,優化時間複雜度,於是我們需要在改變樹的狀態的時候維持它自身的平衡,便出現了AVLTree
。
AVLTree
什麼是AVLTree
AVLTree
也叫高度平衡二叉搜索樹,其是對BSTree
的一個改進,爲了保證其在高度上的平衡性,使其可以儘可能的達到理想狀態中的完全二叉樹的形式,AVLTree
引入了平衡因子的概念,平衡因子即左右子樹的高度差。AVLTree
要求樹上每一個結點的平衡因子都不能大於1,其它要求與BSTree
一致。
這樣一來AVLTree
上任何狀態下的操作都將與理想狀態下BSTree
的操作有着同樣的時間複雜度OlogN
,可以說AVLTree
的存在就是爲了任何時候都可以將BSTree
限定在理想狀態。
AVLTree平衡因子的更新
我們這裏都先假設平衡因子bf = 右子樹高度 - 左子樹高度
。AVLTree引入平衡因子概念後我們在插入一個節點後必須要更新整棵樹的平衡因子,那麼我們該如何更新才能讓整棵樹的平衡因子能重新回到正確的狀態下呢?
以下是一棵AVLTree
,我用藍色標識了每個結點的bf
。
接下來我們假設插入7.5,或者任意一個比7大比8小的值,我們可以發現新插入的結點bf == 0
,因爲其沒有左右孩子,所以並不受影響。新結點的插入只會影響它的祖先們,因此我們必須依次去更新他們的祖先。這裏我們首當其衝更新了插入結點的父親。更新其父親的bf
規律很好總結,如果我們插入在父親的左則bf = bf - 1
,插入在父親的右則bf = bf + 1
。此時其父親bf == 0
。
在一次更新後我們是否還應該繼續更新?當然,我們必須迭代上去繼續更新,讓cur = parent, parent = parent->parent
。但是我們再次按照剛纔的規律更新會發現7
這個元素結點的平衡因子會被更新爲2,但是這樣的更新明顯是錯誤的,因爲雖然插入的新節點但7
的右子樹高度並沒有增高,我們並不應該更新它,那麼我們的更新該到何時爲止呢?
經過實驗我們可以總結出以下幾種parent
更新bf
後的情況,由此來判斷是否需要進一步更新或者執行其他操作:
1、|bf| == 0
則說明此時當前父結點所在的子樹高度並沒有增加,我們自然也不需要再迭代上去繼續更新其他祖先。
2、parent == nullptr
,此時父結點已經指向空說明我們已經更新完了插入結點的所有祖先,沒有祖先可以繼續更新,這是更新的最壞情況。
3、|bf| == 1
,此時當前父結點所在子樹的高度增加了,但是父結點bf
依然符合AVLTree
的要求,我們需要繼續向上迭代更新其祖先。
4、|bf| == 2
,此時當前父結點所在子樹的高度增加了,並且父結點bf
不再符合AVLTree
的要求,我們就此停止不再更新,並且利用旋轉來解決眼下的問題。
AVLTree的旋轉
我們在更新平衡因子的過程中難免會遇到bf == 2
的情況,此時這棵樹不再滿足AVLTree
的要求,當然我們也有方法可以使其繼續成爲一棵AVLTree
,就是旋轉。
旋轉分爲一共四種情況,以下一一列舉。
左單旋
使用場景:
由此我們可以歸納出這種情況的抽象圖來。
右單旋
右單旋與左單旋類似:
抽象圖。
先右旋再左旋
使用場景:
抽象圖:
但是這裏要注意,兩次旋轉後結點的平衡因子更新並不像單旋呢麼簡單,需要根據實際情況進行判斷,具體請看代碼中的實現。
先左旋再右旋
與先右旋再左旋類似,直接給出抽象圖:
但是這裏要注意,兩次旋轉後結點的平衡因子更新並不像單旋呢麼簡單,需要根據實際情況進行判斷,具體請看代碼中的實現。
旋轉場景
四種旋轉搞明白後,AVLTree
的調整就只剩最後一件事要考慮,那就是我們什麼情況下應該使用哪種旋轉來調整呢?
回到我們用cur
和parent
來更新插入結點祖先的平衡因子。以下我們分爲四種情況:
1、parent->_bf == 2 && cur->_bf == 1
說明parent
右子樹高,並且cur
的右子樹也高,說明此時結點插入在了parent
右子樹的外側,我們左單旋即可。
2、parent->bf == 2 && cur->bf == -1
說明parent
右子樹高,但是cur
左子樹高,說明此時結點插入在了parent
右子樹的內側,我們要先右旋再左旋。
3、parent->bf == -2 && cur->bf == -1
說明此時parent
左子樹高,cur
也是左子樹高,則結點插入在了parent
左子樹的外側,右單旋即可。
4、parent->bf == -2 && cur->bf == 1
說明此時parent
左子樹高,但是cur
右子樹高,說明此時結點插入在了parent
左子樹的內側,我們要先左旋再右旋。
實現
這裏僅僅實現了AVLTree
的插入,刪除與插入類似,需要用到BSTree
的刪除思想並且和AVLTree
的插入中的調整平衡的思想相結合來實現。
直接上代碼。
#pragma once
#include <iostream>
#include <utility>
#include <assert.h>
//結點
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode(const std::pair<K, V>& kv)
:_kv(kv)
,_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0)
{}
std::pair<K, V> _kv;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
int _bf; //平衡因子 = 右子樹高度 - 左子樹高度
};
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
bool Insert(const std::pair<K, V>& kv)
{
//插入結點,思路與BSTree一致,額外需要加入更新平衡因子
//插入的爲第一個結點
if(_root == nullptr)
{
_root = new Node(kv);
_root->_bf = 0;
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while(cur)
{
if(cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else if(cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(kv);
if(parent->_kv.first < kv.first)
{
cur->_parent = parent;
parent->_right = cur;
}
else
{
cur->_parent = parent;
parent->_left = cur;
}
//調整平衡
//1、更新平衡因子
//新增在左,父親bf - 1,新增在右,父親bf + 1
//如果父親的更新後|bf|:
//|bf| == 0 || 父結點爲空時停止更新
//因爲bf更新爲0則說明當前父親所在子樹此時的高度並未發生變化,父結點爲空說明此時更新完了整棵樹
//|bf| == 2也停止更新,及時調整,旋轉處理
//|bf| == 1則繼續向上更新
while(parent)
{
if(cur == parent->_right)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
//|bf| == 0
if(parent->_bf == 0)//更新完成
{
break;
}
else if(abs(parent->_bf) == 1)//繼續向上更新
{
cur = parent;
parent = parent->_parent;
}
else if(abs(parent->_bf) == 2)//不滿足AVLTree要求及時旋轉調整
{
//2、旋轉
if(parent->_bf == 2)
{
if(cur->_bf == 1)
{
RotateL(parent);
}
else if(cur->_bf == -1)
{
RotateRL(parent);
}
}
else if(parent->_bf == -2)
{
if(cur->_bf == -1)
{
RotateR(parent);
}
else if(cur->_bf == 1)
{
RotateLR(parent);
}
}
break;//調整完一定要記着break
}
else//在三種情況外,說明出現問題
{
assert(false);
}
}
return true;
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = parent->_right->_left;
parent->_right = subRL;
if(subRL)
{
subRL->_parent = parent;
}
subR->_left = parent;
Node* ppNode = parent->_parent;
parent->_parent = subR;
//根
if(ppNode == nullptr)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if(ppNode->_right == parent)
{
ppNode->_right = subR;
}
else
{
ppNode->_left = subR;
}
subR->_parent = ppNode;
}
//更新平衡因子
subR->_bf = parent->_bf = 0;
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = parent->_left->_right;
parent->_left = subLR;
if(subLR)//subLR可能會爲空,當h == 0時subLR爲空
{
subLR->_parent = parent;
}
subL->_right = parent;//subL不可能爲空
//記錄下parent原來的父結點,爲了方便parent移動可以找到這棵子樹的父結點
Node* ppNode = parent->_parent;
parent->_parent = subL;
//更新這棵子樹的新父結點subL與其父結點的連接
if(ppNode == nullptr)//如果子樹的父結點爲空則說明parent原本是整棵樹的根節點
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if(ppNode->_right == parent)
{
ppNode->_right = subL;
}
else
{
ppNode->_left = subL;
}
subL->_parent = ppNode;
}
parent->_bf = subL->_bf = 0;
}
//先左旋再右旋
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
//保存subRL的平衡因子,之後要根據這個判斷parent和subR的平衡因子分別更新爲多少
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
//注意這裏雙旋過後父結點的平衡因子不一定會爲0
if(bf == 0)
{
parent->_bf = subLR->_bf = subL->_bf = 0;
}
else if(bf == 1)
{
subL->_bf = -1;
parent->_bf = 0;
subLR->_bf = 0;
}
else if(bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
}
//先右旋再左旋
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
//保存subRL的平衡因子,之後要根據這個判斷parent和subR的平衡因子分別更新爲多少
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
//注意這裏雙旋過後父結點的平衡因子不一定會爲0
//這裏三個結點的平衡因子更新要根據新節點到底插在哪裏來決定
if(bf == 0)//此時說明subRL爲新增結點
{
parent->_bf = subRL->_bf = subR->_bf = 0;
}
else if(bf == 1)//此時說明新增節點插在c樹上
{
subR->_bf = 0;
parent->_bf = -1;
subRL->_bf = 0;
}
else if(bf == -1)//此時說明新增結點插在b樹上
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
}
//中序
void InOrder()
{
_InOrder(_root);
std::cout << std::endl;
}
//爲了判定它是一棵平衡樹我們寫一個求樹高度的函數
int _Height(Node* root)
{
if(root == nullptr)
{
return 0;
}
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
}
//判斷這是否是一顆平衡二叉樹
bool IsBalance()
{
return _IsBalance(_root);
}
bool _IsBalance(Node* root)
{
if(root == nullptr)
{
return true;
}
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
//也有可能會出現平衡因子更新錯誤的情況,這裏再做二次判斷
if(rightHeight - leftHeight != root->_bf)
{
std::cout << root->_kv.first << " is error" << std::endl;
}
return (abs(leftHeight - rightHeight) < 2) && _IsBalance(root->_left) && _IsBalance(root->_right);
}
private:
void _InOrder(Node* parent)
{
if(parent == nullptr)
{
return;
}
_InOrder(parent->_left);
std::cout << parent->_kv.first << " ";
_InOrder(parent->_right);
}
Node* _root = nullptr;
};
void TestAVLTree()
{
AVLTree<int, int> t;
int a[] = {16, 3, 7, 11, 9, 26, 18, 14, 15};
int b[] = {4, 2, 6, 1, 3, 5, 15, 7, 16, 14};
for(auto e : b)
{
t.Insert(std::make_pair(e, e));
}
t.InOrder();
//驗證是否平衡
std::cout << t.IsBalance() << std::endl;
}
在主函數中調用TestAVLTree()
1 2 3 4 5 6 7 14 15 16
1
因爲AVLTree
總能通過旋轉來調節自身的平衡,使自己變成一棵近完全二叉樹以追求搜索二叉樹的理想狀態,因此它在搜索上的時間複雜度確實得到了極大的改進,可以一直維持在OlogN
,但是這棵樹它雖然近乎完美但是依然達不到要求,首先的原因就是它的調整,可以說調整雖然加快了它的搜索過程,但是也正是調整使它變得更爲複雜。我們在根部插入新結點時,最壞情況下我們要從葉子調整到根,它的調整實在是太多了。其次,它的實現也因爲調整變得十分複雜繁瑣,雖然我們希望一棵BSTree
搜索起來儘可能追求理想狀態,但是這樣的調整未免付出的代價也有點過大了。於是便有了接下來的RBTree
的誕生。
RBTree
什麼是RBTRee
RBTree
即紅黑樹,它相比AVLTree
更爲簡單,並且它大大減少了要調整的次數,在追求理想狀態的過程中與調整次數達成了妥協,這也使得它成爲STL
中map/set/multimap/multiset
的底層數據結構。
一棵樹要是紅黑樹必須滿足以下幾條規則:
1、每個結點不是紅色就是黑色的。
2、根節點是黑色的。
3、如果一個結點時紅色的,則它兩個孩子結點是黑色的。
4、對於每個結點,從該結點到其所有後代葉結點的簡單路徑上,均包含相同數目的黑結點。
5、每個葉子結點都是黑色的(此處的葉子結點指空結點)。
RBTree的調整
我們在插入新節點的時候要考慮的就是新節點的顏色默認應該爲什麼顏色。我們首先分析如果新節點顏色如果爲黑色,那麼就意味着當前葉子的祖先路徑上黑色必然會多出來一個,爲了維持第4條規則,必然要對樹進行調整,這就意味着如果新插入結點默認爲黑色那麼我們必然要進行調整,因爲黑色結點對整棵樹整體的影響是比較大的。那麼如果新插入結點默認爲紅色呢?如果爲紅色只要它的父結點不爲紅色,那麼此時我們是不需要進行調整的,因此調整的機率從必然要調整降低到了有可能不會需要調整,因此我們默認新插入的結點顏色爲紅色是最優的選擇。
新插入結點默認爲紅色也有可能會遇到需要調整的情況,比如它的父結點也爲紅色那麼此時該如何調整呢?這裏的情況判斷相比AVLTree
更爲複雜一些。
爲了方便討論,這裏約定cur
爲當前節點,p
爲父節點,g
爲祖父節點,u
爲叔叔節點。
情況一
cur
爲紅,p
爲紅,g
爲黑,u
存在且爲紅。
這種情況下肯定是要調整了,這種情況下要做的操作也很簡單,變色即可。爲了讓紅色不連續我們將p
變爲黑色,爲了維持每條路徑上黑色結點數量一致,我們將u
也變爲黑色,而g
變爲紅色。如果g
調整後和它的父結點再次出現了連續紅色則再次根據情況進行調整。
情況二
cur
爲紅,p
爲紅,g
爲黑,u
不存在或爲黑。
這種情況下調整方式爲:如果p
爲g
的左孩子,cur
爲p的左孩子,則進行右單旋轉;相反, p
爲g
的右孩子,cur
爲p
的右孩子,則進行左單旋轉。最後p
變黑,g
變紅。這裏的旋轉方法和AVLTree
的相同。
情況三
cur
爲紅,p
爲紅,g
爲黑,u
不存在或u
爲黑
。
p
爲g
的左孩子,cur
爲p
的右孩子,則針對p
做左單旋轉;相反, p
爲g
的右孩子,cur
爲p
的左孩子,則針對p
做右單旋轉 則轉換成了情況2 。
這種情況與AVLTree
中的雙旋場景類似,cur
在了內側,因此我們必須旋轉兩次,好在這裏旋轉一次就可以變爲情況二我們可以很方便的處理這種問題。要注意的是,旋轉一次完畢後我們要交換cur
和parent
來讓當前結點的情況和情況二完全一致。
實現
直接上代碼,這裏暫且沒有實現刪除。
#pragma once
#include <iostream>
#include <utility>
enum Color
{
RED,
BLACK
};
template<class K, class V>
struct RBTNode
{
RBTNode(const std::pair<K, V>& data = std::pair<K, V>())
:_data(data)
,_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_color(RED)
{
}
std::pair<K, V> _data;
RBTNode<K, V>* _left;
RBTNode<K, V>* _right;
RBTNode<K, V>* _parent;
Color _color;
};
template<class K, class V>
class RBTree
{
typedef RBTNode<K, V> Node;
public:
RBTree(const std::pair<K, V>& data = std::pair<K, V>())
:_header(new Node(data))
{
_header->_left = _header;
_header->_right = _header;
_header->_parent = nullptr;
}
bool Insert(const std::pair<K, V>& data)
{
//空樹,插入的爲根結點
if(_header->_parent == nullptr)
{
Node* root = new Node(data);
//根節點顏色必須爲黑色
root->_color = BLACK;
_header->_parent = root;
root->_parent = _header;
_header->_left = root;
_header->_right = root;
return true;
}
Node* cur = _header->_parent;
Node* parent = _header;
while(cur)
{
if(cur->_data.first < data.first)
{
parent = cur;
cur = cur->_right;
}
else if(cur->_data.first > data.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(data);
if(parent->_data.first < data.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
//_header->_left = cur;
//調整:修改顏色,旋轉
while(cur != _header->_parent && cur->_parent->_color == RED)
{
Node* parent = cur->_parent;
Node* gParent = parent->_parent;
if(gParent->_left == parent)
{
Node *uncle = gParent->_right;
//情況一
if(uncle && uncle->_color == RED)
{
//更新顏色
parent->_color = uncle->_color = BLACK;
gParent->_color = RED;
//向上繼續更新
cur = gParent;
}
//情況二/三
else
{
//叔叔不存在或者叔叔爲黑色
//判斷這裏是否存在雙旋的場景
if(cur = parent->_right)
{
//情況三
//此時就是一個折現的形態就需要兩次旋轉了
RotateL(parent);
//左旋後,父親變子,子變父親,重回情況er
std::swap(cur, parent);
}
else
{
//情況二
RotateR(gParent);
//更改顏色
parent->_color = BLACK;
gParent->_color = RED;
//此時這課子樹的根爲黑色,所以不需要再繼續向上調整
break;
}
}
}
else
{
Node* uncle = gParent->_left;
if(uncle && uncle->_color == RED)
{
parent->_color = uncle->_color = BLACK;
gParent->_color = RED;
cur = gParent;
}
else
{
if(cur == parent->_left)
{
RotateR(parent);
std::swap(cur, parent);
}
else
{
RotateL(gParent);
parent->_color = BLACK;
gParent->_color = RED;
break;
}
}
}
}
//根結點的顏色必須爲黑色
_header->_parent->_color = BLACK;
//更新頭節點
_header->_left = LeftMost();
_header->_right = RightMost();
return true;
}
//左旋
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = parent->_right->_left;
parent->_right = subRL;
if(subRL)
{
subRL->_parent = parent;
}
subR->_left = parent;
Node* ppNode = parent->_parent;
parent->_parent = subR;
//根
if(ppNode == _header)
{
_header->_parent = subR;
subR->_parent = _header;
}
else
{
if(ppNode->_right == parent)
{
ppNode->_right = subR;
}
else
{
ppNode->_left = subR;
}
subR->_parent = ppNode;
}
}
//右旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = parent->_left->_right;
parent->_left = subLR;
if(subLR)//subLR可能會爲空,當h == 0時subLR爲空
{
subLR->_parent = parent;
}
subL->_right = parent;//subL不可能爲空
//記錄下parent原來的父結點,爲了方便parent移動可以找到這棵子樹的父結點
Node* ppNode = parent->_parent;
parent->_parent = subL;
//更新這棵子樹的新父結點subL與其父結點的連接
if(ppNode == _header)//如果子樹的父結點爲空則說明parent原本是整棵樹的根節點
{
_header->_parent= subL;
subL->_parent = _header;
}
else
{
if(ppNode->_right == parent)
{
ppNode->_right = subL;
}
else
{
ppNode->_left = subL;
}
subL->_parent = ppNode;
}
}
//找到當前樹的最左結點
Node* LeftMost()
{
Node* cur = _header->_parent;
while(cur && cur->_left)
{
cur = cur->_left;
}
return cur;
}
//找到當前樹的最有結點
Node* RightMost()
{
Node* cur = _header->_parent;
while(cur && cur->_right)
{
cur = cur->_right;
}
return cur;
}
//中序遍歷
void Inorder()
{
_Inorder(_header->_parent);
}
void _Inorder(Node* root)
{
if(root == nullptr)
{
return;
}
_Inorder(root->_left);
std::cout << root->_data.first << " ";
_Inorder(root->_right);
}
//判斷是否是一個紅黑樹
bool IsRBTree()
{
Node* root = _header->_parent;
if(root == nullptr)
{
return true;
}
if(root->_color == RED)
{
return false;
}
//統計一條路徑黑結點的數量
int blackCount = 0;
Node* cur = root;
while(cur)
{
if(cur->_color == BLACK)
{
blackCount++;
}
cur = cur->_left;
}
//前序遍歷
return _IsRBTree(root, blackCount, 0);
}
bool _IsRBTree(Node* root, int blackCount, int curBlackCount)
{
if(root == nullptr)
{
if(curBlackCount != blackCount)
{
return false;
}
return true;
}
//統計黑色結點的數量
if(root->_color == BLACK)
{
curBlackCount++;
}
//判斷是否有紅色連續
if(root->_parent->_color == RED && root->_color == RED)
{
return false;
}
return _IsRBTree(root->_left, blackCount, curBlackCount)
&& _IsRBTree(root->_right, blackCount, curBlackCount);
}
private:
//Node* _root;
//這裏爲了方便後續封裝成map/set我們將其結構改造成一棵帶頭結點的環形樹
//這裏的頭和環形類似於實現過的帶頭雙向環形鏈表
//頭節點的右孩子連接最右結點,左孩子連接最左結點,用頭指向樹真正的根結點
//相當於這個頭結點是倒過來的,和真正的根結點頭連着頭,連個孩子和最左最右結點構成兩個環
//封裝成這種結構都是爲了方便我們後續進一步封裝,儘量和庫中的保持一致
Node* _header;
};
void TestRBTree()
{
RBTree<int, int> rbt;
rbt.Insert(std::make_pair(1, 1));
rbt.Insert(std::make_pair(10, 1));
rbt.Insert(std::make_pair(2, 1));
rbt.Insert(std::make_pair(5, 1));
rbt.Insert(std::make_pair(3, 1));
rbt.Insert(std::make_pair(4, 1));
rbt.Insert(std::make_pair(7, 1));
rbt.Inorder();
std::cout << std::endl;
//驗證
std::cout << (rbt.IsRBTree()) << std::endl;
}
執行TestRBTree:
1 2 3 4 5 7 10
1
RBTree
相比AVLTree
性能更好,降低了需要旋轉的次數,並且RBTree
實現起來相比AVLTree
較簡單。
封裝
STL庫中的map/set
的底層數據結構就是一棵紅黑樹,到了這一步我們從最基本的BSTree
一路過關斬將最終實現了RBTree
的基本功能,接下來我們要做的就是對RBTree
進一步封裝,使其完成map/set
的基本功能。
RBTreeMod.hpp:
#pragma once
#include <iostream>
#include <utility>
//爲了方便封裝進行的修改版本的紅黑樹
enum Color
{
RED,
BLACK
};
template<class V>
struct RBTNode
{
RBTNode(const V& data = V())
:_data(data)
,_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_color(RED)
{
}
V _data;
RBTNode<V>* _left;
RBTNode<V>* _right;
RBTNode<V>* _parent;
Color _color;
};
//每個容器都有自己的迭代器,我們的Map/Set也必須有!
//在這裏封裝實現迭代器,Map/Set結構都是一致的,所以是現在RBTree的頭文件中
template<class V>
class _RBTreeIterator
{
//封裝紅黑樹的結點
typedef RBTNode<V> Node;
typedef _RBTreeIterator<V> Self;
public:
_RBTreeIterator(Node* node)
:_node(node)
{
}
V& operator*()
{
return _node->_data;
}
V* operator->()
{
return &_node->_data;
}
bool operator!=(const Self& it)
{
return _node != it._node;
}
bool operator==(const Self& it)
{
return _node == it._node;
}
//我們之前更改紅黑樹的結構使其變成帶頭的就是爲這裏迭代器的遍歷做鋪墊
//1、_node->_right存在,走到右子樹的最左結點
//2、_node->_right不存在,向上回溯,只要_node == parent->_right就繼續向上回溯,不滿足條件則停止回溯,更新_node的值爲parent
Self& operator++()
{
if(_node->_right)
{
//找到右子樹最左結點
_node = _node->_right;
while(_node->_left)
{
_node = _node->_left;
}
}
else
{
Node* parent = _node->_parent;
while(_node == parent->_right)
{
_node = parent;
parent = parent->_parent;
}
//這個判斷是爲了避免樹中沒有右子樹導致死循環的情況
if(_node->_right != parent)
{
_node = parent;
}
}
return *this;
}
//1、_node->_left存在,找左子樹的最有結點
//2、_node->_left不存在,只要_node != parent->_right,向上回溯,條件不滿足則更新_node爲parent
Self& operator--()
{
if(_node->_left)
{
_node = _node->_left;
while(_node->_right)
{
_node = _node->_right;
}
}
else
{
Node* parent = _node->_parent;
while(_node != parent->_right)
{
_node = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
private:
Node* _node;
};
//此時的結構仿照STL中的內容,K依然代表Key,而V代表節點中data的類型
//如果是map則V->std::pair<K, V>,如果是set則V->K
//這樣實現的原因是爲了方便紅黑樹更爲靈活的可以分別實現map和set
template<class K, class V, class KeyOfValue>
class RBTree
{
typedef RBTNode<V> Node;
public:
typedef _RBTreeIterator<V> iterator;
//中序遍歷的頭即樹中的最左結點
iterator begin()
{
return iterator(_header->_left);
}
//尾註意是它本身
iterator end()
{
return iterator(_header);
}
RBTree(const V& data = V())
:_header(new Node(data))
{
_header->_left = _header;
_header->_right = _header;
_header->_parent = nullptr;
}
std::pair<iterator, bool> Insert(const V& data)
{
//空樹,插入的爲根結點
if(_header->_parent == nullptr)
{
Node* root = new Node(data);
//根節點顏色必須爲黑色
root->_color = BLACK;
_header->_parent = root;
root->_parent = _header;
_header->_left = root;
_header->_right = root;
return std::make_pair(iterator(root), true);
}
Node* cur = _header->_parent;
Node* parent = _header;
KeyOfValue kov;
while(cur)
{
//修改
if(kov(cur->_data) < kov(data))
{
parent = cur;
cur = cur->_right;
}
else if(kov(cur->_data) > kov(data))
{
parent = cur;
cur = cur->_left;
}
else
{
//return false;
return std::make_pair(iterator(cur), false);
}
}
cur = new Node(data);
if(kov(parent->_data) < kov(data))
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
Node* newNode = cur;
//_header->_left = cur;
//調整:修改顏色,旋轉
while(cur != _header->_parent && cur->_parent->_color == RED)
{
Node* parent = cur->_parent;
Node* gParent = parent->_parent;
if(gParent->_left == parent)
{
Node *uncle = gParent->_right;
//情況一
if(uncle && uncle->_color == RED)
{
//更新顏色
parent->_color = uncle->_color = BLACK;
gParent->_color = RED;
//向上繼續更新
cur = gParent;
}
//情況二/三
else
{
//叔叔不存在或者叔叔爲黑色
//判斷這裏是否存在雙旋的場景
if(cur = parent->_right)
{
//情況三
//此時就是一個折現的形態就需要兩次旋轉了
RotateL(parent);
//左旋後,父親變子,子變父親,重回情況er
std::swap(cur, parent);
}
else
{
//情況二
RotateR(gParent);
//更改顏色
parent->_color = BLACK;
gParent->_color = RED;
//此時這課子樹的根爲黑色,所以不需要再繼續向上調整
break;
}
}
}
else
{
Node* uncle = gParent->_left;
if(uncle && uncle->_color == RED)
{
parent->_color = uncle->_color = BLACK;
gParent->_color = RED;
cur = gParent;
}
else
{
if(cur == parent->_left)
{
RotateR(parent);
std::swap(cur, parent);
}
else
{
RotateL(gParent);
parent->_color = BLACK;
gParent->_color = RED;
break;
}
}
}
}
//根結點的顏色必須爲黑色
_header->_parent->_color = BLACK;
//更新頭節點
_header->_left = LeftMost();
_header->_right = RightMost();
//return true;
return std::make_pair(iterator(newNode), true);
}
//左旋
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = parent->_right->_left;
parent->_right = subRL;
if(subRL)
{
subRL->_parent = parent;
}
subR->_left = parent;
Node* ppNode = parent->_parent;
parent->_parent = subR;
//根
if(ppNode == _header)
{
_header->_parent = subR;
subR->_parent = _header;
}
else
{
if(ppNode->_right == parent)
{
ppNode->_right = subR;
}
else
{
ppNode->_left = subR;
}
subR->_parent = ppNode;
}
}
//右旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = parent->_left->_right;
parent->_left = subLR;
if(subLR)//subLR可能會爲空,當h == 0時subLR爲空
{
subLR->_parent = parent;
}
subL->_right = parent;//subL不可能爲空
//記錄下parent原來的父結點,爲了方便parent移動可以找到這棵子樹的父結點
Node* ppNode = parent->_parent;
parent->_parent = subL;
//更新這棵子樹的新父結點subL與其父結點的連接
if(ppNode == _header)//如果子樹的父結點爲空則說明parent原本是整棵樹的根節點
{
_header->_parent= subL;
subL->_parent = _header;
}
else
{
if(ppNode->_right == parent)
{
ppNode->_right = subL;
}
else
{
ppNode->_left = subL;
}
subL->_parent = ppNode;
}
}
//找到當前樹的最左結點
Node* LeftMost()
{
Node* cur = _header->_parent;
while(cur && cur->_left)
{
cur = cur->_left;
}
return cur;
}
//找到當前樹的最有結點
Node* RightMost()
{
Node* cur = _header->_parent;
while(cur && cur->_right)
{
cur = cur->_right;
}
return cur;
}
//中序遍歷
void Inorder()
{
_Inorder(_header->_parent);
}
void _Inorder(Node* root)
{
if(root == nullptr)
{
return;
}
_Inorder(root->_left);
std::cout << root->_data.first << " ";
_Inorder(root->_right);
}
//判斷是否是一個紅黑樹
bool IsRBTree()
{
Node* root = _header->_parent;
if(root == nullptr)
{
return true;
}
if(root->_color == RED)
{
return false;
}
//統計一條路徑黑結點的數量
int blackCount = 0;
Node* cur = root;
while(cur)
{
if(cur->_color == BLACK)
{
blackCount++;
}
cur = cur->_left;
}
//前序遍歷
return _IsRBTree(root, blackCount, 0);
}
bool _IsRBTree(Node* root, int blackCount, int curBlackCount)
{
if(root == nullptr)
{
if(curBlackCount != blackCount)
{
return false;
}
return true;
}
//統計黑色結點的數量
if(root->_color == BLACK)
{
curBlackCount++;
}
//判斷是否有紅色連續
if(root->_parent->_color == RED && root->_color == RED)
{
return false;
}
return _IsRBTree(root->_left, blackCount, curBlackCount)
&& _IsRBTree(root->_right, blackCount, curBlackCount);
}
private:
//Node* _root;
//這裏爲了方便後續封裝成map/set我們將其結構改造成一棵帶頭結點的環形樹
//這裏的頭和環形類似於實現過的帶頭雙向環形鏈表
//頭節點的右孩子連接最右結點,左孩子連接最左結點,用頭指向樹真正的根結點
//相當於這個頭結點是倒過來的,和真正的根結點頭連着頭,連個孩子和最左最右結點構成兩個環
//封裝成這種結構都是爲了方便我們後續進一步封裝,儘量和庫中的保持一致
Node* _header;
};
//void TestRBTree()
//{
// RBTree<int, int> rbt;
// rbt.Insert(std::make_pair(1, 1));
// rbt.Insert(std::make_pair(10, 1));
// rbt.Insert(std::make_pair(2, 1));
// rbt.Insert(std::make_pair(5, 1));
// rbt.Insert(std::make_pair(3, 1));
// rbt.Insert(std::make_pair(4, 1));
// rbt.Insert(std::make_pair(7, 1));
// rbt.Inorder();
// std::cout << std::endl;
// //驗證
// std::cout << (rbt.IsRBTree()) << std::endl;
//}
Map.hpp:
#include "RBTreeMod.hpp"
template<class K, class V>
class Map
{
//爲了讓紅黑樹可以根據調用它的不同類型得以確定比較條件
//我們這裏用內部類創建一個反函數用域返回當前結構下的Key值
struct MapKeyOfValue
{
const K& operator()(const std::pair<K, V>& data)
{
return data.first;
}
};
public:
//這裏爲了能夠動態識別這是一個類型要在前面加上typename關鍵字
typedef typename RBTree<K, std::pair<K, V>, MapKeyOfValue>::iterator iterator;
std::pair<iterator, bool> Insert(const std::pair<K, V>& data)
{
return _rbt.Insert(data);
}
//實現迭代器
iterator begin()
{
return _rbt.begin();
}
iterator end()
{
return _rbt.end();
}
V& operator[](const K& key)
{
std::pair<iterator, bool> ret = _rbt.Insert(std::make_pair(key, V()));
return ret.first->second;
}
private:
RBTree<K, std::pair<K, V>, MapKeyOfValue> _rbt;
};
Set.hpp:
#include "RBTreeMod.hpp"
template<class K>
class Set
{
struct SetKeyOfValue
{
const K& operator()(const K& data)
{
return data;
}
};
public:
typedef typename RBTree<K, K, SetKeyOfValue>::iterator iterator;
std::pair<iterator, bool> Insert(const K& data)
{
return _rbt.Insert(data);
}
//實現迭代器
iterator begin()
{
return _rbt.begin();
}
//實現迭代器
iterator end()
{
return _rbt.end();
}
private:
RBTree<K, K, SetKeyOfValue> _rbt;
};
test.cpp:
#include "Map.hpp"
#include "Set.hpp"
void TestMapSet()
{
Map<int, int> M;
M.Insert(std::make_pair(10, 1));
M.Insert(std::make_pair(3, 1));
M.Insert(std::make_pair(9, 1));
M.Insert(std::make_pair(2, 1));
M.Insert(std::make_pair(1, 1));
for(auto e : M)
{
std::cout << e.first << " " << e.second << std::endl;
}
M[1] = 100;
M[500] = 50;
std::cout << std::endl;
for(auto e : M)
{
std::cout << e.first << " " << e.second << std::endl;
}
Set<int> S;
S.Insert(1);
S.Insert(3);
S.Insert(5);
S.Insert(6);
S.Insert(2);
S.Insert(6);
std::cout << std::endl;
for(auto e : S)
{
std::cout << e << " ";
}
}
int main()
{
//TestBsTree1();
//TestAVLTree()m
//TestRBTree();
TestMapSet();
}
1 1
2 1
3 1
9 1
10 1
1 100
2 1
3 1
9 1
10 1
500 50
1 2 3 5 6
map/set
這種鏈式容器,也存在迭代器失效問題,但其的插入不會造成迭代器失效,刪除會造成刪除結點的迭代器失效。
由此一來,我們就完全實現了map/set
兩個容器,我們一路從BSTree
到最終的RBTree
,中間充滿了坎坷和艱辛,不過這一趟學習下來,我們對map/set
及其multi
版本的底層實現有了進一步的理解,我們今後對其的使用也會變得更加得心應手。
但是至此我們的關聯式容器部分還並沒有完全結束,我們接下來會討論unordered
版本的關聯式容器,他們的底層與普通版本的底層數據結構大相徑庭,用到了哈希有關知識。