C++ STL之排序函數詳解

排序函數:

函數名 功能描述
sort 對給定區間所有元素進行排序
stable_sort 對給定區間所有元素進行穩定排序 
partial_sort 對給定區間所有元素部分排
partial_sort_copy 對給定區間複製並排序
nth_element 找出給定區間的某個位置對應的元素
is_sorted 判斷一個區間是否已經排好序
partition 使得符合某個條件的元素放在前面 
stable_partition 相對穩定的使得符合某個條件的元素放在前面
 
 
 
 
 
 
 
 
 
 
 
 




sort:

要使用此函數只需用#include <algorithm> sort即可使用,
語法描述爲:
sort(begin,end),表示一個範圍,例如:
int main()
{
	int a[]={2,4,1,23,5,76,0,43,24,65}; 
	int i; 
	for(i=0;i<10;i++)
		cout<<a[i]<<" ";
	cout<<endl;
	sort(a,a+10);
	for(i=0;i<10;i++)
		cout<<a[i]<<" ";
	return 0;
}
輸出結果:
2 4 1 23 5 76 0 43 24 65
0 1 2 4 5 23 24 43 65 76
---------------------------------
輸出結果將是把數組a按升序排序,說到這裏可能就有人會問怎麼樣用它降序排列呢?這就是下一個討論的內容.
一種是自己編寫一個比較函數來實現,接着調用三個參數的sort:sort(begin,end,compare)就成了。
對於list容器,這個方法也適用,把compare作爲sort的參數就可以了,即:sort(compare).
1)自己編寫compare函數:
bool compare(int a,int b)
{
      return a>b;   //降序排列,如果改爲return a<b,則爲升序
}
輸出結果
2 4 1 23 5 76 0 43 24 65
76 65 43 24 23 5 4 2 1 0
---------------------------------

2)更進一步,讓這種操作更加能適應變化。也就是說,能給比較函數一個參數,用來指示是按升序還是按降序排,這回輪到函數對象出場了。
爲了描述方便,我先定義一個枚舉類型EnumComp用來表示升序和降序。很簡單:
        enum Enumcomp{ASC,DESC};
然後開始用一個類來描述這個函數對象。它會根據它的參數來決定是採用“<”還是“>”。
class compare
{
      private:
            Enumcomp comp;
      public:
            compare(Enumcomp c):comp(c) {};
      bool operator () (int num1,int num2)
         {
            switch(comp)
              {
                 case ASC:
                        return num1<num2;
                 case DESC:
                        return num1>num2;
              }
          }
};
接下來使用 sort(begin,end,compare(ASC)實現升序,sort(begin,end,compare(DESC)實現降序

3)其實對於這麼簡單的任務(類型支持“<”、“>”等比較運算符),完全沒必要自己寫一個類出來。
標準庫裏已經有現成的了,就在functional裏,include進來就行了。functional提供了一堆基於模板的比較函數對象。
它們是(看名字就知道意思了):equal_to<Type>not_equal_to<Type>、greater<Type>、greater_equal<Type>、less<Type>、less_equal<Type>
對於這個問題來說,greater和less就足夠了,直接拿過來用:
升序:sort(begin,end,less<data-type>());
降序:sort(begin,end,greater<data-type>()).
int main()
{
	int a[]={2,4,1,23,5,76,0,43,24,65}; 
	int i; 
	for(i=0;i<10;i++)
		cout<<a[i]<<" ";
	cout<<endl;
	sort(a,a+10,greater<int>());  //升序爲less<int>() 
	for(i=0;i<10;i++)
		cout<<a[i]<<" ";
	return 0;
}

4)既然有迭代器,如果是string 就可以使用反向迭代器來完成逆序排列,程序如下:
int main()
{
     string str("cvicses");
     string s(str.rbegin(),str.rend());
     cout << s <<endl;
     return 0;
}



stable_sort:

stable_sort 函數與sort函數用法相同。這兩個函數的原理都是快速排序,時間複雜度在所有排序中最低,爲O(nlog2n) ;

區別是stable_sort 是穩定排序,也就是stable_sort函數遇到兩個數相等時,不對其交換順序;

這個應用在數組裏面不受影響,當函數參數傳入的是結構體時,會發現兩者之間的明顯區別;


partial_sort:

paitical_sort的原理是堆排序!
首先創建一個堆,得到最大值。如果要得到次大值,就將頭結點去掉,即調用pop_heap(),此時的頭結點就是次大值,可以這樣依次得到最大或者最小的幾個值!
函數原型有:
partial_sort(beg,mid,end)
partial_sort(beg,mid,end,comp)
函數作用:
對mid-beg個元素進行排序,也就是說,如果mid-beg等於42,則該函數將有序次序中的最小值元素放在序列中
的前42個位置。partial_sort完成之後,從beg到mid(但不包括mid)範圍內的元素時有序的,已排序範圍內沒有
元素大於mid之後的元素。未排序元素之間的次序是未指定的。
例如:
有一個賽跑成績的集合,我們想知道前三名的成績但並不關心其他名次的次序,可以這樣對這個序列進行排序。
      partial_sort(scores.begin(),scores.begin()+3,scores.end());
#include <vector>  
#include <iterator>  
#include <iostream>  
#include <algorithm>  
#include <functional>  
#include <cstdlib>  
#include <time.h>  
using namespace std;  
  
int rand_int()  
{  
    return rand()%100;  
}  
  
void print(vector<int> &v,const char* s)  
{  
    cout<<s<<endl;  
    copy(v.begin(),v.end(),ostream_iterator<int>(cout," "));  
    cout<<endl;  
}  
  
bool cmp(int &a, int &b)  
{  
    if(a>b)  
        return true;  
    return false;  
}  
  
class compare{  
public:  
    bool operator()(const int &a,const int &b)  
    {  
        if(a<b)  
            return true;  
        return false;  
    }  
};  
  
int main()  
{  
    srand(time(NULL));  
    vector<int> v;  
    generate_n(back_inserter(v),10,rand_int);  
    print(v,"產生10個隨機數");  
  
    partial_sort(v.begin(),v.begin()+4,v.end());  
    print(v,"局部遞增排序");  
  
    partial_sort(v.begin(),v.begin()+4,v.end(),cmp);  
    print(v,"局部遞減排序");  
  
    partial_sort(v.begin(),v.begin()+4,v.end(),compare());  
    print(v,"局部遞增排序");  
  
    return 0;  
}
測試結果:

產生10個隨機數
69 54 33 50 13 52 54 61 29 5
局部遞增排序
5 13 29 33 69 54 54 61 52 50
局部遞減排序
69 61 54 54 5 13 29 33 52 50
局部遞增排序
5 13 29 33 69 61 54 54 52 50

----------------------------------------

通過程序可以看到兩次局部遞增排序並不相同,因爲partial_port不是穩定排序算法。在只需要最大或最小的幾個值時,partial_port比其他排序算法快。



partial_sort_copy:

原型:

 template<class InputIterator, RandomAccessIterator> inline
   RandomAccessIterator partial_sort(InputIterator first1,
                                     InputIterator last1,
                                     RandomAccessIterator first2,
                                     RandomAccessIterator last2)
說明:

partial_sort_copy其實是copy和partial_sort的組合。被排序(被複制)的數量是[first, last)和[result_first, result_last)中區間較小的那個。如果[result_first, result_last)區間大於[first, last)區間,那麼partial_sort相當於copy和sort的組合。

示例代碼:

#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>
using namespace std;

void main()
{
    const int VECTOR_SIZE = 8 ;

    // Define a template class vector of int
    typedef vector<int, allocator<int> > IntVector ;

    //Define an iterator for template class vector of strings
    typedef IntVector::iterator IntVectorIt ;

    IntVector Numbers(VECTOR_SIZE) ;
    IntVector Result(4) ;

    IntVectorIt start, end, it ;

    // Initialize vector Numbers
    Numbers[0] = 4 ;
    Numbers[1] = 10;
    Numbers[2] = 70 ;
    Numbers[3] = 30 ;
    Numbers[4] = 10;
    Numbers[5] = 69 ;
    Numbers[6] = 96 ;
    Numbers[7] = 7;

    start = Numbers.begin() ;   // location of first
                                // element of Numbers

    end = Numbers.end() ;       // one past the location
                                // last element of Numbers

    cout << "Before calling partial_sort_copy\n" << endl ;

    // print content of Numbers
    cout << "Numbers { " ;
    for(it = start; it != end; it++)
        cout << *it << " " ;
    cout << " }\n" << endl ;

    // sort the smallest 4 elements in the Numbers
    // and copy the results in Result
    partial_sort_copy(start, end, Result.begin(), Result.end()) ;

    cout << "After calling partial_sort_copy\n" << endl ;

    cout << "Numbers { " ;
    for(it = start; it != end; it++)
        cout << *it << " " ;
    cout << " }\n" << endl ;

    cout << "Result { " ;
    for(it = Result.begin(); it != Result.end(); it++)
        cout << *it << " " ;
    cout << " }\n" << endl ;
}
測試結果:

Before calling partial_sort_copy

Numbers { 4 10 70 30 10 69 96 7  }

After calling partial_sort_copy

Numbers { 4 10 70 30 10 69 96 7  }

Result { 4 7 10 10  }


-------------------------------------------------


nth_element:

簡單的說nth_element算法僅排序第nth個元素(從0開始的索引)

如iarray [first,last) 元素區間

排序後  iarray[nth] 就是第nth大的元素(從0開始)

要注意的是[first,nth) [nth,last)內 的大小循序還不一定

只能確定iarray[nth]是第nth大的元素。

當然 [first,nth) 肯定是不大於 [nth,last)的。

簡單測試代碼如下

要注意的是,此函數只是將第nth大的元素排好了位置,但並沒有返回值

所以要知道第nth大的元素 還得進行一步,cout<<iarray[nth]<<endl; nth既那個位子

<pre name="code" class="cpp">#include <iostream>
#include <algorithm>
using namespace std;

int main()
{
    int iarray[]={5,6,15,89,7,2,1,3,52,63,12,64,47};
    int len=sizeof(iarray)/sizeof(int);
    int i;
    for(i=0;i<len;i++)
       cout<<iarray[i]<<" ";
    nth_element(iarray,iarray+6,iarray+len);         //排序第6個元素
        cout<<endl;
    for(i=0;i<len;i++)
       cout<<iarray[i]<<" ";
    cout<<endl;
    cout<<"第6-th個元素: "<<iarray[6]<<endl;
}

測試結果:

5 6 15 89 7 2 1 3 52 63 12 64 47
5 3 1 2 6 7 12 15 47 63 52 64 89
第6-th個元素: 12

---------------------------------------------


is_sorted:

is_sorted用於判斷一個區間是否已經有序,和排序一樣,可以自己定義比較函數。
但是,正如文章題目所說的一樣,is_sorted是SGI STL的擴展,在現在的GCC下並沒有(VS下也沒有),所以,爲了保證代碼的可移植性,最好不要使用這個函數。


Partition:

作用:
Partition 函數用於返回一個 Variant (String),指定一個範圍,在一系列計算的範圍中指定的數字出現在這個範圍內。
函數的語法格式:
Partition(number, start, stop, interval)
Partition 函數的語法含有下面這些命名參數:部分描述
number必要參數。整數,在所有範圍中判斷這個整數是否出現。
        start必要參數。整數,數值範圍的開始值,這個數值不能小於 0。
        stop必要參數。整數,數值範圍的結束值,這個數值不能等於或小於 start。
說明:
Partition 函數會標識 number 值出現的特定範圍,並返回一個 Variant (String) 來描述這個範圍。
Partition 函數在查詢中是最有用的。可以創建一個選擇查詢顯示有多少定單落在幾個變化的範圍內,例如,定單數從 1 到 1000、1001 到 2000,以此類推。


解決問題:

快速排序算法裏的partition函數用來解決這樣一個問題:給定一個數組arr[]和數組中任意一個元素a,重排數組使得a左邊都小於它,右邊都不小於它。

// arr[]爲數組,start、end分別爲數組第一個元素和最後一個元素的索引
 // povitIndex爲數組中任意選中的數的索引
int partition(int arr[], int start, int end, int pivotIndex)
{
    int pivot = arr[pivotIndex];
    swap(arr[pivotIndex], arr[end]);
    int storeIndex = start;
    //這個循環比一般的寫法簡潔高效,呵呵維基百科上看到的
    for(int i = start; i < end; ++i) {
        if(arr[i] < pivot) {
            swap(arr[i], arr[storeIndex]);
            ++storeIndex;
        }
    }
    swap(arr[storeIndex], arr[end]);
    return storeIndex;
}


stable_partition:

stable_partition 就是partition排序算法的穩定算法,區別於 sort和stable_sort的區別是一樣



選擇合適的排序函數

爲什麼要選擇合適的排序函數?可能你並不關心效率(這裏的效率指的是程序運行時間), 或者說你的數據量很小, 因此你覺得隨便用哪個函數都無關緊要。

其實不然,即使你不關心效率,如果你選擇合適的排序函數,你會讓你的代碼更容易讓人明白,你會讓你的代碼更有擴充性,逐漸養成一個良好的習慣,很重要吧 。

如果你以前有用過C語言中的qsort, 想知道qsort和他們的比較,那我告訴你,qsort和sort是一樣的,因爲他們採用的都是快速排序。從效率上看,以下幾種sort算法的是一個排序,效率由高到低(耗時由小變大):

  1. partion
  2. stable_partition
  3. nth_element
  4. partial_sort
  5. sort
  6. stable_sort
Effective STL的文章,其中對如何選擇排序函數總結的很好:
  • 若需對vector, string, deque, 或 array容器進行全排序,你可選擇sort或stable_sort;
  • 若只需對vector, string, deque, 或 array容器中取得top n的元素,部分排序partial_sort是首選.
  • 若對於vector, string, deque, 或array容器,你需要找到第n個位置的元素或者你需要得到top n且不關係top n中的內部順序,nth_element是最理想的;
  • 若你需要從標準序列容器或者array中把滿足某個條件或者不滿足某個條件的元素分開,你最好使用partition或stable_partition;
  • 若使用的list容器,你可以直接使用partition和stable_partition算法,你可以使用list::sort代替sort和stable_sort排序。若你需要得到partial_sort或nth_element的排序效果,你必須間接使用。正如上面介紹的有幾種方式可以選擇。
總之記住一句話: 如果你想節約時間,不要走彎路, 也不要走多餘的路!




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章