1. 程式人生 > >STL 原始碼分析《1》---- list 歸併排序的 迭代版本, 神奇的 STL list sort

STL 原始碼分析《1》---- list 歸併排序的 迭代版本, 神奇的 STL list sort

最近在看 侯捷的 STL原始碼分析,發現了以下的這個list 排序演算法,乍眼看去,實在難以看出它是歸併排序。

平常大家寫歸併排序,通常寫的是 遞迴版本。。為了效率的考慮,STL庫 給出瞭如下的 歸併排序的迭代版本.

1. MergeSort 的遞迴版本

考慮如下的例子,對一個長度為 8 的陣列進行歸併排序。


2. 迭代版本

而 迭代版本恰恰相反,是 從下往上。

為了便於明白演算法的思想,我模仿 STL 庫的 list sort 重新寫了歸併排序。文章最後給出了 STL 的原始碼。

// copyright @ L.J.SHOU Feb.19, 2014
// my list-sort mimicing STL's list sort 
#include <iostream>
using namespace std;

struct ListNode{
  int val;
  ListNode *next;
  ListNode(int x)
    :val(x), next(NULL){}
};

// merge two sorted lists into a sorted list
ListNode* merge(ListNode* &, ListNode* &); 

void ListSort(ListNode* & list)
{
  if(list == NULL || list->next == NULL) /* 空或者1個元素的連結串列 */
    return;

  ListNode *carry(NULL);
  ListNode *counter[64] = {NULL}; /* 64個list, 中介資料中轉站,用於模擬遞迴 */
  int fill = 0;
  while(list) {
    /* insert first node of list into front of carry */
    ListNode *node = list; list = list->next;
    node->next = carry;
    carry = node;

    int i = 0;
    while(i < fill && counter[i]) {
      counter[i] = merge(counter[i], carry);
      carry = NULL; /* after merge, carry is now empty */
      swap(carry, counter[i++]);
    }
    swap(carry, counter[i]);
    if(i == fill) ++fill;
  }

  for(int i = 1; i < fill; ++i){ /* 將 64 個 lists 歸併成一個 list */
    counter[i] = merge(counter[i], counter[i-1]);
    counter[i-1] = NULL;
  }
  swap(list, counter[fill-1]); 
}

程式碼分析:上述程式碼中 開了一個長度為 64 的連結串列陣列。

第 i 個連結串列長度最長是 2^(i+1)。 

演算法的執行過程如下:

對 8 1 4 5 進行排序。


比較 遞迴與迭代的演算法過程,可以發現兩者就是互逆的過程。

現在相比大家對演算法有了一個全面的認識。STL 庫正是 利用 長度為 64個連結串列,實現了上圖中的演算法。

這個演算法能夠排序的總數是 2^65-2 個數,應該夠用了。

SGI STL 的原始碼(選自 STL 原始碼分析)如下:

// list 不能使用 STL 演算法 sort ()
// 因為 STL 演算法 sort() 只接受 RandomAccessIterator
// 本函式採用 mergesort
template <class T, class Alloc>
void list<T, Alloc>::sort () {
  // 以下判斷,如果是空連結串列, 或僅有一個元素,就不進行任何操作
  if (node->next == node || link_type(node->next)->next == node)
  return;
  
  // 一些新的 lists, 作為中介資料存放區
  list<T, Alloc> carry;
  list<T, Alloc> counter[64];
  int fill = 0;
  while (!empty()) {
    carry.splice(carry.begin(), *this, begin());
    in i = 0;
    while(i < fill && !counter[i].empty()) {
      counter[i].merge(carry);
      carry.swap(counter[i++]);
    }
carry.swap(counter[i]);
    if (i == fill) ++fill;
  }


  for(int i = 1; i < fill; ++i)
    counter[i].merge(counter[i-1]);
  swap(counter[fill-1]);
}

參考:1. STL原始碼分析, 侯捷