1. 程式人生 > >資料結構--排序之插入排序

資料結構--排序之插入排序

插入排序分類:

一般有直接插入、折半插入、希爾排序三種。

三種插入排序的基本思想大致相同

給定一個序列a[1...n]

前兩種將一個序列分成有序部分(sorted)和無需部分(unsorted), 迴圈遍歷序列a, 當遍歷到第r個下標時, 區間 [1,r-1] 是有序部分,區間[r, n]是無序的,當前任務就是講下標為r的數插入到有序部分,將區間[1,r]變為有序,這樣有序區間長度加一,無序區間長度減一,迴圈結束後區間[1,n]內的數就是有序的,排序完成。

一、直接插入排序:(Straight Insertion Sort)

程式碼如下:

#include <bits/stdc++.h>
using namespace std;

#define MAXSIZE 100
typedef int KeyType;
typedef struct///定義每個節點資訊
{
    KeyType key;
//    InfoType otherinfo;
}RType;
typedef struct///定義順序表
{
    RType r[MAXSIZE + 1];
    int length;
}SqList;
void InsertSort(SqList &L)
{
    for(int i = 2; i <= L.length; i ++)
    {
        /***
        r[1...i-1]有序,r[i+1...n]無序
        對於當前迴圈需要將r[i]插入到有序段中,使r[1...r]有序,
        即找到r[i] 在 r[1...i-1]中的插入位置即可
        ***/
        if(L.r[i-1].key > L.r[i].key)
        {
             ///當r[i] < r[i-1] 時需要將r[i]插入到區間r[1...i-1]中
            L.r[0] = L.r[i];
            int j;
            for(j = i - 1; L.r[0].key < L.r[j].key; j --)
            {
                ///尋找插入位置,當r[0] > r[j]則跳出迴圈,j+1就是r[i]要插入的位置
                ///這裡體會r[0]的妙用
                L.r[j+1] = L.r[j];
            }
            L.r[j+1] = L.r[0];
        }
    }
}
int main()
{
    SqList L;
    printf("請輸入序列的個數:");
    scanf("%d", &L.length);
    printf("\n\n請輸入序列:");
    for(int i = 1; i <= L.length; i ++)
    {
        scanf("%d", &L.r[i].key);
    }
    InsertSort(L);
    printf("\n\n");
    for(int i = 1; i <= L.length; i ++)
    {
        printf("%5d", L.r[i].key);
    }
    printf("\n");
    return 0;
}
/**
7
49 38 65 97 76 13 27

10
18   73   40   84   71   59   64   85   41   98
**/

二、折半插入排序(Binary Insertion Sort)

和直接插入排序差不多,不同的是使用折半查詢來尋找第i個數在區間r[1...i-1]中的插入位置,減少了關鍵字的比較

程式碼如下:

#include <bits/stdc++.h>
using namespace std;

#define MAXSIZE 100
typedef int KeyType;
typedef struct///定義每個節點資訊
{
    KeyType key;
//    InfoType otherinfo;
}RType;
typedef struct///定義順序表
{
    RType r[MAXSIZE + 1];
    int length;
}SqList;
void BInsertSort(SqList &L)
{
    for(int i = 2; i <= L.length; i ++)
    {
        L.r[0] = L.r[i];
        int left = 1, right = i - 1, mid;
        while(left <= right)
        {
            mid = (left + right) >> 1;
            if(L.r[0].key < L.r[mid].key)right = mid - 1;
            else left = mid + 1;
        }
        ///插入位置是 right + 1,思考為什麼?
        for(int j = i - 1; j >= right + 1; j --)
        {
            L.r[j+1] = L.r[j];
        }
        L.r[right+1] = L.r[0];
    }
}
int main()
{
    SqList L;
    printf("請輸入序列的個數:");
    scanf("%d", &L.length);
    printf("\n\n請輸入序列:");
    for(int i = 1; i <= L.length; i ++)
    {
        scanf("%d", &L.r[i].key);
    }
    BInsertSort(L);
    printf("\n\n");
    for(int i = 1; i <= L.length; i ++)
    {
        printf("%5d", L.r[i].key);
    }
    printf("\n");
    return 0;
}
/**
7
49 38 65 97 76 13 27

10
18   73   40   84   71   59   64   85   41   98
**/

思考插入位置是 right + 1

注意插入的位置滿足大於等於 r[i] 的第一位

 

以上是迴圈裡面的兩種條件:

r[0] < r[mid],此時right = mid - 1, 如果r[0] >= r[right],則插入位置是就是right+1;

r[0] >= r[mid],則left = mid +1, 如果r[0] <= r[left], 所以left就是插入位置,然後接下來的迴圈中right始終都是mid-1,最終迴圈結束時

            right  = left - 1

;

隨著迴圈的進行,其他情況都能得到以上兩種情況

同時迴圈結束條件是left = right + 1,所以程式中插入位置right+1也能用left代替

 

直接插入排序與折板插入排序的時間複雜度都是O(n^2),移動次數也一樣,不過後者的關鍵字比較次數較少,空間複雜度均是O(1)。

三、希爾排序(Shell's Sort)

將序列分成若干組,對每一組進行插入排序

程式碼如下:

#include <bits/stdc++.h>
using namespace std;

#define MAXSIZE 100
typedef int KeyType;
typedef struct///定義每個節點資訊
{
    KeyType key;
//    InfoType otherinfo;
}RType;
typedef struct///定義順序表
{
    RType r[MAXSIZE + 1];
    int length;
}SqList;

///相當於對每一個分組進行直接插入排序
void ShellInsert(SqList &L, int dk)
{
    for(int i = dk + 1; i <= L.length; i ++)
    {
        if(L.r[i-dk].key > L.r[i].key)
        {
            L.r[0] = L.r[i];
            int j;
            for(j = i - dk; j > 0 && L.r[0].key < L.r[j].key; j -= dk)
            {
                L.r[j+dk] = L.r[j];
            }
            L.r[j+dk] = L.r[0];
        }
    }
}
void ShellSort(SqList &L, int d[], int t)
{
    for(int i = 0; i < t; i ++)
    {
        ShellInsert(L, d[i]);
    }
}
int main()
{
    SqList L;
    printf("請輸入序列的個數:");
    scanf("%d", &L.length);
    printf("\n\n請輸入序列:");
    for(int i = 1; i <= L.length; i ++)
    {
        scanf("%d", &L.r[i].key);
    }
    int d[3] = {5, 3, 1};
    int t = 3;
    ShellSort(L, d, t);
    printf("\n\n");
    for(int i = 1; i <= L.length; i ++)
    {
        printf("%5d", L.r[i].key);
    }
    printf("\n");
    return 0;
}
/**

以d[0] = 5, d[1] = 3, d[2] = 1 為例
7
49 38 65 97 76 13 27

10
18   73   40   84   71   59   64   85   41   98
**/

對以上的演算法用到一個分組函式d[n], 不過目前還任未找到一個較好的函式。

注意:最後一個分組必定是1,來確保最終的序列有序。

此時演算法就退化到直接插入排序,不過經過前面的對每個分組排序後,一般序列基本有序,此時移動次數較少。

總體來說,希爾排序的時間複雜度可減小到n(log n)^2, 空間複雜度O(1)。