1. 程式人生 > >STL中關於list容器的sort函式詳解

STL中關於list容器的sort函式詳解

寫在前面

因為在stl中stl_algo中提供有sort函式,他的函式原型:

template <class RandomAccessIterator>
inline void sort(RandomAccessIterator first, RandomAccessIterator last);
template <class RandomAccessIterator, class Compare>
inline void sort(RandomAccessIterator first, RandomAccessIterator last,
                 Compare comp);

我們看到關於迭代器都是 RandomAccessIterator型別的,就是最高層次的指標,但是我們list中迭代器型別為:

typedef bidirectional_iterator_tag iterator_category;

由於bidirectional_iterator_tag是random_access_iterator_tag的子類,不會傳遞呼叫random_access_iterator_tag版本的排序函式,那麼關於list中的sort函式的實現就放在了stl_list中來進行實現了

struct random_access_iterator_tag : public bidirectional_iterator_tag {};

前提了解

splice函式:在sort函式中使用到了這種過載型別

//將list &中i位置上的節點,插入到this的position位置之前,list &中i位置上的節點消失
void splice(iterator position, list&, iterator i) {           //將i所指元素結合與position所指位置之前,position和i可指向同一個list
    iterator j = i;
    ++j;
    if (position == i || position == j) return;                 //位置發生重合或者要移動的位置已經在哦position前面
    transfer(position, i, j);
  }

swap函式,我們這裡使用一個栗子來看一下swap函式,因為我最後沒有找到swap的原函式在哪裡,函式可以實現將兩個list容器裡的所有內容互換(感覺只交換一下最後尾部的節點就好)

#include <iostream>
#include <list>

using namespace std;

int main()
{
	list<int> first(3, 100);  
	list<int> second(5, 200);

	first.swap(second);

	list<int>::iterator iter = first.begin();
	while (iter != first.end())
		cout << *iter++ << " ";
	cout << endl;

	list<int>::iterator iter_ = second.begin();
	while (iter_ != second.end())
		cout << *iter_++ << " ";
	cout << endl;
	system("pause");
}

merge函式:

//將list x的內容全部新增到當前操作的list中,並且按照順序進行插入
//前提是兩個list都是遞增排序的
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;
      transfer(first1, first2, ++next);                              
      first2 = next;
    }
    else
      ++first1;
  if (first2 != last2) transfer(last1, first2, last2);
}

這裡要著重說一下,因為在list中進行轉移操作,都是指標的移動,而不知重新建造空間,使用建構函式,所以在merge函式執行完之後,list x中的內容全都會消失,只剩最後的指向自身的節點。

這裡實現的sort函式有點類似於歸併排序,但是很神奇的是,表面上看不出什麼限制,但是他的list陣列counter,每個裡面的元素最多都是2^i個,這裡我覺得也是理解的難點。

sort函式程式

template <class T, class Alloc>  
void list<T, Alloc>::sort() {
  if (node->next == node || link_type(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].empty(),是用來對第i列最多2^i個元素控制的
      counter[i].merge(carry);                                       //清空carry,將它合併到counter[i]中
      carry.swap(counter[i++]);                                      //因為可能還會向下傳遞,要將內容在此放到carry中,清空counter[i],因為此時carry內容為空
    }
    carry.swap(counter[i]);                                          //carry不為空的話,那麼就將所有內容放到後面一個counter中
    if (i == fill) ++fill;                                           //說明前面的所有counter都進行了更新,那麼之前第fill中存在的元素個數為2^(fill+1),所以上面一句話中開一個新的counter來儲存,這裡來更新迴圈次數
  } 

  for (int i = 1; i < fill; ++i) counter[i].merge(counter[i-1]);     //最後合併所有的counter
  swap(counter[fill-1]);
}

為什麼counter[i]最多儲存2^i個內容?

        我認為這裡關鍵的位置就是!counter[i].empty()以及if(i==fill) ++fill,這裡因為每次carry都是攜帶一個節點來,第一次迴圈不再陳述,假設為第二次迴圈,首先和counter[0]合併,然後counter[0]中內容為2個,執行一次swap,那麼carry中內容為2個,counter[0]為空,但是上次counter[0]的大小為1,那麼第一次迴圈的時候會滿足i==fill條件,fill會變為1,但是counter[1] 為空,所以後面會將carry中的兩個元素放入counter[1]中,依次這樣向上推進,向counter[2]中存放資料的時候一定是counter[0]含有一個元素,counter[1]中含有兩個元素,當carry再次攜帶一個元素來的時候,會將所有元素放入counter[2]中,counter[2]中含有4個元素

因為函式merge執行完後,元素都是有序的,那麼在最後進行合併的時候會非常省時,基本上就是歸併排序的思想。

我們可以來模擬一下這個過程,使用連結串列:8 ,2 ,5, 1, 9,  7

第一次:carry = 8,但是counter[0]為空,那麼直接將carry放入到counter[0]中,i == fill,此時fill++

第二次:carry = 2,首先與counter[0]合併,counter[0] = {2,8},carry = NULL,此時counter[1] = fill,直接將所有元素放入counter[1]中,counter[0] = NULL,counter[1] = {2,8}

第三次:carry = 5,直接將5放入counter[0]中,因為counter[0],為空

第四次:carry = 1,與counter[0]合併,生成序列{1,5},再與counter[1]合併,生成序列{1,2,5,8},將序列放入counter[2]中,前面全部為空

第五次:carry = 9,直接放入到counter[0]中

第六次:carry = 7,與counter[0]合併,且因為counter[1] = NULL,所以將合併的序列放入counter[1]中,形成{7,9}

最後將counter[0]-counter[3]合併到counter[3]中,得到正確的序列

使用陣列簡化的歸併排序

參考部落格

《STL原始碼分析》侯捷著