STL sort解析

從上學接觸到編程開始,到工做了幾年。有關排序算法的內容反反覆覆有接觸,可是要說每一種排序算法的細節都能說清,那就有難度了。算法

一來算法難度有深有淺。有比較簡單的冒泡,插入,也有複雜的堆排序,快排這些。編程

二來排序算法實際上成型已久,是個套路的東西。dom

三來平時工做用不到每一種算法。實際數據很少的時候用冒泡足夠了。數據多時,用快排足夠了。函數

關聯容器內部有自動排序,序列容器中只有vector,deque的迭代器是隨機位置迭代器(RandomAccessIterators),所以只有vector和deque能使用std::sort()。ui

後面講快排會講爲何sort()必須是隨機位置迭代器。spa

 

今天看到一個實際工做中常常用到的例子,可是平時沒有注意它的原理。指針

這個例子有助於加深對排序的理解。即是STL裏面的sort()算法。code

 

sort()是有策略進行排序。具體規則以下:blog

// 1.數據量大時Quick Sort(快排)
// 2.分段時數據量小於門檻,改用Inserition Sort(插入排序)
// 3.遞歸層次太深,改用Heap Sort(堆排序)排序

裏面使用了3種排序。既有複雜度O(n*n)的插入排序,也有複雜度O(nlogn)的快排和堆排序。

先分別來看這3種排序。從簡單的插入排序開始吧。

 

插入排序

假設待排序的序列是a[0..n - 1],總共n個數據。

1)a[0]是已排序的序列,a[i, n - 1]是未排序的序列。令i = 1。

2)取a[i]插入到有序序列合適位置,使得新序列有序。

3)重複2步驟,直到i == n - 1。

外層循環是從1到n-1,內層循環是找插入位置,無需遍歷全部位置,當a[j - 1] < a[j]便可中止,不然繼續找位置,同時交換a[j - 1],a[j]的位置。

void InsertSort(int *a, int count)
{
    if (count <= 1)
    {
        return;
    }

    for (int i = 1; i < count; ++i)
    {
        for (int j = i; j >= 0 && a[j - 1] > a[j]; j--)
        {
            swap(a[j - 1], a[j]);
        }
    }
}

有兩層循環,算法複雜度是O(n*n)  。穩定不穩定,取決於中間的判斷條件。a[j - 1] > a[j]則是穩定的,條件變成a[j - 1] >= a[j]則是不穩定的。

 

快速排序

快排的核心是分治法

1.若是序列S中數據的數量是0或者1。結束。

2.取S中的任意一個數字,看成樞紐v

3.將S分割成兩段L,R,使得L中的元素所有小於等於v,R中的元素所有大於等於v。

4.將L,R遞歸執行快排。

其中第2步,選取一個數字作樞紐。能夠是任意的數字,一般用的是median-of-three。從首尾和中間數中,選擇中值。

好比序列:1,4,5,7,9,8,3。那麼1,7,3中選3做爲樞紐來對序列進行分割。

爲了能快速取出中間元素,迭代器必須支持隨機位置(RandomAccess)。

三個數取中值的函數,只有畫線段標位置,容易看出a,b,c三者的大小關係。

int median(int a, int b, int c)
{
    if (a < b)
    {
        if (b < c)
        {
            return b; // a < b < c
        }
        else
        {
            if (a < c)
            {
                return c;    // a < c <= b
            }
            else
            {
                return a;    // c <= a < b
            }
        }
    }
    else
    {
        if (b < c)
        {
            if (a < c)
            {
                return a;    // b <= a < c
            }
            else
            {
                return c;    // b < c < a
            }
        }
        else
        {
            return b;        // c <= b <= a
        }
    }
}

再來看分割算法:

令first指向頭元素,last指向尾元素。first向尾端移動,last向頭端移動。

當first大於等於樞紐時,中止。當last小於等於樞紐時,中止。

檢查first,last是否交錯(first >= last),若是交錯返回first。若是沒有交錯,first,last各自向中央移動一個位置。

重複上面的步驟。

最終返回的first是右半部分的起點。左半部分所有小於等於樞紐,右半部分所有大於等於樞紐。

來看代碼,RandomAccessIter能夠理解成一個普通指針。

int* partition(int *first, int *last, int pivot)
{
    while (true)
    {
        while (*first < pivot) first++;
        while (*last > pivot) last--;
        if (first >= last) return first;
        swap(*first, *last);
        first++;
        last--;
    }
}

 

 

引用:

1.《STL源碼剖析》

2.《MoreWindows白話經典算法之七大排序第2版》

相關文章
相關標籤/搜索