1. 程式人生 > >STL原始碼分析之list有序容器 下

STL原始碼分析之list有序容器 下

前言

前兩節對list的push, pop, insert等操作做了分析, 本節準備探討list怎麼實現sort功能.

list是一個迴圈雙向連結串列, 不是一個連續地址空間, 所以sort功能需要特殊的演算法單獨實現, 而不能用演算法中的sort. 當然還可以將list的元素插入到vector中最後在將vector排序好的資料拷貝回來, 不過這種做法很費時, 費效率.

list操作實現

在分析sort之前先來分析transfer, reverse, merge這幾個會被呼叫的函式.

transfer函式

transfer函式功能是將一段連結串列插入到我們指定的位置之前

. 該函式一定要理解, 後面分析的所有函式都是該基礎上進行修改的.

transfer函式接受3個迭代器. 第一個迭代器表示連結串列要插入的位置, firstlast最閉右開區間插入到position之前.

if下面開始分析(這裡我將原始碼的執行的先後順序進行的部分調整, 下面我分析的都是調整順序過後的程式碼. 當然我也會把原始碼順序寫下來, 以便參考)

  • 為了避免待會解釋起來太繞口, 這裡先統一一下部分名字
  1. last的前一個節點叫last_but_one
  2. first的前一個節點叫zero
  • 好, 現在我們開始分析transfer的每一步(最好在分析的時候在紙上畫出兩個連結串列一步步來畫
    )
  1. 第一行. last_but_onenext指向插入的position節點
  2. 第二行. positionnext指向last_but_one
  3. 第三行. 臨時變數tmp儲存position的前一個節點
  4. 第四行. firstprev指向tmp
  5. 第五行. position的前一個節點的next指向first節點
  6. 第六行. zeronext指向last節點
  7. 第七行. lastprev指向zero
template <class T, class Alloc = alloc>
class list 
{
    ...
protected: void transfer(iterator position, iterator first, iterator last) { if (position != last) { (*(link_type((*last.node).prev))).next = position.node; (*position.node).prev = (*last.node).prev; link_type tmp = link_type((*position.node).prev); (*first.node).prev = tmp; (*(link_type((*position.node).prev))).next = first.node; (*(link_type((*first.node).prev))).next = last.node; (*last.node).prev = (*first.node).prev; } } /* void transfer(iterator position, iterator first, iterator last) { if (position != last) { (*(link_type((*last.node).prev))).next = position.node; (*(link_type((*first.node).prev))).next = last.node; (*(link_type((*position.node).prev))).next = first.node; link_type tmp = link_type((*position.node).prev); (*position.node).prev = (*last.node).prev; (*last.node).prev = (*first.node).prev; (*first.node).prev = tmp; } } */ ... };

splice 將兩個連結串列進行合併.

template <class T, class Alloc = alloc>
class list 
{
    ...
public:
    void splice(iterator position, list& x) {
      if (!x.empty()) 
        transfer(position, x.begin(), x.end());
    }
    void splice(iterator position, list&, iterator i) {
      iterator j = i;
      ++j;
      if (position == i || position == j) return;
      transfer(position, i, j);
    }
    void splice(iterator position, list&, iterator first, iterator last) {
      if (first != last) 
        transfer(position, first, last);
    }
    ...
};

merge函式

merge函式接受一個list引數.

merge函式是將傳入的list連結串列x與原連結串列按從小到大合併到原連結串列中(前提是兩個連結串列都是已經從小到大排序了). 這裡merge的核心就是transfer函式.

template <class T, class Alloc>
void list<T, Alloc>::merge(list<T, Alloc>& x) {
  iterator first1 = begin();
  iterator last1 = end();
  iterator first2 = x.begin();
  iterator last2 = x.end();
  while (first1 != last1 && first2 != last2)
    if (*first2 < *first1) {
      iterator next = first2;
      // 將first2到first+1的左閉右開區間插入到first1的前面
      // 這就是將first2合併到first1連結串列中
      transfer(first1, first2, ++next);
      first2 = next;
    }
    else
      ++first1;
      // 如果連結串列x還有元素則全部插入到first1連結串列的尾部
  if (first2 != last2) transfer(last1, first2, last2);
}

reverse函式

reverse函式是實現將連結串列翻轉的功能. 主要是list的迭代器基本不會改變的特點, 將每一個元素一個個插入到begin之前. 這裡注意迭代器不會變, 但是begin會改變, 它始終指向第一個元素的地址.

template <class T, class Alloc>
void list<T, Alloc>::reverse() 
{
  if (node->next == node || link_type(node->next)->next == node) 
  	return;
  iterator first = begin();
  ++first;
  while (first != end()) {
    iterator old = first;
    ++first;
      // 將元素插入到begin()之前
    transfer(begin(), old, first);
  }
} 

sort

list實現sort 功能本身就不容易, 當我分析了之後就對其表示佩服. 嚴格的說list排序的時間複雜度應為nlog(n), 其實現用了歸併排序的思想, 將所有元素分成n分, 總共2^n個元素.

這個sort的分析 :

  • 這裡將每個重要的引數列出來解釋其含義

    1. fill : 當前可以處理的元素個數為2^fill個

    2. counter[fill] : 可以容納2^(fill+1)個元素

    3. carry : 一個臨時中轉站, 每次將一元素插入到counter[i]連結串列中.

在處理的元素個數不足2^fill個時,在counter[i](0<i<fill)之前轉移元素

具體是顯示步驟是:

  1. 每次讀一個數據到carry中,並將carry的資料轉移到counter[0]
    1. counter[0]中的資料個數少於2時,持續轉移資料到counter[0]中
    2. 當counter[0]的資料個數等於2時,將counter[0]中的資料轉移到counter[1]…從counter[i]轉移到counter[i+1],直到counter[fill]中資料個數達到2^(fill+1)個。
  2. ++fill, 重複步驟1
//list 不能使用sort函式,因為list的迭代器是bidirectional_iterator, 而sort
//sort函式要求random_access_iterator
template<class T,class Alloc>
void list<T,Alloc>::sort()
{
    //如果元素個數小於等於1,直接返回
    if(node->next==node||node->next->next==node)
    return ;
    list<T,Alloc> carry; //中轉站
    list<T,Alloc> counter[64];
    int fill=0;
    while(!empty())
    {
        carry.splice(carry.begin(),*this,begin());  //每次取出一個元素
        int i=0;    
        while(i<fill&&!counter[i].empty())
        {
            counter[i].merge(carry);  //將carry中的元素合併到counter[i]中
            carry.swap(counter[i++]);  //交換之後counter[i-1]為空
        }
        carry.swap(counter[i]);
        if(i==fill) 
            ++fill;
    }
    // 將counter陣列連結串列的所有節點按從小到大的順序排列儲存在counter[fill-1]的連結串列中
    for(int i=1;i<fill;++i)
    {
        counter[i].merge(counter[i-1]);
    }
    // 最後將couter與carry交換, 實現排序
    swap(counter[fill-1]);
}

sort用了一個數組連結串列用來儲存2^i個元素, 當上一個元素儲存滿了之後繼續往下一個連結串列儲存, 最後將所有的連結串列進行merge歸併(合併), 從而實現了連結串列的排序.

總結

本節我們分析了list最難的transfersort實現, 當然transfer函式是整個實現的核心. 我在將本節分析的函式在進行一個歸納.

  1. transfer : 將兩個連結串列進行合併(兩段可以是來自同一個連結串列, 但不交叉).
  2. merge : 前提兩個段連結串列都已經排好序. 將兩段連結串列按從小到大的順序進行合併, 主要是sort實現呼叫.
  3. reverse : 呼叫transfer函式將元素一個個調整到begin之前, 實現連結串列的轉置.
  4. sort : 運用歸併思想將連結串列分段排序.