常用排序算法(二)選擇排序、快速排序
常用排序算法(三)歸併排序、堆排序、基數排序
1. 插入排序
算法
最簡單的排序算法之一是插入排序(insertion sort)。插入排序由N-1趟(pass)排序組成。對於P=1趟到P=N-1趟,插入排序保證從位置0到位置P上的元素爲已排序狀態。插入排序利用了這樣的事實:位置0到位置P-1上的元素是已排過序的。下圖顯示一個簡單的數組在每一趟插入排序後的情況。
C語言給出算法參考程序如下,顯然,插入排序算法複雜度爲 ,輸入爲逆序的時候達到最壞邊界。
void InsertionSort(int a[], int n)
{
int i, j;
for (i = 1; i < n; i++)
{
int temp = a[i];
for (j = i; j > 0 && temp < a[j - 1]; j--)
a[j] = a[j - 1];
a[j] = temp;
}
}
改進版的插入排序
我們知道,插入排序利用了這樣的事實:位置0到位置P-1上的元素是已排過序的。那麼對於前面已排好序的序列,將數據找到合適的位置插入,一個比較不錯的方法是用二分法找到插入位置,然後將該位置後直到P-1位置處的數據都往後移動一位。具體實現代碼讀者可自行實踐。
二分插入排序的平均時間複雜度仍然爲O(n2),空間複雜度爲O(1),挪動的次數不變,只是減少了比較的次數,時間上會比直接插入排序要快一些,但時間複雜度不變。
2. 希爾排序
希爾排序(Shellsort)通過比較相距一定間隔的元素來工作,各趟比較所用的距離隨着算法的進行而減小,直到只比較相鄰元素的最後一趟排序未知,由於這個原因,希爾排序有時也叫作縮小增量排序(diminishing increment sort)。
希爾排序使用一個序列h1,h2,…,ht,叫做增量序列(increment sequence)。只要h1=1,任何增量序列都是可行的,不過,有些增量序列比另外一些增量序列更好。在使用增量hk的一趟排序之後,對於每一個i我們有A[i]<=A[i+hk]:所有相隔hk的元素都被排序。此時稱文件是hk-排序(hk-sorted)的。下圖顯示各趟排序後數組的情況。
仔細的考查可以發現,一趟hk-排序的作用就是對hk個獨立的子數組執行一次插入排序。
void ShellSort(int a[], int n)
{
for (int increment = n / 2; increment > 0; increment /= 2)
{
for (int i = increment; i < n; i++)
{
for (int j = i; j >= increment; j -= increment)
{
if (a[j] < a[j - increment])
{
int temp = a[j];
a[j] = a[j - increment];
a[j - increment] = temp;
}
else
break;
}
}
}
}
希爾排序的最差時間複雜度是O(n2),最優時間複雜度是O(n),平均時間複雜度爲O(n1.5),需要強調的是,希爾排序的平均時間複雜度與增量的選擇有關,在我的實現中使用的是折半的方法,不一定是最好的增量序列,查閱資料表明,當增量之間成倍數關係時效率其實是不高的。希爾排序的時間複雜度比快速排序要高,它的優勢在於平均情況下和最差情況下的表現接近,在這點上比快排有優勢,並且易於實現。因此有資料表明,任何排序工作在一開始都可以用希爾排序來做,在表現不佳的時候,再換成快速排序來做。本質上講,希爾排序算法是直接插入排序算法的一種改進,減少了其複製的次數,速度要快很多。 原因是,當n值很大時數據項每一趟排序需要的個數很少,但數據項的距離很長。當n值減小時每一趟需要和動的數據增多,此時已經接近於它們排序後的最終位置。正是這兩種情況的結合才使希爾排序效率比插入排序高很多。
3. 冒泡排序
算法
冒泡算法的主要邏輯是,假設有N個數,遊標從第一位數開始,若左邊的數比右邊的數大,則左邊交換,遊標移向下一位直到最後一位。在遊標移動過程中,可以保證,右邊的數一定比左邊的數大,因爲第一輪遍歷是要找出最大的數,並且最大的數在最後一位。同理,要找出第二大的數,重複上述過程,直至找出第N大的數,排序結束。
舉例說明:要排序數組:int[] arr={34,8,64,51,32,21};
第一趟排序:
第一次排序:34和8比較,34大於8,交換位置: 8 34 64 51 32 21
第二次排序:34和64比較,34小於64,不交換位置:8 34 64 51 32 21
第三次排序:64和51比較,64大於51,交換位置: 8 34 51 64 32 21
第四次排序:64和32比較,64大於32,交換位置:8 34 51 32 64 21
第五次排序:64和21比較:64大於21,交換位置: 8 34 51 32 21 64
第一趟總共進行了5次比較,找到最大的數。 排序結果: 8 34 51 32 21 64
第二趟排序:
第一次排序:8和34比較,8小於34,不交換位置:8 34 51 32 21 64
第二次排序:34和51比較,34小於51,不交換位置: 8 34 51 32 21 64
第三次排序:51和32比較,51大於32,交換位置:8 34 32 51 21 64
第四次排序:51和21比較,51大於21,交換位置: 8 34 32 21 51 64
第二趟總共進行了4次比較,找到次大的數。 排序結果: 8 34 32 21 51 64
第三趟排序:
第一次排序:8和34比較,8小於34,不交換位置: 8 34 32 21 51 64
第二次排序:34和32比較,34大於32,交換位置:8 32 34 21 51 64
第三次排序:34和21比較,34大於21,交換位置: 8 32 21 34 51 64
第二趟總共進行了3次比較,找到第三大的數。 排序結果: 8 32 21 34 51 64
第四趟排序:
第一次排序:8和32比較,8小於32,不交換位置:8 32 21 34 51 64
第二次排序:32和21比較,32大於21,交換位置: 8 21 32 34 51 64
第二趟總共進行了2次比較,找到第四大的數。 排序結果: 8 21 32 34 51 64
第五趟排序:
第一次排序:8和21比較,8小於21,不交換位置: 8 21 32 34 51 64
第二趟總共進行了1次比較,找到第五大的數。 排序結果: 8 21 32 34 51 64
最終結果:8 21 32 34 51 64
冒泡排序算法C語言實現如下,
void BubbleSort(int a[], int n)
{
for (int i = 0; i < n - 1; i++)
{
for (int j = 0; j < n - 1 - i; j++)
{
if (a[j] > a[j + 1])
{
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
改進版的冒泡排序
冒泡排序可以做一點改進讓它的速度稍微快一點,設想,當某一次循環中,兩兩比較之後,沒有一次交換髮生,意味着此時數組已經排好序了,這個時候在進行之後的循環是沒有意義的,純粹是浪費時間,所以在這個時候應該結束循環,排序結束。C語言實現如下
void BubbleSort(int a[], int n)
{
bool swapFlag = false;
for (int i = 0; i < n - 1; i++)
{
for (int j = 0; j < n - 1 - i; j++)
{
if (a[j] > a[j + 1])
{
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
swapFlag = true;
}
}
if (!swapFlag)
break;
}
}
基本版的冒泡排序的時間複雜度最好和最差情況下都是O(n²),改進版的冒泡排序最好情況下時間複雜度可以達到O(n),最差情況下時間複雜度仍爲O(n²)。兩個版本的冒泡排序平均時間複雜度都爲O(n²),空間複雜度爲O(1),因爲冒泡排序不佔用多餘的空間。
冒泡排序是一種原地排序(In-place sort)並且穩定(stable sort)的排序算法,優點是實現簡單,佔用空間小,缺點是效率低,時間複雜度高,對於大規模的數據耗時長。