1. 程式人生 > >STL中sort排序演算法原理

STL中sort排序演算法原理

1、所有sort演算法介紹

所有的sort演算法的引數都需要輸入一個範圍,[begin,end)。這裡使用的迭代器(iterator)都需是隨機迭代器(RadomAccessIterator), 也就是說可以隨機訪問的迭代器,如:it+n什麼的。(partition 和stable_partition 除外)

如果需要自己定義比較函式,可以把定義好的仿函式(functor)作為引數傳入。每種演算法都支援傳入比較函式。以下是所有STL sort演算法函式的名字列表:

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

其中nth_element 是最不易理解的,實際上,這個函式是用來找出第幾個。例如:找出包含7個元素的陣列中排在中間那個數的值,此時,我可能不關心前面,也不關心後面,我只關心排在第四位的元素值是多少。

2、sort中的比較函式

當需要按照某種特定方式進行排序時,需要給sort指定比較函式,否則程式會自動提供一個預設比較函式(仿函式)。

STL中提供的仿函式列表:

名稱              功能描述
equal_to           相等
not_equal_to      不相等
less
小於 greater 大於 less_equal 小於等於 greater_equal 大於等於

需要注意的是,這些函式不是都能適用於你的sort演算法,如何選擇,決定於你的應用。另外,不能直接寫入仿函式的名字,而是要寫其過載的()函式:

less<int>()
greater<int>()

當容器元素是一些標準型別(int,float,char)或者string時,可以直接使用這些函式模板。但如果是自己定義的型別或者需要按照其他方式排序,可以有兩種方法來達到效果:一種是自己寫比較函式。另一種是過載型別的’<’操作賦。
例子如下:

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

bool cmp(int x,int y)
{
    return x>y?true:false;
}

int main()
{
    int arr[5] = {3,2,5,8,4};
    for(int i=0;i<5;i++)
    {
        cout<<arr[i]<<"  ";
    }
    cout<<endl;
    //sort(arr,arr+5); //(1)預設從小到大排序
    //sort(arr,arr+5,cmp);//(2)用自己定義的cmp函式排序
    //sort(arr,arr+5,less<int>());//(3)用STL提供的仿函式。注意用法!
    sort(arr,arr+5,greater<int>());//(4)用STL提供的仿函式。注意用法!
    for(int i=0;i<5;i++)
    {
        cout<<arr[i]<<"  ";
    }
    cout<<endl;
    return 0;
}

3、sort演算法穩定性

你發現有sort和stable_sort,還有 partition 和stable_partition, 感到奇怪吧。其中的區別是,帶有stable的函式可保證相等元素的原本相對次序在排序後保持不變。或許你會問,既然相等,你還管他相對位置呢,也分不清楚誰是誰了?這裡需要弄清楚一個問題,這裡的相等,是指你提供的函式表示兩個元素相等,並不一定是一模一樣的元素。
例如,如果你寫一個比較函式:

bool less_len(const string &str1, const string &str2)
{
        return str1.length() < str2.length();
}

此時,”apple” 和 “winter” 就是相等的,如果在”apple” 出現在”winter”前面,用帶stable的函式排序後,他們的次序一定不變,如果你使用的是不帶”stable”的函式排序,那麼排序完後,”Winter”有可能在”apple”的前面。

4、各種排序函式

4.1 全排序: sort,stable_sort

全排序即把所給定範圍所有的元素按照大小關係順序排列。用於全排序的函式有:sort和stable_sort。

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

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

template <class RandomAccessIterator>
void stable_sort(RandomAccessIterator first, RandomAccessIterator last);

template <class RandomAccessIterator, class StrictWeakOrdering>
void stable_sort(RandomAccessIterator first, RandomAccessIterator last, 
StrictWeakOrdering comp);

在第1,3種形式中,sort和stable_sort都沒有指定比較函式,系統會預設使用operator< 對區間[first,last)內的所有元素進行排序。第2, 4種形式,你可以隨意指定比較函式,應用更為靈活一些。

sort採用的是成熟的”快速排序演算法”(目前大部分STL版本已經不是採用簡單的快速排序,而是結合內插排序演算法)。可以保證很好的平均效能、複雜度為n*log(n),由於單純的快速排序在理論上有最差的情況,效能很低,其演算法複雜度為n*n,但目前大部分的STL版本都已經在這方面做了優化,因此你可以放心使用。
stable_sort採用的是”歸併排序”,分派足夠記憶體是,其演算法複雜度為n*log(n), 否則其複雜度為n*log(n)*log(n),其優點是會保持相等元素之間的相對位置在排序前後保持一致。

4.2 區域性排序

區域性排序其實是為了減少不必要的操作而提供的排序方式。其函式原型為:partial_sort和partial_sort_copy。

template <class RandomAccessIterator>
void partial_sort(RandomAccessIterator first, 
RandomAccessIterator middle,
RandomAccessIterator last);

template <class RandomAccessIterator, class StrictWeakOrdering>
void partial_sort(RandomAccessIterator first,
RandomAccessIterator middle,
RandomAccessIterator last, 
StrictWeakOrdering comp);

template <class InputIterator, class RandomAccessIterator>
RandomAccessIterator partial_sort_copy(InputIterator first, InputIterator last,
RandomAccessIterator result_first,
RandomAccessIterator result_last);

template <class InputIterator, class RandomAccessIterator, 
class StrictWeakOrdering>
RandomAccessIterator partial_sort_copy(InputIterator first, InputIterator last,
RandomAccessIterator result_first,
RandomAccessIterator result_last, Compare comp);

理解了sort 和stable_sort後,再來理解partial_sort 就比較容易了。先看看其用途: 班上有10個學生,我想知道分數最低的5名是哪些人。如果沒有partial_sort,你就需要用sort把所有人排好序,然後再取前5個。現在你只需要對分數最低5名排序:

partial_sort(vect.begin(), vect.begin()+5, vect.end(),less<student>());

這樣的好處知道了嗎?當資料量小的時候可能看不出優勢,如果是100萬學生,我想找分數最少的5個人……
partial_sort採用的堆排序(heapsort),它在任何情況下的複雜度都是n*log(n). 如果你希望用partial_sort來實現全排序,你只要讓middle=last就可以了。

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

4.3 nth_element 指定元素排序

nth_element一個容易看懂但解釋比較麻煩的排序。用例子說會更方便:
班上有10個學生,我想知道分數排在倒數第4名的學生。
如果要滿足上述需求,可以用sort排好序,然後取第4位(因為是由小到大排), 更聰明的朋友會用partial_sort, 只排前4位,然後得到第4位。其實這是你還是浪費,因為前兩位你根本沒有必要排序,此時,你就需要nth_element:

template <class RandomAccessIterator>
void nth_element(RandomAccessIterator first, RandomAccessIterator nth,
RandomAccessIterator last);

template <class RandomAccessIterator, class StrictWeakOrdering>
void nth_element(RandomAccessIterator first, RandomAccessIterator nth,
RandomAccessIterator last, StrictWeakOrdering comp);

對於上述例項需求:

nth_element(vect.begin(), vect.begin()+3, vect.end(),less<student>());

為什麼是begin()+3而不是+4? begin()是第一個,begin()+1是第二個,… begin()+3當然就是第四個了。

另外,partition 和stable_partition,好像這兩個函式並不是用來排序的,’分類’演算法,會更加貼切一些。partition就是把一個區間中的元素按照某個條件分成兩類。如果使用的是stable_partition, 元素之間的相對次序是沒有變.