資料結構--排序之插入排序
插入排序分類:
一般有直接插入、折半插入、希爾排序三種。
三種插入排序的基本思想大致相同
給定一個序列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)。