STL原始碼剖析——list容器的排序演算法sort()
原文:https://blog.csdn.net/chenhanzhun/article/details/39337331
前言
由於STL本身的排序演算法sort接受的輸入迭代器是隨機訪問迭代器,但是雙向list連結串列容器的訪問方式是雙向迭代器,因此,不能使用STL本身的排序演算法sort,必須自己定義屬於自己訪問的排序演算法。我們從原始碼的剖析中,可以看到該排序演算法思想類似於歸併排序。
list容器之排序演算法sort
在該排序演算法的實現過程中,定義了一個類似於搬運作用的連結串列carry和具有中轉站作用的連結串列counter,這裡首先對counter[i]裡面儲存資料的規則進行分析;counter[i]裡面最多儲存資料個數為
//按升序進行排序,list連結串列的迭代器訪問時雙向迭代器 //因為STL的排序演算法函式sort()是接受隨機訪問迭代器,在這裡並不適合 template <class _Tp, class _Alloc> void list<_Tp, _Alloc>::sort() { // Do nothing if the list has length 0 or 1. if (_M_node->_M_next != _M_node && _M_node->_M_next->_M_next != _M_node) { list<_Tp, _Alloc> __carry;//carry連結串列起到搬運的作用 //counter連結串列是中間儲存作用 /* *其中對於counter[i]裡面最多的儲存資料為2^(i+1)個節點 *若超出則向高位進位即counter[i+1] */ list<_Tp, _Alloc> __counter[64]; int __fill = 0; while (!empty()) {//若不是空連結串列 //第一步: __carry.splice(__carry.begin(), *this, begin());//把當前連結串列的第一個節點放在carry連結串列頭 int __i = 0; while(__i < __fill && !__counter[__i].empty()) { //第二步: __counter[__i].merge(__carry);//把連結串列carry合併到counter[i] //第三步: __carry.swap(__counter[__i++]);//交換連結串列carry和counter[i]內容 } //第四步: __carry.swap(__counter[__i]);//交換連結串列carry和counter[i]內容 //第五步: if (__i == __fill) ++__fill; } for (int __i = 1; __i < __fill; ++__i) //第六步: __counter[__i].merge(__counter[__i-1]);//把低位不滿足進位的剩餘資料全部有序的合併到上一位 //第七步: swap(__counter[__fill-1]);//最後把已排序好的連結串列內容交換到當前連結串列 } }
從原始碼中,我們可以看到,第一個while迴圈執行的條件是當前連結串列必須非空,該演算法的核心就是while裡面的處理,巢狀while(即第二個while)執行的條件是i小於fill且counter[i]連結串列是非空;下面給出例項進行分析,在分析之前先做一些規定:
- 把第20行語句carry.splice(carry.begin(), *this, begin())標記為下圖中執行的步驟1;
- 把第25行語句counter[i].merge(carry)標記為下圖中執行的步驟2;
- 把第27行語句carry.swap(counter[i++])標記為下圖中執行的步驟3;
- 把第30行語句carry.swap(counter[i])標記為下圖中執行的步驟4;
- 把第32行語句if (i == fill) ++fill標記為下圖中執行的步驟5;
- 把第37行for迴圈裡面語句counter[i].merge(counter[i-1])標記為下圖中執行的步驟6;
- 把第39行最後一個語句swap(counter[fill-1])標記為下圖中執行的步驟7;
下圖是待排序的原始連結串列:
下面是排序演算法sort的執行過程,定義初始值fill=0,因為原始連結串列有四個節點,即原始連結串列非空,則執行while迴圈裡面的語句:注:由於方便畫圖,下面相鄰方塊之間表示是雙向連結串列的連線;白色的方塊表示空連結串列;
第一次執行while迴圈:步驟1搬運連結串列carry取出當前連結串列的第一個資料節點7,初始值i=0,由於巢狀while迴圈條件不成立,則跳過巢狀while迴圈,直接進入到步驟4,交換carry和counter[i](i=0)的內容;又因為此時i=fill,則更新值++fill,即fill=1;流圖如下:
第二次執行while迴圈:步驟1搬運連結串列carry取出當前連結串列的第一個資料節點5,初始值i=0,由於巢狀while迴圈條件成立,步驟2將carry連結串列的內容有序的合併到counter[i](i=0)連結串列中,再執行步驟3交換carry和counter[0]內容且i++,此時carry有兩個有序的節點5和7,counter[0]為空連結串列,i的值為i=1;這時巢狀while迴圈條件不成立;跳轉到步驟4交換carry和counter[1]的內容,且執行步驟5更新fill的值++fill,第二次while迴圈執行結束時,資料節點的狀況為:carry和counter[0]內容都為空,counter[1]有兩個有序的節點5和7,值fill=2;流圖如下:
第三次執行while迴圈:步驟1搬運連結串列carry取出當前連結串列的第一個資料節點8,初始值i=0,由於巢狀while迴圈條件不成立,則跳過巢狀while迴圈,直接進入到步驟4,交換carry和counter[i](i=0)的內容;又因為此時i不等於fill,則不更新fill值,即fill=2;第三次while迴圈執行結束時,資料節點的狀況為:carry為空,counter[0]內容為一個節點8,counter[1]有兩個有序的節點5和7,值fill=2;流圖如下:
第四次執行while迴圈:步驟1搬運連結串列carry取出當前連結串列的第一個資料節點1,初始值i=0,由於巢狀while迴圈條件成立,步驟2將carry連結串列的內容有序的合併到counter[i](i=0)連結串列中,再執行步驟3交換carry和counter[0]內容且i++,此時carry有兩個有序的節點1和8,counter[0]為空連結串列,i的值為i=1;這時巢狀while迴圈條件依然成立;繼續執行步驟2將carry的內容有序的合併到counter[1]連結串列中,再執行步驟3交換carry和counter[1]的內容且++i,此時,carry內容為四個有序的節點1、5、7和8,counter[0]和counter[1]的內容都為空連結串列,i的值為i=2;這時巢狀while迴圈條件不成立;跳轉到步驟4交換carry和counter[2]的內容,且執行步驟5更新fill的值++fill,第四次while迴圈執行結束時,資料節點的狀況為:carry、counter[0]和counter[1]內容都為空,counter[2]有四個有序的節點1、5、7和8,值fill=3;流圖如下:
最後,由於此時當前連結串列為空連結串列,則跳出while迴圈,執行for語句,因為i=2之前的counter[1]和counter[0]內容都為空,則執行完for語句之後連結串列都沒變化,然後執行步驟7交換當前連結串列和counter[2]的內容,執行完後,counter[2]為空,當前連結串列內容為四個有序的節點1、5、7和8;流程如下:
以上是list排序演算法的整個流程,關鍵是要理解其核心,不斷地更新連結串列內容。
參考資料:
《STL原始碼剖析》侯捷;