STL原始碼分析之list有序容器 下
前言
前兩節對list
的push, pop, insert等操作做了分析, 本節準備探討list
怎麼實現sort
功能.
list
是一個迴圈雙向連結串列, 不是一個連續地址空間, 所以sort
功能需要特殊的演算法單獨實現, 而不能用演算法中的sort. 當然還可以將list
的元素插入到vector中最後在將vector排序好的資料拷貝回來, 不過這種做法很費時, 費效率.
list操作實現
在分析sort
之前先來分析transfer
, reverse
, merge
這幾個會被呼叫的函式.
transfer函式
transfer
函式功能是將一段連結串列插入到我們指定的位置之前
transfer
函式接受3個迭代器. 第一個迭代器表示連結串列要插入的位置, first
到last
最閉右開區間插入到position
之前.
從if
下面開始分析(這裡我將原始碼的執行的先後順序進行的部分調整, 下面我分析的都是調整順序過後的程式碼. 當然我也會把原始碼順序寫下來, 以便參考)
- 為了避免待會解釋起來太繞口, 這裡先統一一下部分名字
last
的前一個節點叫last_but_one
first
的前一個節點叫zero
- 好, 現在我們開始分析
transfer
的每一步(最好在分析的時候在紙上畫出兩個連結串列一步步來畫
- 第一行.
last_but_one
的next
指向插入的position
節點 - 第二行.
position
的next
指向last_but_one
- 第三行. 臨時變數
tmp
儲存position
的前一個節點 - 第四行.
first
的prev
指向tmp
- 第五行.
position
的前一個節點的next
指向first
節點 - 第六行.
zero
的next
指向last
節點 - 第七行.
last
的prev
指向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的分析 :
-
這裡將每個重要的引數列出來解釋其含義
-
fill
: 當前可以處理的元素個數為2^fill個 -
counter[fill]
: 可以容納2^(fill+1)個元素 -
carry
: 一個臨時中轉站, 每次將一元素插入到counter[i]連結串列中.
-
在處理的元素個數不足2^fill個時,在counter[i](0<i<fill)
之前轉移元素
具體是顯示步驟是:
- 每次讀一個數據到
carry
中,並將carry的資料轉移到counter[0]
中- 當
counter[0]
中的資料個數少於2時,持續轉移資料到counter[0]中 - 當counter[0]的資料個數等於2時,將counter[0]中的資料轉移到counter[1]…從counter[i]轉移到counter[i+1],直到counter[fill]中資料個數達到2^(fill+1)個。
- 當
- ++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
最難的transfer
和sort
實現, 當然transfer
函式是整個實現的核心. 我在將本節分析的函式在進行一個歸納.
transfer
: 將兩個段連結串列進行合併(兩段可以是來自同一個連結串列, 但不交叉).merge
: 前提兩個段連結串列都已經排好序. 將兩段連結串列按從小到大的順序進行合併, 主要是sort
實現呼叫.reverse
: 呼叫transfer
函式將元素一個個調整到begin
之前, 實現連結串列的轉置.sort
: 運用歸併思想將連結串列分段排序.