1. 程式人生 > >STL原始碼剖析——list容器的排序演算法sort()

STL原始碼剖析——list容器的排序演算法sort()

原文:https://blog.csdn.net/chenhanzhun/article/details/39337331

前言

    由於STL本身的排序演算法sort接受的輸入迭代器是隨機訪問迭代器,但是雙向list連結串列容器的訪問方式是雙向迭代器,因此,不能使用STL本身的排序演算法sort,必須自己定義屬於自己訪問的排序演算法。我們從原始碼的剖析中,可以看到該排序演算法思想類似於歸併排序。

list容器之排序演算法sort

    在該排序演算法的實現過程中,定義了一個類似於搬運作用的連結串列carry和具有中轉站作用的連結串列counter,這裡首先對counter[i]裡面儲存資料的規則進行分析;counter[i]裡面最多儲存資料個數為

,若儲存資料超過該數字,則向相鄰高位進位,即把counter[i]連結串列裡的內容都合併到counter[i+1]連結串列。carry負責取出原始連結串列的頭一個數據節點和交換資料中轉站作用;原始碼中的fill表示當前可處理資料的個數為。下面給出sort的原始碼分析:

//按升序進行排序,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]連結串列是非空;下面給出例項進行分析,在分析之前先做一些規定:

 

  1. 把第20行語句carry.splice(carry.begin(), *this, begin())標記為下圖中執行的步驟1;
  2. 把第25行語句counter[i].merge(carry)標記為下圖中執行的步驟2;
  3. 把第27行語句carry.swap(counter[i++])標記為下圖中執行的步驟3;
  4. 把第30行語句carry.swap(counter[i])標記為下圖中執行的步驟4;
  5. 把第32行語句if (i == fill) ++fill標記為下圖中執行的步驟5;
  6. 把第37行for迴圈裡面語句counter[i].merge(counter[i-1])標記為下圖中執行的步驟6;
  7. 把第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原始碼剖析》侯捷;

        《STL原始碼剖析之list的sort函式實現