1. 程式人生 > >基本排序(三):插入排序 和 希爾排序- 從後向前掃描,比正操作元素大的逐步移位

基本排序(三):插入排序 和 希爾排序- 從後向前掃描,比正操作元素大的逐步移位

1. 插入排序

插入排序(Insertion Sort)工作原理(維基百科

通過構建有序序列,對於未排序資料,在已排序序列中從後向前掃描,找到相應位置插入。插入排序實現上,通常採用in-place排序(即只需用到O(1)的額外空間的排序),因而在從後向前掃描過程中,需要反覆把已排序元素逐步向後移位,為最新元素提供插入空間。

  • 演算法描述:一般來說,插入排序都採用in-place在陣列上實現。具體演算法描述如下:
    1. 從第一個元素開始,該元素可以認為已經被排序
    2. 取出下一個元素,在已經排序的元素序列中從後向前掃描
    3. 如果該元素(已排序)大於新元素,將該元素移到下一位置
    4. 重複步驟3,直到找到已排序的元素小於或者等於新元素的位置
    5. 將新元素插入到該位置後
    6. 重複步驟2~5
      這裡寫圖片描述
  • 時間複雜度:
    • 最差:O(n2)
    • 最優 : O(n)
    • 平均 : O(n2)
  • 空間複雜度:共O(n),需要輔助空間O(1)

    程式實現

 void Insertion_sort(Item a[], int l, int r)
{
    int i, j;
    for(i = l+1; i <= r; i++)
    {
        Item temp = a[i];
        for(j = i-1; j >= l && less(temp, a[j]); j--)
        {
            a[j+1
] = a[j]; } a[j+1] = temp; } }
  • 總結
    和選擇排序相比,插入排序的執行時間與輸入資料的原始順序密切相關。例如,如果檔案較大,並且關鍵字已經排好序(或幾乎排好序),插入排序比選擇排序要快。

2. 希爾排序

希爾排序是基於插入排序的以下兩點性質而提出改進方法的:
- 插入排序在對幾乎已經排好序的資料操作時,效率高,即可以達到線性排序的效率
- 但插入排序一般來說是低效的,因為插入排序每次只能將資料移動一位(相鄰的元素進行交換或移位)

演算法原理 From:維基百科

希爾排序通過將比較的全部元素分為幾個區域來提升插入排序的效能。這樣可以讓元素可以一次性地朝最終位置前進一大步。然後再取越來越小的步長進行排序,演算法的最後一步就是普通的插入排序(步長為1),但到了這步,需排序的資料幾乎是已經排序好的了(此時的插入排序較快)。

例如,假設有這樣一組數[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我們以步長(h)為5開始進行排序,我們可以通過將這列表放在有5列的表中來更好地描述演算法,這樣他們就應該看起來是這樣:

13 14 94 33 82
25 59 94 65 23
45 27 73 25 39
10
移動的增量從1變成了h = 5,然後我們對每列進行排序:

10 14 73 25 23
13 27 94 33 39
25 59 94 65 82
45
將上述四行數字,依序接在一起時我們得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ].這時10已經移至正確位置了,然後再以3為步長進行排序:

10 14 73
25 23 13
27 94 33
39 25 59
94 65 82
45
排序之後變為:

10 14 13
25 23 33
27 25 59
39 65 73
45 94 82
94
最後以1步長進行排序(此時就是簡單的插入排序了)。

其中步長序列的選擇至今沒有確定的方法。

程式實現

普通插入排序的C實現:

void Insertion_sort(Item a[], int l, int r)
{
    int i, j;
    for(i = l+1; i <= r; i++)
    {
        Item temp = a[i];
        for(j = i-1; j >= l && less(temp, a[j]); j--)
        {
            a[j+1] = a[j];
        }
        a[j+1] = temp;
    }
}

那麼在希爾排序中只需將步長“1”用“h”替換

void Shell_sort(Item a[], int l, int r)
{
    int i, j, h;
    for(h = 1; h <= (r-1)/9; h = 3*h+1);//產生步長序列
    for(; h > 0; h /= 3)
    {
        for(i = l+h; i <= r; i = i+h) //步長h
        {
            Item temp = a[i];
            for(j = i-h; j >= l && less(temp, a[j]); j = j-h)
            {
                a[j+h] = a[j];
            }
            a[j+h] = temp;
        }
    }
}

希爾排序的較高效率以及程式碼的簡單容易執行,經常被採用。

3. 測試程式

// 插入排序和希爾排序:從後向前掃描,比操作元素大的逐步移位
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define key(A) (A) //可改為A的標記
#define less(A, B) (key(A) < key(B))
#define N 10
typedef int Item;

void Insertion_sort(Item a[], int l, int r)
{
    int i, j;
    for(i = l+1; i <= r; i++)
    {
        Item temp = a[i];
        for(j = i-1; j >= l && less(temp, a[j]); j--)
        {
            a[j+1] = a[j];
        }
        a[j+1] = temp;
    }
}
void Shell_sort(Item a[], int l, int r)
{
    int i, j, h;
    for(h = 1; h <= (r-1)/9; h = 3*h+1);
    for(; h > 0; h /= 3)
    {
        for(i = l+h; i <= r; i = i+h)
        {
            Item temp = a[i];
            for(j = i-h; j >= l && less(temp, a[j]); j = j-h)
            {
                a[j+h] = a[j];
            }
            a[j+h] = temp;
        }
    }
}
void Random(Item a[], int length)
{
    srand((unsigned)time(NULL));
    for(int i = 0; i < length; i++)
        a[i] = rand() % 100;
}
void printArray(Item a[], int length)
{
    for(int i = 0; i < length; i++)
        printf("%d,\t", a[i]);
    printf("\n");
}
int main()
{
    Item a[N], b[N], c[N], d[N];
    Random(a, N);
    Random(b, N);
    Random(c, N);
    Random(d, N);
    printArray(a, N);

    Insertion_sort(b, 0, N - 1);
    printf("    Insertion_sort:\n");
    printArray(b, N);

    Shell_sort(c, 0, N-1);
    printf("    Shell_sort:\n");
    printArray(c, N);
    return 0;
}