1. 程式人生 > >STL之tree的實現詳解

STL之tree的實現詳解

1、紅黑樹介紹

關聯容器都有一個key(鍵)和一個value(值)。當元素被插入到關聯式容器中時,內部結構依照其鍵值的大小,以特定的規則將元素放到合適的位置(實現查詢演算法吧)。
一般關聯時容器內部結構是一個平衡二叉樹,用於在惡劣的環境下獲得良好的搜尋效率。平衡二叉樹的實現有:AVL-tree、RB-tree、AA-tree。用的最廣的就是RB-tree,不論是在Nginx還是Linux核心任務排程,都使用廣泛,所以在STL中也不例外。

關聯式容器分為set(集合)和map(對映)兩大類,以及衍生出來的multiset(多鍵集合)和multimap(多鍵對映)。這些容器都是通過紅黑樹實現。所以這節就詳細講講STL裡面各種樹的實現。
這裡寫圖片描述

平衡二叉搜尋樹:
AVL-Tree:保證任何節點的左右子樹高度相差最大1。

RB-Tree:
這裡寫圖片描述
紅黑樹的實現超級複雜,這裡看起來真的不容易,先放過去,後面需要看的時候,再詳細看看,先不著急哦。重點講解紅黑樹應該如何使用以及程式碼架構。紅黑樹通過迭代器從頭到尾遍歷,便可以提供排序操作。不應該通過紅黑樹的迭代器修改key值。紅黑樹是通過比較key建成的平衡二叉樹,所以不允許隨意更改key值,但是可以修改節點裡面對應的data值,這就是為set和map提供了思路

2、紅黑樹迭代器

所謂迭代器,就是提供一種遍歷容器內部元素的方法,在記憶體連續的容器內指標就是天然的迭代器,因為天然支援++、– 、+= 、== 、!=等操作。但是紅黑樹的迭代器不能直接使用指標,因為其節點在記憶體中分配是不連續的。那麼如何製造一個類,使得也可以提供如同天然指標的操作呢?那麼必然就是定義一個對應的類,然後將上述相應的操作符過載,這就是所謂的迭代器模式了,紅黑樹也是如此的。
紅黑樹迭代器使用的兩層,這真的是沒有一點必要,何必這樣設計了,通過一個迭代器過載不就可以了嗎?估計是為了簡單一點吧。
第一層:
這是基本迭代器,注意裡面僅僅含有一個變數node

指向__rb_tree_node_base
因為二叉樹裡面是排好序的,所以increment使得node指向紅黑樹中比當前key值大一個的節點,decrement則是使得node指向紅黑樹中比當前key值小一個的節點

struct __rb_tree_base_iterator
{
  typedef __rb_tree_node_base::base_ptr base_ptr;//指向一個__rb_tree_node_base
  typedef bidirectional_iterator_tag iterator_category;
  typedef ptrdiff_t difference_type;
  base_ptr node;//指向一個__rb_tree_node_base
void increment();//找到比node大的第一個節點 void decrement();//找到比node小的第一個節點 };

第二層迭代器:
繼承了第一層裡面的方法,然後實現了操作符過載,使得紅黑樹迭代器支援如同天然指標的一樣的操作。例如通過ite++操作符遍歷紅黑樹,就將key值從小到大對應著遍歷出來。

template <class Value, class Ref, class Ptr>//紅黑樹迭代器
struct __rb_tree_iterator : public __rb_tree_base_iterator
{
  typedef Value value_type;
  typedef Ref reference;
  typedef Ptr pointer;
  typedef __rb_tree_iterator<Value, Value&, Value*>             iterator;//類定義
  typedef __rb_tree_iterator<Value, const Value&, const Value*> const_iterator;//類定義
  typedef __rb_tree_iterator<Value, Ref, Ptr>                   self;//類定義
  typedef __rb_tree_node<Value>* link_type;//一個__rb_tree_node指標定義

  __rb_tree_iterator() {}//建構函式
  __rb_tree_iterator(link_type x) { node = x; }//這裡有蹊蹺,設計繼承和基類之間的關係,父類指標給了基類會產生什麼情況?編譯器自動轉換,不需要強制
  __rb_tree_iterator(const iterator& it) { node = it.node; }

  //相當於指向結構體的成員
  reference operator*() const { return link_type(node)->value_field; }//link_type(node)呼叫__rb_tree_node的建構函式,基類轉換成父類必須強制轉換成父類
  pointer operator->() const { return &(operator*()); }
  self& operator++() { increment(); return *this; }//++ite,increment()將node修改為大一點的節點指標,返回引用
  self operator++(int) {//ite++
    self tmp = *this;//返回__rb_tree_iterator物件,迭代器裡面有成員支援對應的操作。
    increment();
    return tmp;
  }    
  self& operator--() { decrement(); return *this; }//--ite
  self operator--(int) {//ite--
    self tmp = *this;
    decrement();
    return tmp;
  }
};  

3、紅黑樹資料結構

對應與迭代器,也有兩層資料結構,這直接寫一層不就得了,還要寫兩層,真是有病哦。具體不清楚為啥要寫兩層,但是咱們可以來剖析。
第一層資料結構:
包含父子節點以及顏色資訊,包含求最大和最小值的方法而已。

struct __rb_tree_node_base//節點結構體
{
  typedef __rb_tree_color_type color_type;
  typedef __rb_tree_node_base* base_ptr;
  color_type color; 
  base_ptr parent; //父節點,RB樹必須知道父節點
  base_ptr left;   //左子樹
  base_ptr right;  //右子樹

  static base_ptr minimum(base_ptr x)//直接左子樹,找最小值
  {
    while (x->left != 0) x = x->left;
    return x;
  }

  static base_ptr maximum(base_ptr x)//直接右子樹,找最大值
  {
    while (x->right != 0) x = x->right;
    return x;
  }
};

第二層資料結構:
繼承了第一層,並添加了value_field資料域這個資訊,當然這裡使用的都是模板,當時候,客戶儲存的資料都放在value_field中。

template <class Value>//一個節點定義,包含__rb_tree_node_base和value_field
struct __rb_tree_node : public __rb_tree_node_base
{
  typedef __rb_tree_node<Value>* link_type;//一個__rb_tree_node指標定義
  Value value_field;//節點值
};

下圖是迭代器和節點之間的對應關係。迭代器裡面僅僅需要指向節點的指標,加上實現自增、自減操作符即可。很容易。
這裡寫圖片描述

4、紅黑樹模板類

模板類,具體搞清楚紅黑樹的資料結構,以及內部模板引數應該如何設定,裡面的具體一些插入、旋轉等等實現紅黑樹的操作沒必要去看。
1、Key是鍵的型別。
2、Value是鍵和值的合體型別。一般節點裡面有鍵和值。紅黑樹裡面將鍵和值合併成一個Value。
3、KeyOfValue:告訴如何從Value中拿出key,這通常是一個仿函式。
4、Compare:因為插入和查詢都是通過比較key的,所以必須紅黑樹如何比較key。
5、Alloc:記憶體分配器,通常使用預設記憶體分配器。
這幾個引數重點搞清楚Key和Value的區別。

6、header相當於指向紅黑樹的頭節點,此頭節點裡面儲存了最左節點,最右節點,以及紅黑樹根節點,所以僅僅需要這一個節點即可管理一顆紅黑樹。
7、node_count記錄紅黑樹中節點的個數。
8、key_compare定義一個仿函式物件,通過函式呼叫來比較對應的鍵值。
下圖顯示了header的使用方法,一顆紅黑樹,通過一個header管理即可。
這裡寫圖片描述
至於內部一些插入,旋轉的方法,我們暫時先不管。

/*
Key是鍵的型別。 
Value是鍵和值的合體型別
KeyOfValue:告訴如何從Value中拿出key。
Compare:如何比較key。
Alloc:記憶體分配器
*/  
template <class Key, class Value, class KeyOfValue, class Compare,
          class Alloc = alloc>
class rb_tree {
protected:
  typedef void* void_pointer;
  typedef __rb_tree_node_base* base_ptr;
  typedef __rb_tree_node<Value> rb_tree_node;//節點定義
  typedef simple_alloc<rb_tree_node, Alloc> rb_tree_node_allocator;//節點記憶體分配器
  typedef __rb_tree_color_type color_type;//顏色型別定義
public:
  typedef Key key_type;//鍵型別
  typedef Value value_type;//值型別
  typedef value_type* pointer;
  typedef const value_type* const_pointer;
  typedef value_type& reference;
  typedef const value_type& const_reference;
  typedef rb_tree_node* link_type;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;

  typedef __rb_tree_iterator<value_type, reference, pointer> iterator;//迭代器定義
  typedef __rb_tree_iterator<value_type, const_reference, const_pointer> 
          const_iterator;//常量迭代器定義  
protected:
  link_type get_node() { return rb_tree_node_allocator::allocate(); }//分配一個節點記憶體
  void put_node(link_type p) { rb_tree_node_allocator::deallocate(p); }//釋放一個節點記憶體
  link_type create_node(const value_type& x) {//在記憶體上面構造節點
    link_type tmp = get_node();
    __STL_TRY {
      construct(&tmp->value_field, x);
    }
    __STL_UNWIND(put_node(tmp));
    return tmp;
  }

  link_type clone_node(link_type x) {//克隆一個節點,值和色
    link_type tmp = create_node(x->value_field);
    tmp->color = x->color;
    tmp->left = 0;
    tmp->right = 0;
    return tmp;
  }

  void destroy_node(link_type p) {//刪除一個節點,釋放記憶體,解構函式。
    destroy(&p->value_field);
    put_node(p);
  }

protected:
  //類中只有三個資料表現自己
  size_type node_count; //記錄數節點數量
  link_type header;     //實現一個技巧
  Compare key_compare;  //節點間的鍵值大小比較函式,仿函式,也就是仿函式
  //類資料就這個三個。
  //4 + 4 + 1 = 9 --->124位元組對齊
  //仿函式為空,預設都是1位元組。 
  .............  
}

4、紅黑樹幾個重要方法

iterator begin() { return leftmost(); }//RB樹的起頭為最左節點(指向數值最小的節點),類裡面含有一個起始迭代器和結束結束迭代器,用來管理樹
iterator end() { return header; }//RB樹的終點,最右邊節點(指向數值最大的節點),頭結點使用,使其實現左閉右開的用法。

  //將x插入到RB-tree中(保持節點key值獨一無二),返回一個pair物件,裡面儲存對應迭代器和成功與否。
 pair<iterator,bool> insert_unique(const value_type& x);

 //key值可以重複
  //將x插入到RB-tree中(允許節點值重複),返回插入的新節點的迭代器。
 iterator insert_equal(const value_type& x);

使用紅黑樹demo

#include <bits/stl_tree.h>
int main(void)
{
    _Rb_tree<int , int , _Identity<int> , less<int> > itree;
    cout << itree.empty() << endl;//1
    cout << itree.size() << endl;//0

    itree._M_insert_unique(3);
    itree._M_insert_unique(8);
    itree._M_insert_unique(5);
    itree._M_insert_unique(9);
    itree._M_insert_unique(13);
    itree._M_insert_unique(5);//沒有作用,因為unique插入

    cout << itree.empty() << endl;//0
    cout << itree.size() << endl;//5
    cout << itree.count(5);//1 找出紅黑樹中節點為5的個數

    itree._M_insert_equal(5);
    itree._M_insert_equal(5);

    cout << itree.size() << endl;//7
    cout << itree.count(5) << endl;//3 找出紅黑樹中節點為5的個數

注意使用紅黑樹需要傳遞5個引數進去,_Rb_tree<int , int , _Identity<int> , less<int> >,第一個為int表示我的鍵是int型別,第二個也為int表示我的key和data合起來也是int,那麼也就是key就是data,這個樹裡面沒有儲存資料。第三個告訴紅黑樹如何從value取出key,這個函式就是直接返回鍵。第四個是如何比較鍵值,第五個採用預設引數。

template <class T>
struct identity : public unary_function<T, T> {
  const T& operator()(const T& x) const { return x; }
};//同樣的,直接返回x