漫談經典排序演算法 二 各種插入排序解析及效能比較

2021-06-01 08:40:01 字數 4506 閱讀 3180

這是《漫談經典排序演算法系列》第二篇,解析了各種插入排序演算法

。主要包括:直接插入排序、折半插入排序、表插入排序、希爾插入排序。

每一種演算法的開頭都敘述了引出該演算法的原因,然後給出**,最後分析演算法效率及和其他插入排序相比,優劣在**。

各種排序演算法的解析請參考如下:

《漫談經典排序演算法:一、從簡單選擇排序到堆排序的深度解析》

《漫談經典排序演算法:二、各種插入排序解析及效能比較》

《漫談經典排序演算法:三、氣泡排序 && 快速排序》

《漫談經典排序演算法:四、歸併排序》

《漫談經典排序演算法:五、線性時間排序(計數、基數、桶排序)》

《漫談經典排序演算法:六、各種排序演算法總結》

注:為了敘述方便,本文以及源**中均不考慮a[0],預設下標從1開始。

給定待排序序列a[ 1.....n ],現假設a[1...i]已經有序,那麼我們取出a[i+1]插入到序列a[1...i].這樣有序序列記錄數就增加了1.如此重複上述操作,不斷取出記錄插入有序序列,直到a[n]插入到有序序列,排序完成。

view plain

//直接插入排序

void

straightinsertsort(

int*a,

intn)  

else

break

;  }  

a[j+1]=temp;  

}  }  

容易看出,要插入的記錄個數為n-1,其中關鍵字的比較次數和記錄移動次數是依賴於給出的待排序序列是否基本有序。在最佳情況下(待排

序序列有序),比較次數和移動次數時間為o(1),所以時間複雜度為o(n).在最壞情況下(待排序序列逆序)和平均時間均為o(n^2).

從上述分析中可以看出,直接插入排序適合記錄數比較少、給定序列基本有序的情況。熟悉了排序過程我們發現,直接插入排序是一種穩定的原地排序演算法。

在直接插入排序過程中,我們是把乙個記錄插入到有序序列中,至於要插入到有序序列中的哪個位置呢?採用的是順序查詢確定插入的位置。顯然對於有序序列,折半查詢的效率要高,所以在尋找插入位置時可以用折半查詢。折半查詢主要分為三步:1、查詢要插入的位置  2、移位  3、把記錄插入相應的位置。

view plain

//折半查詢

intbinarysearch(

int*a,

intlow,

inthigh,

intkey)  

//折半插入排序

void

binaryinsertsort(

int*a,

intn)  

}  

折半插入排序是對直接插入排序的一種改進,這種改進只考慮了關鍵字比較次數,並沒有減少移位次數,所以平均時間和最壞情況下(待排序序列逆序)時間複雜度o(n^2),如果記錄數量很大的話,這兩種情況下是優於直接插入排序。再來看一下最佳情況(待排序序列有序),此時關鍵字比較次數並不為o(1),時間複雜度為o(n*log2n)。(其中折半查詢時間複雜度o(log2n),這個在以後寫查詢的時候再分析,這裡不做詳細講解。)。所以在記錄數較小、待排序序列基本有序情況下直接插入排序優於折半插入排序。此外,折半插入排序是不穩定的原地排序,實現起來也較複雜。

折半插入排序相對於直接插入排序來說減少了比較次數。那麼我們可不可以減少移動次數呢,答案是可以的。於是就有了表插入排序,用乙個靜態鍊錶來儲存待排序序列,其他操作和直接插入排序很像。主要步驟:1、初始化鍊錶  2、取出要插入的記錄 3、遍歷鍊錶尋找插入位置  4、記錄插入鍊錶中。

view plain

//靜態鍊錶

typedef

struct

node,*pnode;  

//表插入排序

void

tableinsertsort(pnode list,

intn)  

if(p==0)else

}  }  

表插入排序也是對直接插入排序的一種改進,這種改進只減少了移動次數,並沒有減少關鍵字比較次數,所以平均時間和最壞情況下(待排序序列逆序)時間複雜度o(n^2),如果記錄數量很大的話,這兩種情況下是優於直接插入排序。再來看一下最佳情況(待排序序列有序),關鍵字比較次數並為o(1),時間複雜度為o(n)。此時和直接插入排序時間複雜度一樣。此外,表插入排序改變了記錄的儲存結構,無法順序訪問,是一種穩定的排序演算法,實現起來也較複雜。

上述兩種排序都是只考慮減少關鍵字比較次數或者只考慮減少關鍵字移動次數。有沒有別的改進辦法呢?我們注意到,直接插入排序適合於記錄數較少、基本有序的情況。於是我們可以先將整個待排序序列分割成若干子串行分別進行直接插入排序,整個序列基本有序時,再對序列進行一次直接插入排序。這就是希爾排序。

view plain

//一趟增量為dk的希爾插入排序

void

shellinsert(

int*a,

intn,

intdk)  

}  //希爾排序

void

shellsort(

int*a,

intn)  

;  for

(i=0;i<5;i++)  

shellinsert(a,6,dk[i]);  

}  

當給定序列記錄量較大時,希爾排序效能優於直接插入排序。再希爾排序的過程中,關鍵字是跳躍式移動的,這樣就減少了移動次數。希爾排序效能的分析是乙個複雜的問題,時間與所取的增量有關。增量選取的不好可能會大大降低排序效率。

《資料結構》嚴蔚敏版

view plain

#include

//直接插入排序

void

straightinsertsort(

int*a,

intn)  

else

break

;  }  

a[j+1]=temp;  

}  }  

void

main()  

;//不考慮a[0]

straightinsertsort(a,6);  

for(i=1;i<=6;i++)  

printf("%-4d"

,a[i]);  

printf("\n"

);  

}  

view plain

#include

//折半查詢

intbinarysearch(

int*a,

intlow,

inthigh,

intkey)  

//折半插入排序

void

binaryinsertsort(

int*a,

intn)  

}  void

main()  

;//不考慮a[0]

binaryinsertsort(a,6);  

for(i=1;i<=6;i++)  

printf("%-4d"

,a[i]);  

printf("\n"

);  

}  

view plain

#include

#define max 10000

//靜態鍊錶

typedef

struct

node,*pnode;  

//表插入排序

void

tableinsertsort(pnode list,

intn)  

if(p==0)else

}  }  void

main()  

;  tableinsertsort(list,6);  

p=list[0].next;  

while

(p!=0)  

printf("\n"

);  

}  

view plain

#include

//一趟增量為dk的希爾插入排序

void

shellinsert(

int*a,

intn,

intdk)  

}  //希爾排序

void

shellsort(

int*a,

intn)  

;  for

(i=0;i<5;i++)  

shellinsert(a,6,dk[i]);  

}  void

main()  

;//不考慮a[0]

shellsort(a,6);  

for(i=1;i<=6;i++)  

printf("%-4d"

,a[i]);  

printf("\n"

);  

}  原文出處:

漫談經典排序演算法 二 各種插入排序解析及效能比較

這是 漫談經典排序演算法系列 第二篇,解析了各種插入排序演算法 主要包括 直接插入排序 折半插入排序 表插入排序 希爾插入排序。每一種演算法的開頭都敘述了引出該演算法的原因,然後給出 最後分析演算法效率及和其他插入排序相比,優劣在 各種排序演算法的解析請參考如下 漫談經典排序演算法 一 從簡單選擇排...

經典排序演算法 插入排序

插入的排序的原理是每趟將乙個數按照大小插入到它前面已經排好序的子串行中。依次重複,直到插入全部數字。以陣列 3,4,1,2 為例,公升序排列陣列。第一趟從第2個資料開始 第1個數字自己已經有序 跟前面乙個數字比較,如果小於前面數字就將前面數字後移,並繼續向前比較,直到下標小於0為止。如下圖所示 第二...

經典排序演算法2(插入排序)

a 插入排序分類 插入排序主要做兩件事,一是尋找插入點,二是移動插入點左側的資料 所以根據插入點的不同我們將插入排序又分為直接插入排序 折半插入排序和二路插入排序。原理 我將依次遍歷陣列中的元素,將陣列分為 使用中的 和 待使用中的 兩部分,在使用中的 資料時排序好的,待使用中的 資料時未排序的,我...