9種排序算法總結
排序算法可以說是計算機專業學生要學習的最基礎的算法,但其實也是最重要的,現在大部分互聯網公司筆試麵試也都會涉及到排序算法的知識。除了了解思想之外,還應該動手寫一寫,分析一些具體思路、時間複雜度、空間複雜度和穩定性等。
我們麵試討論小分隊也簡單討論了一下排序算法,為了加深記憶,我自己也動手寫了一些代碼(Linux平台寫的,自己測試是通過了),並做一些分析(由於水平較水,代碼可能有誤!)。
9種排序算法分別為:選擇排序、冒泡排序、插入排序、希爾排序、歸並排序、堆排序、快速排序、計數排序、基數排序!
1. 選擇排序
基本思想:從第一個位置開始依次選擇該位置的元素,第i次掃描就可以選出第i小的元素,思想很簡單,現在用的較少。
特點:平均時間複雜度O(n^2),最壞時間複雜度O(n^2),額外空間O(1),不穩定排序(舉例:序列5 8 5 2 9, 第一遍選擇第1個元素5會和2交換,原序列中2個5的相對前後順序就被破壞了),n較小時較好!
代碼:
void select_sort(int *a, int n)
{
for(inti = 1; i <= n; i++) {
intmin_pos = i;
for(intj = i+1; j <= n; j++)
if(a[j] < a[min_pos])
min_pos = j;
if(min_pos != i) {
inttemp = a[i];
a[i] = a[min_pos];
a[min_pos] = temp;
}
}
}
2. 冒泡排序
基本思想:顧名思義,每一趟都通過相鄰元素兩兩比較,通過交換將較小的元素往前移動,一趟下來就可以將最小的元素(氣泡)移動到最前麵。一般會加一個標誌flag,若一趟掃描沒有任何元素交換,則說明序列已經有序,flag為false,直接退出。
特點:平均時間複雜度O(n^2),最壞時間複雜度O(n^2),額外空間O(1),穩定排序(因為比較和交換都是兩相鄰元素,相等時不交換),n較小時較好!
代碼:
void bubble_sort(int *a, int n)
{
boolflag =false;
for(inti = 1; i <= n; i++) {
for(intj = n; j > i; j--)
if(a[j] < a[j-1]) {
inttemp = a[j];
a[j] = a[j-1];
a[j-1] = temp;
flag =true;
}
if(!flag)
return;
}
}
3. 插入排序
基本思想:假定一個已排好序的序列和一個元素,隻需將該元素從序列末尾向前比較,找到第一個小於它的序列元素,排在其之後即可。思想類似於玩撲克牌時整理牌麵。
特點:平均時間複雜度O(n^2),最壞時間複雜度O(n^2),額外空間O(1),穩定排序(比較元素和序列時,找到序列中相等元素的話,排在其之後),序列大部分已排好序時(時間複雜度可提升至O(n))較好!
代碼:
void insert_sort(int *a, int n)
{
inttemp;
for(inti =2; i <= n; i++) {
intj = i - 1;
temp = a[i];
while(j >= 1) {
if(a[j] > temp) {
a[j+1] = a[j];
j--;
}else
break;
}
a[j+1] = temp;
}
}
4. 希爾排序
基本思想:插入排序的升級版(根據其特點:序列大部分已排好序時效率很高),將數據分為不同的組,先對每一組進行排序,然後對所有元素進行一次排序(即最後步長必須為1),步長的選擇是遞減的,比如5、3、1,現在一般使用D.E.Knuth分組方法(n很大是,用h(n+1)=3h(n)+1來分組,即1、4、13......)。
特點:平均時間複雜度O(n*logn),最壞時間複雜度O(n^s)(1<s<2),額外空間O(1),不穩定排序(相等元素在不同組裏,交換後相對順序可能改變)!
代碼:
void shell_sort(int *a, int n)
{ //我這裏步長為5、3、1,僅為舉例
for(intgap = 5; gap > 0; gap -= 2)
for(inti = gap + 1; i <=n; i++) {
intj = i - gap;
inttemp = a[i];
while(j >= 1) {
if(a[j] > temp) {
a[j + gap] = a[j];
j -= gap;
}else
break;
}
a[j+gap] = temp;
}
}
5. 歸並排序
基本思想:分治的思想,就是用遞歸先將序列分解成隻剩一個元素的子序列,然後逐漸向上進行合並,每次合並過程就是將兩個內部已排序的子序列進行合並排序,隻需O(n)時間。
特點:平均時間複雜度O(n*logn),最壞時間複雜度O(n*logn),額外空間O(n)(另外需要一個數組),穩定排序,當n較大時較好(當也不能太大,用了遞歸就要考慮棧溢出)!
代碼:
int b[MAX] = {0};
void merge(int *a,intlow, int mid, inthigh)
{
inti = low, j = mid + 1;//左邊和右邊的初始位置
intk = i;
while(i <= mid && j <= high) {
if(a[i] <= a[j]) {
b[k++] = a[i];
i++;
}else{
b[k++] = a[j];
j++;
}
}
while(i <= mid){
b[k++] = a[i++];
}
while(j <= high){
b[k++] = a[j++];
}
for(intx = 1, i = low; x <= high-low+1; x++, i++)
a[i] = b[i];
}
voidmerge_sort(int*a,int low, int high)
{
intmid;
if(low < high) {
mid = (low + high) / 2;
merge_sort(a, low, mid);
merge_sort(a, mid+1, high);
merge(a, low, mid, high);
}
}
6. 堆排序
基本思想:利用最大堆的性質——父節點擁有最大值,所以不斷的將堆的根節點與最後節點交換,減小堆長度,然後再恢複堆性質,堆排序主要就是建立最大堆和不斷恢複堆性質兩個過程。堆排序不需要用到遞歸,所以適合海量數據處理,同時堆還可以用於優先級隊列。
特點:平均時間複雜度O(n*logn),最壞時間複雜度O(n*logn),額外空間O(1),不穩定排序(涉及根節點與最後節點的交換,可能會破壞兩相等元素的相對位置!),當n較大時較好(海量數據)!
代碼:
void max_heapify(int *a, int p, int n)
{
intleft = 2 * p;
intright = 2 * p + 1;
intlarge = p;
if(left <= n && a[left] > a[p])
large = left;
if(right <= n && a[right] > a[large])
large = right;
if(large != p) {
inttemp = a[p];
a[p] = a[large];
a[large] = temp;
max_heapify(a, large, n);
}
}
voidheap_sort(int*a,int n)
{
//build_max_heap
for(inti = n/2; i > 0; i--)
max_heapify(a, i, n);
inttemp;
while(n > 1){
temp = a[n];
a[n] = a[1];
a[1] = temp;
--n;
max_heapify(a, 1, n);
}
}
7. 快速排序
基本思想:快排是目前使用最多的排序算法,每次都是先選擇一個位置的元素(可以為序列的最左或最右位置)作為中間值,將比其小的元素放在其左邊,比其大的元素放在右邊,然後遞歸對其左邊和右邊的子序列進行相同操作,直到子序列為單個元素。
特點:平均時間複雜度O(n*logn),最壞時間複雜度O(n^2)(序列基本有序時,退化為冒泡排序),額外空間O(logn),不穩定排序(舉例:序列為 5 3 3 4 3 8 9 10 11, 現在中樞元素5和3(第5個元素,下標從1開始計)交換就會把元素3的穩定性打亂),當n較大時較好(當也不能太大,用了遞歸就要考慮棧溢出)!
代碼:
void quick_sort(int *a, int p, int r)
{
if(p < r) {
inttemp;
intx = a[r];
inti = p - 1;
for(intj = p; j < r; j++)
if(a[j] < x) {
i++;
temp = a[j];
a[j] = a[i];
a[i] = temp;
}
temp = a[i+1];
a[i+1] = a[r];
a[r] = temp;
quick_sort(a, p, i);
quick_sort(a, i+2, r);
}
}
8. 計數排序
基本思想:假定輸入是有一個小範圍內的整數構成的(比如年齡等),利用額外的數組去記錄元素應該排列的位置,思想比較簡單,看代碼即可了解。
特點:在一定限製下時間複雜度為O(n),額外空間O(n)(需要兩個數組),穩定排序!
代碼:
int b[MAX] = {0};
int c[MAX] = {0};
void counting_sort(int *a, int n)
{
for(inti=1; i <= n; i++)
c[a[i]]++; //c[i]包含等於i的元素個數
for(inti=1; i < MAX; i++)
c[i] += c[i-1];//c[i]包含小於等於i的元素個數
for(inti = n; i>0; i--){
b[c[a[i]]] = a[i];
c[a[i]]--;
}
for(inti = 1; i <=n; i++)
a[i] = b[i];
}
9. 基數排序
基本思想:隻適用於整數排序,確定序列中元素的最大位數d,隻要進行d次循環,從低位開始根據相應位置的數進行排序。(我的代碼中具體排序是參考了計數排序,數據結構中還可以用鏈式相關的方法)。
特點:在一定限製下時間複雜度為O(n),額外空間O(n)(需要兩個數組),穩定排序!
代碼:
int b[MAX] = {0};
int counter[10] = {0};
int get_value(int v, int d) //獲取第d位上的值
{
for(inti = 1; i < d; i++)
v = v/10;
returnv%10;
}
//隻能排序d位的十進製數
voidradix_sort(int*a,int n, int d)
{
intx;
for(intk = 1; k <= d; k++) {
for(inti = 0; i < 10; i++)
counter[i] = 0;//注意,一定要清零
for(inti = 1; i <= n; i++) {
x = get_value(a[i], k);
counter[x]++;
}
for(inti = 1; i < 10; i++)
counter[i] += counter[i-1];
for(inti = n; i > 0; i--) {
x = get_value(a[i], k);
b[counter[x]] = a[i];
counter[x]--;
}
for(inti = 1; i <= n; i++)
a[i] = b[i];
}
}
排序總結
穩定性:選擇排序、快速排序、希爾排序、堆排序不是穩定的排序算法,而冒泡排序、插入排序、歸並排序和基數排序是穩定的排序算法。
快速排序算法使用最廣泛,大數據量時適合使用快速排序、歸並排序和堆排序,需要O(n)時間複雜度時(當然要考慮數值範圍的限製),可以考慮使用計數排序、基數排序、桶排序(上麵未介紹,思想很簡單,假設數據分布均勻!)等。
最後是我用來測試排序算法的main函數,非常簡單!
#include <iostream>
usingnamespacestd;
constintMAX = 255;
int main ()
{
intn;
inta[MAX];
cin >> n;
for(inti = 1; i <= n; i++)
cin >> a[i];
cout <<"Before sort:";
for(inti = 1; i <= n; i++)
cout << a[i] <<" ";
cout << endl;
//radix_sort(a, n, 2);
//select_sort(a, n);
//insert_sort(a, n);
//bubble_sort(a, n);
//quick_sort(a, 1, n);
//heap_sort(a, n);
//merge_sort(a, 1, n);
//counting_sort(a, n);
//shell_sort(a, n);
cout <<"Sort:";
for(inti = 1; i <= n; i++)
cout << a[i] <<" ";
cout << endl;
return0;
}
排序算法基本上就總結如上,告誡自己,不能死記硬背!要理解思想,同時要注意實現上的一些技巧!
最後更新:2017-04-03 20:51:30