處理海量數據的高級排序之——希爾排序(C++)


希爾算法簡介                                                                                                                                       

常見排序算法一般按平均時間復雜度分為兩類:
O(n^2):冒泡排序、選擇排序、插入排序
O(nlogn):歸並排序、快速排序、堆排序

簡單排序時間復雜度一般為O(n^2),如冒泡排序、選擇排序、插入排序等
高級排序時間復雜度一般為O(nlogn),如歸並排序、快速排序、堆排序。
兩類算法隨着排序集合越大,效率差異越大,在數量規模1W以內的排序,兩類算法都可以控制在毫秒級別內完成,但當數量規模達到10W以上后,簡單排序往往需要以幾秒、分甚至小時才能完成排序;而高級排序仍可以在很短時間內完成排序。

今天所講的希爾排序是從插入排序進化而來的排序算法,也屬於高級排序,只不過時間復雜度為O(n^1.5),略遜於其他幾種高級排序,但也遠遠優於O(n^2)的簡單排序了。希爾排序沒有明顯的短板,不像歸並排序需要大量的輔助空間,也不像快速排序在最壞的情況下和平均情況下執行效率差別比較大,且代碼簡單,易於實現。
一般在面對中等規模數量的排序時,可以優先使用希爾排序,當發現執行效率不理想時,再改用其他高級排序。

實際測試做了各個高級排序對大數據量排序的耗時對比(沒錯,冒泡排序就是拿出來搞笑的..),可以看到希爾排序的效率比其他幾種O(nlogn)的高級排序差了幾倍了,1W個數以下規模的排序這種差異還可以忽略不計的;但當數據規模超過10W以上時,可以很明顯看到希爾排序效率跟其他高級排序差了很多。這種效率差距隨着數據規模變大,會越來越大。

總結來說:希爾排序對中等大小規模數據表現良好,對規模非常大的數據排序不是最優選擇。

算法穩定性:不穩定

基本概念                                                                                                                                        

什么是增量?
增量也稱步長。做個形象比喻:一個書架放着一排書,現在我們每數X本書就拿出一本,這個變量X就稱之為增量。

希爾排序原理                                                                                                                                  
教科書式表述:
先取一個小於n的整數d1作為第一個增量,把文件的全部記錄分組。所有距離為d1的倍數的記錄放在同一個組中。先在各組內進行直接插入排序;然后,取第二個增量d2<d1重復上述的分組和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有記錄放在同一組中進行直接插入排序為止。
大白話表述:
仍然拿上述例子做比喻:一個書架放着一排書,現在從第一本書起每數X本書,就在那本書上貼紅色貼紙,貼完紅色貼紙后,再次從第二本書起每數X本書就貼上藍色貼紙(跟之前顏色不同即可),重復貼紙過程,直到所有書都貼滿貼紙。接着對有相同顏色貼紙的書做插入排序。然后撕掉所有貼紙后重新對書進行貼紙,這次則每數Y本書就貼紙(Y>X),所有書貼滿后再進行插入排序。重復貼紙排序、貼紙排序這個過程,直到最后每數1本書就貼紙(也就是每本書都貼同樣顏色貼紙),再插入排序為止。

過程圖示                                                                                                                                        

實現代碼                                                                                                                                        

#include "stdafx.h"
#include <iostream>
#include <ctime>
using namespace std;

int a[100000];

#define BEGIN_RECORD            \
{                                \
clock_t ____temp_begin_time___;    \
____temp_begin_time___=clock();

#define END_RECORD(dtime)        \
dtime=float(clock()-____temp_begin_time___)/CLOCKS_PER_SEC;\
}

/*
    希爾插入排序過程
    a - 待排序數組
    s - 排序區域的起始邊界
    delta - 增量
    len - 待排序數組長度
*/
void shellInsert(int a[], int s, int delta, int len)
{
    int temp, i, j, k;
    for (i = s + delta; i < len; i += delta)
    {
        for(j = i - delta; j >= s; j -= delta)
            if(a[j] < a[i])break;

        temp = a[i];
        for (k = i; k > j; k -= delta)
        {
            a[i] = a[i - delta];
        }
        a[k + delta] = temp;
    }
}

/*
 希爾排序
 a - 待排序數組
 len - 數組長度
*/
void shellSort(int a[], int len)
{
    int temp;
    int delta;    //增量

    //Hibbard增量序列公式
    delta = (len + 1)/ 2 - 1;

    while(delta > 0)    //不斷改變增量,對數組迭代分組進行直接插入排序,直至增量為1
    {
        for (int i = 0; i < delta; i++)
        {
            shellInsert(a, i, delta, len);
        }
        delta = (delta + 1)/ 2 - 1;
    }

}

void shellSort2(int a[], int len)
{
    int temp;
    int delta;    //增量

    //希爾增量序列公式
    delta = len / 2;

    while(delta > 0)
    {
        for (int i = 0; i < delta; i++)
        {
            shellInsert(a, i, delta, len);
        }
        delta /= 2;
    }

}


void printArray(int a[], int length)
{
    cout << "數組內容:";
    for(int i = 0; i < length; i++)
    {
        if(i == 0)
            cout << a[i];
        else
            cout << "," << a[i];

    }
    cout << endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
    float tim;
    int i;

    for (i = 0; i < 1000000; i++)
    {
        a[i] = int(rand() % 100000);
    }

    cout << "10W個數的希爾排序:" <<  endl;

    for (i = 0; i < 1000000; i++)
    {
        a[i] = int(rand() % 100000);
    }
    BEGIN_RECORD
    
    shellSort2(a, sizeof(a)/sizeof(int));

    END_RECORD(tim)
    
    cout << "希爾增量序列運行時間:" << tim << "s" <<  endl;

    for (i = 0; i < 1000000; i++)
    {
        a[i] = int(rand() % 100000);
    }
    BEGIN_RECORD
    
    shellSort(a, sizeof(a)/sizeof(int));

    END_RECORD(tim)
    
    cout << "Hibbard增量序列運行時間:" << tim << "s" <<  endl;

    system("pause");
    return 0;
}
View Code

 

希爾排序的效率                                                                                                                              
希爾排序的增量序列是影響希爾排序效率的最關鍵因素,至今為止還沒有一個最完美的增量序列公式。可究竟應該選取什么樣的增量才是最好,目前還是一個數學難題。

看如下兩個增量序列:

n/2、n/4、n/8...1

1、3、7...2^k-1

第一個序列稱為希爾增量序列,使用希爾增量時,希爾排序在最壞情況下的時間復雜度為O(n*n)。

第二個序列稱為Hibbard增量序列,使用Hibbard增量時,希爾排序在最壞情況下的時間復雜度為O(n^3/2)。

 

 

對10W個無序數分別以希爾增量序列、Hibbard增量序列進行希爾排序,耗時比較如圖所示,在10W量級的排序,Hibbard增量序列比希爾增量序列的效率已經高了幾倍。盡管Hibbard並不是最完美的增量序列,但表現已經非常不錯,因此在實際應用中希爾排序多采用Hibbard增量序列。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM