經典排序算法
算法分類
十種常見排序算法可以分爲兩大類:
比較類排序:通過比較來決定元素間的相對次序,由於其時間複雜度不能突破O(nlogn),因此也稱爲非線性時間比較類排序。
非比較類排序:不通過比較來決定元素間的相對次序,它可以突破基於比較排序的時間下界,以線性時間運行,因此也稱爲線性時間非比較類排序。
算法複雜度
穩定:如果a原本在b前面,而a=b,排序之後a仍然在b的前面。
不穩定:如果a原本在b的前面,而a=b,排序之後 a 可能會出現在 b 的後面。
時間複雜度:對排序數據的總的操作次數。反映當n變化時,操作次數呈現什麼規律。
空間複雜度:是指算法在計算機內執行時所需存儲空間的度量,它也是數據規模n的函數。
冒泡排序(Bubble Sort)
- 比較相鄰的元素。如果第一個比第二個大,就交換它們兩個;
- 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對,這樣在最後的元素應該會是最大的數;
- 針對(N-1)個元素重複以上的步驟(除了最後一個) 重複步驟1~3,直到排序完成。
#include <stdio.h>
#include <stdlib.h>
void ShowArray(int*pArr, int len);
void BabbleSort(int *pArr, int len);
int main()
{
int array[10] = {-1, 9, 5, 0, -10, 44, 77, 33, -2, -99};
int length = sizeof(array) / sizeof(array[0]);
ShowArray(array, length);
BabbleSort(array, length);
printf("排序後的數組爲:");
ShowArray(array, length);
system("PAUSE");
return 0;
}
//默認爲增序排序
void BabbleSort(int *pArr, int len)
{
for (int i = 0; i < len - 1; i++)
{
for (int j = i+1; j < len; j++)
{
if (pArr[i] > pArr[j])
{
int tmp = pArr[i];
pArr[i] = pArr[j];
pArr[j] = tmp;
}
}
}
}
void ShowArray(int *pArr, int len)
{
for (int i=0; i<len; i++)
{
printf("%d ", pArr[i]);
}
printf("\n");
}
選擇排序(Selection Sort)
- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
- 再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾
- 以此類推,直到所有元素均排序完畢。
#include <stdio.h>
#include <stdlib.h>
void ShowArray(int*pArr, int len);
void SelectionSort(int *pArr, int len);
int main()
{
int array[10] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
int length = sizeof(array) / sizeof(array[0]);
ShowArray(array, length);
SelectionSort(array, length);
printf("排序後的數組爲:");
ShowArray(array, length);
system("PAUSE");
return 0;
}
//默認爲增序排序
void SelectionSort(int *pArr, int len)
{
int minIndex;
for (int i = 0; i < len - 1; i++)
{
minIndex = i;
for (int j = i + 1; j < len; j++)
{
if (pArr[minIndex] > pArr[j])
{
minIndex = j; // search min value
}
}
int tmp = pArr[i];
pArr[i] = pArr[minIndex];
pArr[minIndex] = tmp;
}
}
void ShowArray(int *pArr, int len)
{
for (int i = 0; i < len; i++)
{
printf("%d ", pArr[i]);
}
printf("\n");
}
插入排序(Insertion Sort)
工作原理是通過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。
- 從第一個元素開始,該元素可以認爲已經被排序;
- 取出下一個元素,在已經排序的元素序列中從後向前掃描;
- 如果該元素(已排序)大於新元素,將該元素移到下一位置;
- 重複步驟3,直到找到已排序的元素小於或者等於新元素的位置;
- 將新元素插入到該位置後;
- 重複步驟2~5。
#include <stdio.h>
#include <stdlib.h>
void ShowArray(int*pArr, int len);
void InsertSort(int *pArr, int len);
int main()
{
int array[10] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
int length = sizeof(array) / sizeof(array[0]);
ShowArray(array, length);
InsertSort(array, length);
printf("排序後的數組爲:");
ShowArray(array, length);
system("PAUSE");
return 0;
}
//默認爲增序排序
void InsertSort(int *pArr, int len)
{
int preIndex, curr;
for (int i = 1; i < len; i++)
{
curr = pArr[i];
preIndex = i - 1;
while (preIndex >= 0 && pArr[preIndex] > curr)
{
pArr[preIndex + 1] = pArr[preIndex];
preIndex--;
}
pArr[preIndex + 1] = curr;
}
}
void ShowArray(int *pArr, int len)
{
for (int i = 0; i < len; i++)
{
printf("%d ", pArr[i]);
}
printf("\n");
}
歸併排序(Merge Sort)
歸併排序包括"從上往下"和"從下往上"2種方式
- 從下往上的歸併排序:
將待排序的數列分成若干個長度爲1的子數列,然後將這些數列兩兩合併;
得到若干個長度爲2的有序數列,再將這些數列兩兩合併;
得到若干個長度爲4的有序數列,再將它們兩兩合併;
直接合併成一個數列爲止。 - 從上往下的歸併排序:它與"從下往上"在排序上是反方向的。它基本包括3步:
① 分解 – 將當前區間一分爲二,即求分裂點 mid = (low + high)/2;
② 求解 – 遞歸地對兩個子區間a[low…mid] 和 a[mid+1…high]進行歸併排序。遞歸的終結條件是子區間長度爲1。
③ 合併 – 將已排序的兩個子區間a[low…mid]和 a[mid+1…high]歸併爲一個有序的區間a[low…high]。
#include <stdio.h>
#include <stdlib.h>
// 數組長度
#define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )
/*
* 將一個數組中的兩個相鄰有序區間合併成一個
*
* 參數說明:
* a -- 包含兩個有序區間的數組
* start -- 第1個有序區間的起始地址。
* mid -- 第1個有序區間的結束地址。也是第2個有序區間的起始地址。
* end -- 第2個有序區間的結束地址。
*/
void merge(int a[], int start, int mid, int end)
{
int *tmp = (int *)malloc((end-start+1)*sizeof(int)); // tmp是彙總2個有序區的臨時區域
int i = start; // 第1個有序區的索引
int j = mid + 1; // 第2個有序區的索引
int k = 0; // 臨時區域的索引
while(i <= mid && j <= end)
{
if (a[i] <= a[j])
tmp[k++] = a[i++];
else
tmp[k++] = a[j++];
}
while(i <= mid)
tmp[k++] = a[i++];
while(j <= end)
tmp[k++] = a[j++];
// 將排序後的元素,全部都整合到數組a中。
for (i = 0; i < k; i++)
a[start + i] = tmp[i];
free(tmp);
}
/*
* 歸併排序(從上往下)
*
* 參數說明:
* a -- 待排序的數組
* start -- 數組的起始地址
* endi -- 數組的結束地址
*/
void merge_sort_up2down(int a[], int start, int end)
{
if(a==NULL || start >= end)
return ;
int mid = (end + start)/2;
merge_sort_up2down(a, start, mid); // 遞歸排序a[start...mid]
merge_sort_up2down(a, mid+1, end); // 遞歸排序a[mid+1...end]
// a[start...mid] 和 a[mid...end]是兩個有序空間,
// 將它們排序成一個有序空間a[start...end]
merge(a, start, mid, end);
}
/*
* 對數組a做若干次合併:數組a的總長度爲len,將它分爲若干個長度爲gap的子數組;
* 將"每2個相鄰的子數組" 進行合併排序。
*
* 參數說明:
* a -- 待排序的數組
* len -- 數組的長度
* gap -- 子數組的長度
*/
void merge_groups(int a[], int len, int gap)
{
int i;
int twolen = 2 * gap; // 兩個相鄰的子數組的長度
// 將"每2個相鄰的子數組" 進行合併排序。
for(i = 0; i+2*gap-1 < len; i+=(2*gap))
{
merge(a, i, i+gap-1, i+2*gap-1);
}
// 若 i+gap-1 < len-1,則剩餘一個子數組沒有配對。
// 將該子數組合併到已排序的數組中。
if ( i+gap-1 < len-1)
{
merge(a, i, i + gap - 1, len - 1);
}
}
/*
* 歸併排序(從下往上)
*
* 參數說明:
* a -- 待排序的數組
* len -- 數組的長度
*/
void merge_sort_down2up(int a[], int len)
{
int n;
if (a==NULL || len<=0)
return ;
for(n = 1; n < len; n*=2)
merge_groups(a, len, n);
}
void main()
{
int i;
int a[] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
int ilen = LENGTH(a);
printf("before sort:");
for (i=0; i<ilen; i++)
printf("%d ", a[i]);
printf("\n");
merge_sort_up2down(a, 0, ilen-1); // 歸併排序(從上往下)
//merge_sort_down2up(a, ilen); // 歸併排序(從下往上)
printf("after sort:");
for (i=0; i<ilen; i++)
printf("%d ", a[i]);
printf("\n");
}
快速排序(Quick Sort)
- 從數列中挑出一個元素,稱爲 “基準”(pivot);
- 重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊)。在這個分區退出之後,該基準就處於數列的中間位置。這個稱爲分區(partition)操作;
- 遞歸地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。
#include <stdio.h>
#include <stdlib.h>
// 數組長度
#define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )
/*
* 快速排序
*
* 參數說明:
* a -- 待排序的數組
* l -- 數組的左邊界(例如,從起始位置開始排序,則l=0)
* r -- 數組的右邊界(例如,排序截至到數組末尾,則r=a.length-1)
*/
void quick_sort(int a[], int l, int r)
{
if (l < r)
{
int i, j, x;
i = l;
j = r;
x = a[i];
while (i < j)
{
while (i < j && a[j] > x)
j--; // 從右向左找第一個小於x的數
if (i < j)
a[i++] = a[j];
while (i < j && a[i] < x)
i++; // 從左向右找第一個大於x的數
if (i < j)
a[j--] = a[i];
}
a[i] = x;
quick_sort(a, l, i - 1); /* 遞歸調用 */
quick_sort(a, i + 1, r); /* 遞歸調用 */
}
}
void main()
{
int i;
int a[] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
int ilen = LENGTH(a);
printf("before sort:");
for (i = 0; i < ilen; i++)
printf("%d ", a[i]);
printf("\n");
quick_sort(a, 0, ilen - 1);
printf("after sort:");
for (i = 0; i < ilen; i++)
printf("%d ", a[i]);
printf("\n");
system("PAUSE");
}
堆排序(Heap Sort)
堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序算法。堆積是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。
最大堆進行升序排序的基本思想:
- ① 初始化堆:將數列a[1…n]構造成最大堆。
- ② 交換數據:將a[1]和a[n]交換,使a[n]是a[1…n]中的最大值;然後將a[1…n-1]重新調整爲最大堆。 接着,將a[1]和a[n-1]交換,使a[n-1]是a[1…n-1]中的最大值;然後將a[1…n-2]重新調整爲最大值。 依次類推,直到整個數列都是有序的。
(最大堆的性質)在第一個元素的索引爲 0 的情形中:
性質一:索引爲i的左孩子的索引是 (2i+1);
性質二:索引爲i的右孩子的索引是(2i+2);
性質三:索引爲i的父結點的索引是 floor((i-1)/2);
#include <stdio.h>
#include <stdlib.h>
// 數組長度
#define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )
#define swap(a,b) (a^=b,b^=a,a^=b)
/*
* (最大)堆的向下調整算法
*
* 注:數組實現的堆中,第N個節點的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
* 其中,N爲數組下標索引值,如數組中第1個數對應的N爲0。
*
* 參數說明:
* a -- 待排序的數組
* start -- 被下調節點的起始位置(一般爲0,表示從第1個開始)
* end -- 截至範圍(一般爲數組中最後一個元素的索引)
*/
void maxheap_down(int a[], int start, int end)
{
int c = start; // 當前(current)節點的位置
int l = 2 * c + 1; // 左(left)孩子的位置
int tmp = a[c]; // 當前(current)節點的大小
for (; l <= end; c = l, l = 2 * l + 1)
{
// "l"是左孩子,"l+1"是右孩子
if (l < end && a[l] < a[l + 1])
l++; // 左右兩孩子中選擇較大者,即m_heap[l+1]
if (tmp >= a[l])
break; // 調整結束
else // 交換值
{
a[c] = a[l];
a[l] = tmp;
}
}
}
/*
* 堆排序(從小到大)
*
* 參數說明:
* a -- 待排序的數組
* n -- 數組的長度
*/
void heap_sort_asc(int a[], int n)
{
int i;
// 從(n/2-1) --> 0逐次遍歷。遍歷之後,得到的數組實際上是一個(最大)二叉堆。
for (i = n / 2 - 1; i >= 0; i--)
maxheap_down(a, i, n - 1);
// 從最後一個元素開始對序列進行調整,不斷的縮小調整的範圍直到第一個元素
for (i = n - 1; i > 0; i--)
{
// 交換a[0]和a[i]。交換後,a[i]是a[0...i]中最大的。
swap(a[0], a[i]);
// 調整a[0...i-1],使得a[0...i-1]仍然是一個最大堆。
// 即,保證a[i-1]是a[0...i-1]中的最大值。
maxheap_down(a, 0, i - 1);
}
}
/*
* (最小)堆的向下調整算法
*
* 注:數組實現的堆中,第N個節點的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
* 其中,N爲數組下標索引值,如數組中第1個數對應的N爲0。
*
* 參數說明:
* a -- 待排序的數組
* start -- 被下調節點的起始位置(一般爲0,表示從第1個開始)
* end -- 截至範圍(一般爲數組中最後一個元素的索引)
*/
void minheap_down(int a[], int start, int end)
{
int c = start; // 當前(current)節點的位置
int l = 2 * c + 1; // 左(left)孩子的位置
int tmp = a[c]; // 當前(current)節點的大小
for (; l <= end; c = l, l = 2 * l + 1)
{
// "l"是左孩子,"l+1"是右孩子
if (l < end && a[l] > a[l + 1])
l++; // 左右兩孩子中選擇較小者
if (tmp <= a[l])
break; // 調整結束
else // 交換值
{
a[c] = a[l];
a[l] = tmp;
}
}
}
/*
* 堆排序(從大到小)
*
* 參數說明:
* a -- 待排序的數組
* n -- 數組的長度
*/
void heap_sort_desc(int a[], int n)
{
int i;
// 從(n/2-1) --> 0逐次遍歷每。遍歷之後,得到的數組實際上是一個最小堆。
for (i = n / 2 - 1; i >= 0; i--)
minheap_down(a, i, n - 1);
// 從最後一個元素開始對序列進行調整,不斷的縮小調整的範圍直到第一個元素
for (i = n - 1; i > 0; i--)
{
// 交換a[0]和a[i]。交換後,a[i]是a[0...i]中最小的。
swap(a[0], a[i]);
// 調整a[0...i-1],使得a[0...i-1]仍然是一個最小堆。
// 即,保證a[i-1]是a[0...i-1]中的最小值。
minheap_down(a, 0, i - 1);
}
}
void main()
{
int i;
int a[] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
int ilen = LENGTH(a);
printf("before sort:");
for (i = 0; i < ilen; i++)
printf("%d ", a[i]);
printf("\n");
heap_sort_asc(a, ilen); // 升序排列
//heap_sort_desc(a, ilen); // 降序排列
printf("after sort:");
for (i = 0; i < ilen; i++)
printf("%d ", a[i]);
printf("\n");
system("PAUSE");
}
希爾排序(Shell Sort)
- 選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列個數k,對序列進行k 趟排序;
- 表中每個元素每次向前移動ti個元素距離的倍數進行比較,若大於(小於)則交換位置,直到本次移動的元素出界
- 重複第三步,直到ti取1,結束交換
#include <stdio.h>
#include <stdlib.h>
// 數組長度
#define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )
/*
* 希爾排序
*
* 參數說明:
* a -- 待排序的數組
* n -- 數組的長度
*/
void shell_sort1(int a[], int n)
{
int i, j, gap;
// gap爲步長,每次減爲原來的一半。
for (gap = n / 2; gap > 0; gap /= 2)
{
// 共gap個組,對每一組都執行直接插入排序
for (i = 0; i < gap; i++)
{
for (j = i + gap; j < n; j += gap)
{
// 如果a[j] < a[j-gap],則尋找a[j]位置,
//並將後面數據的位置都後移。
if (a[j] < a[j - gap])
{
int tmp = a[j];
int k = j - gap;
while (k >= 0 && a[k] > tmp)
{
a[k + gap] = a[k];
k -= gap;
}
a[k + gap] = tmp;
}
}
}
}
}
/*
* 對希爾排序中的單個組進行排序
*
* 參數說明:
* a -- 待排序的數組
* n -- 數組總的長度
* i -- 組的起始位置
* gap -- 組的步長
*
* 組是"從i開始,將相隔gap長度的數都取出"所組成的!
*/
void group_sort(int a[], int n, int i, int gap)
{
int j;
for (j = i + gap; j < n; j += gap)
{
// 如果a[j] < a[j-gap],則尋找a[j]位置,並將後面數據的位置都後移。
if (a[j] < a[j - gap])
{
int tmp = a[j];
int k = j - gap;
while (k >= 0 && a[k] > tmp)
{
a[k + gap] = a[k];
k -= gap;
}
a[k + gap] = tmp;
}
}
}
/*
* 希爾排序
*
* 參數說明:
* a -- 待排序的數組
* n -- 數組的長度
*/
void shell_sort2(int a[], int n)
{
int i, gap;
// gap爲步長,每次減爲原來的一半。
for (gap = n / 2; gap > 0; gap /= 2)
{
// 共gap個組,對每一組都執行直接插入排序
for (i = 0; i < gap; i++)
group_sort(a, n, i, gap);
}
}
void main()
{
int i;
int a[] = { -1, 9, 5, 0, -10, 44, 77, 33, -2, -99 };
int ilen = LENGTH(a);
printf("before sort:");
for (i = 0; i < ilen; i++)
printf("%d ", a[i]);
printf("\n");
//shell_sort1(a, ilen);
shell_sort2(a, ilen);
printf("after sort:");
for (i = 0; i < ilen; i++)
printf("%d ", a[i]);
printf("\n");
system("PAUSE");
}