九種排序算法分析與實現

九種排序算法分析與實現

  簡介:總的來說,排序算法共有八大類,即冒泡排序、選擇排序、快速排序、插入排序、希爾排序、歸併排序、基數排序以及堆排序等,本文另外也介紹了桶排序。編程語言使用了C/C++(其實主要用的C),3個經常出現的函數形參,arr - 待排序數組名(首元素地址)、bgn - 待排序數組起始排序元素位置(有時我們僅需要對數組中某一段元素進行排序,但通常bgn = 0,即arr首元素位置)、end - 待排序數組截止排序尾元素的下一個位置(即該位置無效,不可引用)。文中均已升序爲例,降序原理相同。

  時間複雜度:描述該算法在處理大量數據時,總的來說其時間效率的參考;  穩定性:描述算法對原始序列處理前後,該序列相等大小的元素前後位置是否發生改變

  兩個常用的函數:1、獲取數組最大元素值; 2、交換兩個整形元素。代碼如下:

複製代碼

//獲取整形數組的最大值
//NOTE: 默認arr非空
int getMaxValue(const vector<int> &arr)
{
    int max = INT_MIN;
    for (auto val : arr)
    {
        if (val > max)
            max = val;
    }
    return max;
}

/*交換兩個整形值*/
void mySwap(int *pa, int *pb)
{
    int tmp = *pa;
    *pa = *pb;
    *pb = tmp;
}

複製代碼

 1、冒泡排序 - 依次比較相鄰兩元素,若前一元素大於後一元素則交換之,直至最後一個元素即爲最大;然後重新從首元素開始重複同樣的操作,直至倒數第二個元素即爲次大元素;依次類推。如同水中的氣泡,依次將最大或最小元素氣泡浮出水面。

時間複雜度:O(N2)   穩定性:穩定

複製代碼

/*冒泡排序*/
void bubbleSort(vector<int> &arr, int bgn, int end)
{
    /*isLoop用於指示依次遍歷中是否發生元素交換,若沒有,則已是有序數列,退出即可*/
    bool isLoop = true;
    for (int i = end; true == isLoop && i > bgn; --i)
    {
        isLoop = false;
        for (int j = bgn + 1; j < i; ++j)
        {
            if (arr[j] < arr[j - 1])
            {
                mySwap(&arr[j], &arr[j - 1]);
                isLoop = true;
            }
        }
    }
}

複製代碼

2、選擇排序 - 首先初始化最小元素索引值爲首元素,依次遍歷待排序數列,若遇到小於該最小索引位置處的元素則刷新最小索引爲該較小元素的位置,直至遇到尾元素,結束一次遍歷,並將最小索引處元素與首元素交換;然後,初始化最小索引值爲第二個待排序數列元素位置,同樣的操作,可得到數列第二個元素即爲次小元素;以此類推。

時間複雜度:O(N2)   穩定性:不穩定

複製代碼

/*選擇排序*/
void selectSort(vector<int> &arr, int bgn, int end)
{
    for (int i = bgn; i < end; ++i)
    {
        int minIndex = i;
        for (int j = i + 1; j < end; ++j)
        {
            if (arr[j] < arr[minIndex])
                minIndex = j;
        }
        if (minIndex != i)
            mySwap(&arr[i], &arr[minIndex]);
    }
}

複製代碼

3、快速排序 - (類似於選擇排序的定位思想)選一基準元素,依次將剩餘元素中小於該基準元素的值放置其左側,大於等於該基準元素的值放置其右側;然後,取基準元素的前半部分和後半部分分別進行同樣的處理;以此類推,直至各子序列剩餘一個元素時,即排序完成(類比二叉樹的思想,from up to down)

時間複雜度:O(NlogN)   穩定性:不穩定

複製代碼

/*快排*/
void quickSort(vector<int> &arr, int bgn, int end)  //arr must be the reference of real param
{
    //數組arr空or僅有一個元素則退出
    if (bgn >= end - 1)
        return;

    int lindex = bgn;
    int rindex = end - 1;
    int std = arr[lindex];
    while (lindex < rindex)
    {
        while (lindex < rindex)
        {
            if (arr[rindex] < std)
            {
                arr[lindex++] = arr[rindex];
                break;
            }
            --rindex;
        }

        while (lindex < rindex)
        {
            if (arr[lindex] >= std)
            {
                arr[rindex--] = arr[lindex];
                break;
            }
            ++lindex;
        } 
    }

    arr[lindex] = std;
    quickSort(arr, bgn, lindex);
    quickSort(arr, rindex + 1, end);
}

複製代碼

4、插入排序 - 數列前面部分看爲有序,依次將後面的無序數列元素插入到前面的有序數列中,初始狀態有序數列僅有一個元素,即首元素。在將無序數列元素插入有序數列的過程中,採用了逆序遍歷有序數列,相較於順序遍歷會稍顯繁瑣,但當數列本身已近排序狀態效率會更高。

時間複雜度:O(N2)   穩定性:穩定

複製代碼

/*插入排序*/
void insertSort(vector<int> &arr, int bgn, int end)
{
    for (int i = bgn + 1; i < end; ++i)
    {
        /*
        * 分爲1,2兩部分處理,可以囊括j = beg - 1時的情況
        * 即需要將arr[i]插入到首元素前的位置,若使用一個for
        * 包括這兩部分,則會在發生這種情況時退出
        */
        /*1*/
        int j = i - 1;
        for ( ; j >= bgn; --j)
            if (arr[j] <= arr[i])
                break;
        /*2*/
        if (j != i - 1)
        {
            int temp = arr[i];
            for (int k = i; k > j + 1; --k)
            {
                arr[k] = arr[k - 1];
            }
            arr[j + 1] = temp;
        }
    }
}

複製代碼

5、希爾排序 - 插入排序的改進版。爲了減少數據的移動次數,在初始序列較大時取較大的步長,通常取序列長度的一半,此時只有兩個元素比較,交換一次;之後步長依次減半直至步長爲1,即爲插入排序,由於此時序列已接近有序,故插入元素時數據移動的次數會相對較少,效率得到了提高。

時間複雜度:通常認爲是O(N3/2) ,未驗證  穩定性:不穩定

複製代碼

 1 /*希爾排序*/
 2 void shellSort(vector<int> &arr, int bgn, int end)
 3 {
 4     for (int step = (end - bgn) / 2; step > 0; step /= 2)
 5     {
 6         for (int i = bgn; i < bgn + step; ++i)
 7         {
 8             /*
 9             * 以下,insertSort的變異
10             */
11             for (int j = i + step; j < end; j += step)
12             {
13                 int k = j - step;
14                 for ( ; k >= i; k -= step)
15                     if (arr[k] <= arr[j])
16                         break;
17                 if (k != j - step)
18                 {
19                     int tmp = arr[j];
20                     for (int m = j; m > k + step; m -= step)
21                         arr[m] = arr[m - step];
22                     arr[k + step] = tmp;
23                 }
24             }
25         }
26     }
27 }

複製代碼

6、桶排序 - 實現線性排序,但當元素間值得大小有較大差距時會帶來內存空間的較大浪費。首先,找出待排序列中得最大元素max,申請內存大小爲max + 1的桶(數組)並初始化爲0;然後,遍歷排序數列,並依次將每個元素作爲下標的桶元素值自增1;最後,遍歷桶元素,並依次將值非0的元素下標值載入排序數列(桶元素>1表明有值大小相等的元素,此時依次將他們載入排序數列),遍歷完成,排序數列便爲有序數列。

時間複雜度:O(x*N)   穩定性:穩定

複製代碼

/*桶排序*/
void bucketSort(vector<int> &arr)
{
    int max = getMaxValue(arr);
    int *pBuf = new int[max + 1];

    memset(pBuf, 0, (max + 1)*sizeof(int));
    for (auto const i : arr)
        ++pBuf[i];

    for (int i = 0, j = 0; i <= max; ++i)
    {
        while (pBuf[i]--)
            arr[j++] = i;
    }
    delete []pBuf;
}

複製代碼

7、基數排序 - 桶排序的改進版,桶的大小固定爲10,減少了內存空間的開銷。首先,找出待排序列中得最大元素max,並依次按max的低位到高位對所有元素排序;桶元素10個元素的大小即爲待排序數列元素對應數值爲相等元素的個數,即每次遍歷待排序數列,桶將其按對應數值位大小分爲了10個層級,桶內元素值得和爲待排序數列元素個數。

時間複雜度:O(x*N)   穩定性:穩定

複製代碼

/*基數排序*/
//1. 計數排序,按整形數值單位進行排序
void countSort(vector<int> &arr, int exp)
{
    int bucket[10] = { 0 };
    int arrSize = arr.size();
    int *pTemp = new int[arrSize];
    memset(pTemp, 0, arrSize * sizeof(int));

    //統計單位exp各數值計數值
    for (auto const val : arr)
        ++bucket[(val / exp) % 10];

    //計數分層
    for (int i = 1; i < 10; ++i)
        bucket[i] += bucket[i - 1];

    //按exp位大小用數組arr元素填充pTemp
    for (int i = arrSize - 1; i >= 0; --i)
        pTemp[ --bucket[(arr[i] / exp) % 10] ] = arr[i];
/*bugs*/
#if 0
    //bug1: bucket各層次的計數值沒遍歷一次相應自減1
    for (auto const val : arr)
        pTemp[bucket[(val / exp) % 10] - 1] = val;
    //bug2: arr數組元素每次排序時,下標應從大到小遍歷,否則無法實現排序
    for (auto const val : arr)
        pTemp[ --bucket[(val / exp) % 10] ] = val;
#endif

    //pTemp -> arr
    for (int i = 0; i < arrSize; ++i)
        arr[i] = pTemp[i];
    delete []pTemp;
}
//2. 合併各單位計數排序結果
void radixSort(vector<int> &arr)
{
    int max = getMaxValue(arr);
    for (int exp = 1; max / exp != 0; exp *= 10)
        countSort(arr, exp);
}

複製代碼

8、歸併排序 - 採用了分治和遞歸的思想,遞歸&分治-排序整個數列如同排序兩個有序數列,依次執行這個過程直至排序末端的兩個元素,再依次向上層輸送排序好的兩個子列進行排序直至整個數列有序(類比二叉樹的思想,from down to up)。

時間複雜度:O(NlogN)   穩定性:穩定

複製代碼

/*歸併排序*/
//排序兩個有序數列
void mergeSortInOrder(vector<int> &arr, int bgn, int mid, int end)
{
    int *pBuf = new int[end - bgn];
    int *pTemp = pBuf;
    int lindex = bgn;
    int rindex = mid;

    while ((lindex < mid) && (rindex < end))
        *(pTemp++) = (arr[lindex] < arr[rindex]) ? arr[lindex++] : arr[rindex++];

    while (lindex < mid)
        *pTemp++ = arr[lindex++];
    while (rindex < end)
        *pTemp++ = arr[rindex++];

    //pTemp -> arr
    pTemp = pBuf;
    for (int i = bgn; i < end; i++)
        arr[i] = *pTemp++;

    delete []pBuf;
}
//UpToDown To DownToUp
void mergeSort(vector<int> &arr, int bgn, int end)
{
    //數組arr空or僅有一個元素則退出
    if (bgn >= end - 1)
        return;

    int mid = (bgn + end) / 2;
    mergeSort(arr, bgn, mid);
    mergeSort(arr, mid, end);
    mergeSortInOrder(arr, bgn, mid, end);
}

複製代碼

9、堆排序 - 堆排序的思想藉助於二叉堆中的最大堆得以實現。首先,將待排序數列抽象爲二叉樹,並構造出最大堆;然後,依次將最大元素(即根節點元素)與待排序數列的最後一個元素交換(即二叉樹最深層最右邊的葉子結點元素);每次遍歷,刷新最後一個元素的位置(自減1),直至其與首元素相交,即完成排序。

時間複雜度:O(NlogN)   穩定性:不穩定

複製代碼

/*堆排序*/
//根節點元素自頂向下移動到合適的位置以構成最大堆
void downToMaxHeap(vector<int> &arr, int bgn, int end)
{
    int child;
    int parent = bgn;

    /*假根節點向下移動至合適的位置 --整個堆排序的核心代碼塊*/
    while ((child = parent * 2 + 1) < end)
    {
        if ((child < end - 1) && (arr[child] < arr[child + 1]))
            ++child;    //右孩子節點
        if (arr[child] > arr[parent])
            mySwap(&arr[child], &arr[parent]);
        else
            break;
        parent = child;
    }
}
//將數組構造爲最大堆
void buildMaxHeap(vector<int> &arr, int bgn, int end)
{
    if (bgn >= end - 1)
        return;

    int parent = end / 2 - 1;
    while (parent >= 0)
    {
        downToMaxHeap(arr, parent, end);
        --parent;
    }
}
//堆排序
void heapSort(vector<int> &arr, int bgn, int end)
{
    //構造最大堆
    buildMaxHeap(arr, bgn, end);

    while (end > 1)
    {
        mySwap(&arr[0], &arr[--end]);
        downToMaxHeap(arr, 0, end);
    }
}

複製代碼

參考博文:http://www.cnblogs.com/skywang12345/p/3603935.html

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