對於算法思想的理解可以參考下面的這個帖子,十大經典排序算法(動圖演示) - 一像素 - 博客園,因為算法的邏輯和數學很像,相應的基礎資料一般也能在網上找到,所以,本帖子這談論一些重要的注意點,其他人講到的我就不提了,在實現的過程中可能有些代碼不是很理解,其他的就相對比較容易多了。
整體按照這個順序來,也比較好記憶一點:
一、交換排序
1、冒泡排序,基本過程參考前面的帖子,實現代碼:
void BubbleSort(int a[], int n) // 本算法將a[]中的元素從小到到大進行排序
{ for(int i = 0; i < n - 1; i++){ flag = false; // 表示本趟冒泡是否發生交換的標志
for(j = n - 1; j > i; j--){ // 一趟冒泡過程
swap(a[j - 1], a[j]); // 為交換函數,將a[j] 與 a[j - 1] 進行交換
flag = true; } } if (flag == false) // 本趟遍歷后沒有發生交換,說明表已經有序
return; }
注意:
1)通過設置flag可以直接判斷表是否有序,如果有序直接退出,是一個小技巧;
2)排序時間復雜度與表的初始順序有關,表有序時,比較次數為 n - 1,此為最好的時間復雜度O(n),最差為O(n^2);
3)每趟排序將一個元素放在最終的位置上;
2、快速排序
基於分治法思想,這個與動態圖不太一樣,針對考研的同學,希望還是以嚴蔚敏的版本為准:
int partition(int a[], int low, int high){ int pivot = a[low]; // 將當前表中第一個元素設為樞軸值
while(low < high){ // 下標控制條件
while(low < high && a[high] >= pivot) --high; a[low] = a[high]; while(low < high && pivot >= a[low]) ++low; a[high] = a[low]; } a[low] = pivot; return low; // 返回數組下標, }
如圖,下面的列子:
一開始的表 a[ ]
while(low < high && a[high] >= pivot) // 如果a[high]> pivot, high向前移動,直到a[high] < pivot; --high; a[low] = a[high]; // 交換
此時的狀態:
while(low < high && pivot >= a[low]) // 如果a[low] < pivot, low向后移動,直到a[low] > pivot; ++low; a[high] = a[low]; // 交換
此時的狀態:
while( low < high) { ... } 的第一遍循環結束,開始第二次的循環:
這個小例子比較簡單,兩遍就結束了,接下來判斷條件不滿足,退出:
while(low < high){ // 下標控制條件
... }
a[low] = pivot; return low; // 返回數組下標,
結果圖:
這是各個分支的過程,其中主程序的代碼可以采用遞歸式,也可以采用非遞歸的,
void QuickSort (int a[], int low, int high){ if (low < high){ int pivot = partition(A, low, high); QuickSort(A, low, pivot - 1); QuickSort(A, pivot + 1, high); } /if }
point:
1)快排是所有內排序算法平均性能最優的,平均時間復雜度O(nlog₂n),最差為O(n²),平均空間復雜度O(log₂n),最差為O(n);
2)不穩定;
3)其過程中,不產生有序子序列,但是每趟將一個元素放在最終的位置上,就是基准元素;
三、插入排序
1、直接插入排序
這個比較好理解,直接上代碼:
void InsertSort(int a[], int n){ int i, j; for(int i = 2; i <= n; i++){ // 依次將下標2 —— n 插入到已排好的序列
if (a[i] < a[i -1]){ // 前面下標從2開始就是此處 i - 1 的原因
a[0] = a[i]; //
for(j = i -1; a[0] < a[j]; --j){ // 從后面往前查找待插入的元素
a[j + 1] = a[j]; // 向后挪位置
} a[j + 1] = a[0]; } } }
point:
1)移動和比較次數取決於待排序表的初始狀態,最好的情況是表已經有序,時間復雜度為O(n), 平均時間復雜度為O(n²);
2)穩定;
3)注意有個哨兵機制,之后的折半插入與希爾排序也是根據此算法轉換而來;
2、折半插入排序
基於直接插入排序作出的改動,如圖:
當下邊指向7時,前面已經有序,因此利用二分法找到2的后面,然后再直接放入,
point :
1)僅僅是減少了比較的次數,約為O(nlog₂n),該比較次數與待排序表的初始狀態無關,僅取決於表中的元素個數n;移動次數沒有改變,依賴於待排序表中的初始狀態;
2)時間復雜度仍為O(n²)
3)穩定
3、希爾排序
參考的帖子有點小瑕疵,這里面着重提一下:
先上代碼:
void ShellSort(int a[], int n){ for(dk = n/2; dk >= 1; dk = df/2){ // 初始增量序列是n/2,后面依次是
for(i = dk+1; i<=n; ++i){ if (a[i] < a[i - dk]){ a[0] = a[i]; // 先暫存在a[0]中
for(j = i-dk; j>0 && a[0]<a[j]; j -= dk){ //j<0 的時候說明到頭了
a[j + dk] = a[j]; } a[j + dk] = a[0]; // 記錄后移,查找插入的位置
} } } }
如圖,初始數組:
第一趟,dk = n / 2 時,注意,帶*號的還沒進行比較:
此時,重點來了,
for(j = i-dk; j>0 && a[0]<a[j]; j -= dk){ //j<0 的時候說明到頭了 a[j + dk] = a[j];
}
這段代碼的作用就是下圖的效果,還要與前面同樣的增量序列大小進行比較
至此,第一輪循環結束,第二輪增量序列為2 = 4 / 2,之后的過程也是如此,就不重復了。
point:
1)時間復雜度依賴於增量序列的函數,n 取某個值時,最好為O()
2)不穩定