排序演算法--幾種插入排序
插入排序總體上說也是簡單排序,除去Shell排序的話,其他的插入排序演算法時間複雜度基本上是O(n ^ 2)的,除非有特殊的情況.
首先說說直接插入排序:
描述:
插入排序的思想大概就像是打牌是摸牌的過程(很多書諸如<<演算法導論>>和<<計算機程式設計藝術>>都那這個例子來打比方,好像大師們都喜歡玩橋牌,我不是大師所以只喜歡打夠級).
玩牌者新摸到牌時總是要搜尋一下然後把牌插到適合的位置,慢慢地,牌摸完時,手中地牌自然而然地排好了順序.
程式碼:
#include <stdio.h>constint
void InsertSort(int*A)
{
int i, j, temp;
for(i =1; i < MAX; i++)
{
temp = A[i];
j = i -1;
while( j >=0&& temp < A[j])
{
A[j +1] = A[j];
j--;
}
A[j +1] = temp;
}
}
int main()
{
int A[MAX];
int nums = MAX;
printf("Please input %d numbers: ", nums);
for(i =0; i < MAX; i++)
scanf("%d", &A[i]);
InsertSort(A);
for(i =0; i < MAX; i++)
printf("%d ", A[i]);
return0;
}
分析:
直接插入排序的好處就是演算法簡單明瞭,程式容易實現.但是效率就不可避免的比較低下.程式除了需要一個記錄資料的陣列以外還要有一個臨時的輔助空間.
從程式中可以看出,即使假設陣列已經是順序的,程式仍舊需要進行n - 1次的比較,但是不用進行陣列元素的移動.當陣列是逆序的時候,程式需要進行(n + 2)(n - 1) / 2次的比較,另加(n + 4)(n - 1) / 2次的陣列元素移動.綜上平均計算一下的話,差不多要進行(n ^ 2) / 4次的比較和移動,所以複雜度應該是O(n ^ 2).
折半插入排序:
鑑於直接插入排序再處理第i個數據的時候,平均要和i / 2個已經排好序的資料進行比較,如果有n個元素的話,那麼總的計算起來平均就是(n ^ 2) / 4.我們可以把二分查詢和直接插入排序結合起來.用二分查詢的技術來尋找應該插入的正確位置.
程式碼:
#include <stdio.h>constint MAX =10;
void BinaryInsertSort(int*A)
{
int i, j, temp, first, last, mid;
for(i =1; i < MAX; i++)
{
temp = A[i];
first =0;
last = i -1;
while(first <= last) //用二分查詢得方法查詢key應該插入的位置
{
mid = (first + last) /2;
if(A[mid] > temp)
last = mid -1;
else
first = mid +1;
}
j = i -1;
while(j > last && temp < A[j])
{
A[j +1] = A[j];
j--;
}
A[last +1] = temp;
}
}
int main()
{
int A[MAX];
int i;
int nums = MAX; // Just for the next sentence。
printf("Please input %d numbers: ", nums);
for(i =0; i < MAX; i++)
scanf("%d", &A[i]);
BinaryInsertSort(A);
for(i =0; i < MAX; i++)
printf("%d ", A[i]);
return0;
}
這個方法可以減少資料比較的次數,但是資料移動還是要一步一步的進行(仍舊需要移動大約i / 2個已排好序的元素),所以時間複雜度沒有什麼實質的改善.治標不治本的方法.
二路插入排序:
上面的二分插入減少的是資料比較的次數,而二路插入的方法可以減少資料移動的次數.
程式碼:
#include <stdio.h>constint MAX =10;
void twoWaysInsertSort(int*A)
{
int first, final, i, keyIndex, index;
int B[MAX]; //輔助記錄空間
first = final =0;
B[0] = A[0];
for(i =1; i < MAX; i++)
{
if(A[i] >= B[final])
{
final +=1;
B[final] = A[i];
}
elseif(A[i] < B[first])
{
first = (first + MAX -1) % MAX;
B[first] = A[i];
}
else//當資料在最大值和最小值之間時
{
keyIndex = (final -1+ MAX) % MAX;
while(1)
{
if (B[keyIndex] <= A[i])
{
index = final++;
while(index != keyIndex)
{
B[(index +1) % MAX] = B[index];
index = (index -1+ MAX) % MAX;
}
B[(keyIndex +1) % MAX] = A[i];
break;
}
keyIndex = (keyIndex -1+ MAX) % MAX;
}
}
}
for(i =0; i < MAX; i++)
{
printf("%d ", B[i]);
}
printf("");
for(i =0; i < MAX; i++) //將輔助空間中的排好序的陣列複製到原陣列中
{
A[i] = B[first];
first = (first +1) % MAX;
}
}
int main()
{
int A[MAX];
int i;
int nums = MAX; // Just for the next sentence。
printf("Please input %d numbers: ", nums);
for(i =0; i < MAX; i++)
scanf("%d", &A[i]);
twoWaysInsertSort(A);
for(i =0; i < MAX; i++)
printf("%d ", A[i]);
return0;
}
這個二路插入的演算法可以比二分插入演算法節約一半的執行時間,但是程式比較複雜.
上面的程式另外使用了一個大小為n的輔助空間,並且把B看成了迴圈向量.first是陣列中最小的元素,final是陣列中最大的元素,如果待排序的元素小於first則放在first的前面,如果比final小則放在final的後面.
在<<計算機程式設計藝術>>中作者說到過一種方法,把輸入區域當作一個迴圈表,而且位置N同1相鄰.根據上一次插入的元素是落在已排序元素的中心的左面還是右面,而從當前未排序元素段的右面或是左面來取新的元素.過後通常需要"轉動"這個區域.通過這個方法可以僅用N + 1的空間同時進行輸入,輸出和排序.
插入排序族中還有許多其他的方法,但是基本上都是在直接插入的方法上改進的,目的無非是減少比較和移動的次數.Shell排序也可以算是一種插入排序.不同的是Shell排序的時間複雜度不是O(n ^ 2),而是根據所選的增量序列而定的.