原文連接:http://www.javashuo.com/article/p-apvjfrqu-dq.htmlhtml
注意:算法
原文中的算法實現都是基於JS,本文所有修改成C實現,而且統一排序接口,另外增長了一些描述信息,後面會持續更新本文。shell
十種常見排序算法能夠分爲兩大類:編程
這裏提供兩個算法可視化網站,方便理解這些排序算法:api
https://visualgo.net/en/sorting數組
https://www.cs.usfca.edu/~galles/visualization/ComparisonSort.html數據結構
兩個連接均可以本身控制元素的移動和流程,十分方便。編程語言
推薦第一個連接,能夠本身設定須要排序的元素,同時能夠查看每一次移動相應的代碼片斷。不只如此,這個網站還提供了不少其餘常見算法的可視化模型,具體包括以下這些算法:ide
enjoy!函數
另外提供一個,深刻淺出的算法和數據結構教程:
文章淺顯易懂,而且提供了常見算法的大部分語言實現。同時附帶大量圖解,在爭取理解算法的基礎上,再來具體使用某個編程語言來實現這個算法。
冒泡排序是一種簡單的排序算法。它重複地走訪過要排序的數列,一次比較兩個元素,若是它們的順序錯誤就把它們交換過來。走訪數列的工做是重複地進行直到沒有再須要交換,也就是說該數列已經排序完成。這個算法的名字由來是由於越大的元素會經由交換慢慢「浮」到數列的末尾
1.2 動圖演示
//冒泡排序 #include<stdio.h> void swap(int *a,int *b){ int tmp =*a; *a=*b; *b=tmp; } void sort(int size,int arr[]){ for(int i =1;i<size ;i++){ for(int j=1;j<size-i+1;j++){ if(arr[j]<arr[j-1]){ swap(arr+j-1,arr+j); } } } } void show(int size,int arr[]){ for(int i=0;i<size;i++){ if(i!=0){ printf(" "); } printf("%d",arr[i]); } } int main(){ int a=1,b=2; swap(&a,&b); printf("a=%d b=%d\n",a,b); int arr[10]={2,1,4,3,7,5,6,8,9,0}; show(10,arr); sort(10,arr); printf("\n"); show(10,arr); }
選擇排序(Selection-sort)是一種簡單直觀的排序算法。它的工做原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,而後,再從剩餘未排序元素中繼續尋找最小(大)元素,而後放到已排序序列的末尾。以此類推,直到全部元素均排序完畢。
n個記錄的直接選擇排序可通過n-1趟直接選擇排序獲得有序結果。具體算法描述以下:
//選擇排序 #include<stdio.h> void swap(int *a,int *b) { int tmp =*a; *a=*b; *b=tmp; } void sort(int size,int arr[]) { int min; for(int i =0; i<size ; i++) { min = i; for(int j = i;j<size;j++){ if(arr[j]<arr[min]){ min = j; } } swap(arr+min,arr+i); } } void show(int size,int arr[]) { for(int i=0; i<size; i++) { if(i!=0) { printf(" "); } printf("%d",arr[i]); } } int main() { int a=1,b=2; swap(&a,&b); printf("a=%d b=%d\n",a,b); int arr[10]= {2,1,4,3,7,5,6,8,9,0}; show(10,arr); sort(10,arr); printf("\n"); show(10,arr); }
表現最穩定的排序算法之一,由於不管什麼數據進去都是O(n^2)的時間複雜度,因此用到它的時候,數據規模越小越好。惟一的好處可能就是不佔用額外的內存空間了吧。理論上講,選擇排序可能也是平時排序通常人想到的最多的排序方法了吧。
插入排序(Insertion-Sort)的算法描述是一種簡單直觀的排序算法。它的工做原理是經過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。
通常來講,插入排序都採用in-place在數組上實現。具體算法描述以下:
從第一個元素開始,該元素能夠認爲已經被排序;
取出下一個元素,在已經排序的元素序列中從後向前掃描;
若是該元素(已排序)大於新元素,將該元素移到下一位置;
重複步驟3,直到找到已排序的元素小於或者等於新元素的位置;
將新元素插入到該位置後;
重複步驟2~5。
//插入排序 #include<stdio.h> void swap(int *a,int *b) { int tmp =*a; *a=*b; *b=tmp; } void sort(int size,int arr[]) { int i,j; for (i = 1; i < size; i++) { int tmp = arr[i]; for (j = i; j > 0 && arr[j - 1] > tmp; j--) { arr[j] = arr[j - 1]; } arr[j] = tmp; } } void show(int size,int arr[]) { for(int i=0; i<size; i++) { if(i!=0) { printf(" "); } printf("%d",arr[i]); } } int main() { int a=1,b=2; swap(&a,&b); printf("a=%d b=%d\n",a,b); int arr[10]= {2,1,4,3,7,5,6,8,9,0}; show(10,arr); sort(10,arr); printf("\n"); show(10,arr); }
插入排序在實現上,一般採用in-place排序(即只需用到O(1)的額外空間的排序),於是在從後向前掃描過程當中,須要反覆把已排序元素逐步向後挪位,爲最新元素提供插入空間。
簡單來講就像摸撲克牌同樣,抓一張牌,插到手中牌的合適的位置,這就是插入排序。
1959年Shell發明,第一個突破O(n^2)的排序算法,是簡單插入排序的改進版。它與插入排序的不一樣之處在於,它會優先比較距離較遠的元素。希爾排序又叫縮小增量排序。
先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序,具體算法描述:
//希爾排序 #include<stdio.h> void swap(int *a,int *b) { int tmp =*a; *a=*b; *b=tmp; } void sort(int size,int arr[]) { for (int gap = size/2; gap > 0; gap /= 2) { for (int i = gap; i < size; i += 1) { int temp = arr[i]; int j; for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) arr[j] = arr[j - gap]; arr[j] = temp; } } } void show(int size,int arr[]) { for(int i=0; i<size; i++) { if(i!=0) { printf(" "); } printf("%d",arr[i]); } } int main() { int a=1,b=2; swap(&a,&b); printf("a=%d b=%d\n",a,b); int arr[10]= {2,1,4,3,7,5,6,8,9,0}; show(10,arr); sort(10,arr); printf("\n"); show(10,arr); }
希爾排序,是把序列按照下標的必定增量分組,而後對每組進行插入排序。而後讓增量逐漸減小,當增量爲1時,整個序列剛好被分爲一組,算法結束。
什麼意思呢,就是說首先肯定一個增量,假如增量是4,那麼a[0],a[4],a[8]會被分爲一組,a[1],a[5],a[9]會被分爲一組,a[2],a[6],a[10]被分爲一組,a[3]....而後每一個組各自進行插入排序,排序後每一個組就是有序的,而後減小增量,從新分組、排序。。。直到增量爲1。希爾排序經過這種策略使得整個數組在初始階段達到從宏觀上看基本有序,小的基本在前,大的基本在後。當增量爲1的時候,這些序列大多數狀況下已經基本有序,只須要進行微調便可。
希爾排序的核心在於間隔序列的設定。既能夠提早設定好間隔序列,也能夠動態的定義間隔序列。動態定義間隔序列的算法是《算法(第4版)》的合著者Robert Sedgewick提出的。
歸併排序是創建在歸併操做上的一種有效的排序算法。該算法是採用分治法(Divide and Conquer)的一個很是典型的應用。將已有序的子序列合併,獲得徹底有序的序列;即先使每一個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲2-路歸併。
//歸併排序 #include<stdio.h> void swap(int *a,int *b) { int tmp =*a; *a=*b; *b=tmp; } void merge(int arr[], int l, int m, int r) { int i, j, k; int n1 = m - l + 1; int n2 = r - m; int L[n1], R[n2]; for (i = 0; i < n1; i++) L[i] = arr[l + i]; for (j = 0; j < n2; j++) R[j] = arr[m + 1+ j]; i = 0; j = 0; k = l; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k] = L[i]; i++; } else { arr[k] = R[j]; j++; } k++; } while (i < n1) { arr[k] = L[i]; i++; k++; } while (j < n2) { arr[k] = R[j]; j++; k++; } } void mergeSort(int arr[], int l, int r) { if (l < r) { int m = l+(r-l)/2; mergeSort(arr, l, m); mergeSort(arr, m+1, r); merge(arr, l, m, r); } } void sort(int size,int arr[]) { mergeSort(arr,0,size-1); } void show(int size,int arr[]) { for(int i=0; i<size; i++) { if(i!=0) { printf(" "); } printf("%d",arr[i]); } } int main() { int a=1,b=2; swap(&a,&b); printf("a=%d b=%d\n",a,b); int arr[10]= {2,1,4,3,7,5,6,8,9,0}; show(10,arr); sort(10,arr); printf("\n"); show(10,arr); }
歸併排序是一種穩定的排序方法。和選擇排序同樣,歸併排序的性能不受輸入數據的影響,但表現比選擇排序好的多,由於始終都是O(nlogn)的時間複雜度。代價是須要額外的內存空間。
快速排序的基本思想:經過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另外一部分的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。
快速排序使用分治法來把一個串(list)分爲兩個子串(sub-lists)。具體算法描述以下:
//快速排序 #include<stdio.h> void swap(int *a,int *b) { int tmp =*a; *a=*b; *b=tmp; } int partition (int arr[], int low, int high) { int pivot = arr[high]; int i = (low - 1); for (int j = low; j <= high- 1; j++) { if (arr[j] <= pivot) { i++; swap(&arr[i], &arr[j]); } } swap(&arr[i + 1], &arr[high]); return (i + 1); } void quickSort(int arr[], int low, int high) { if (low < high) { int pi = partition(arr, low, high); quickSort(arr, low, pi - 1); quickSort(arr, pi + 1, high); } } void sort(int size,int arr[]) { quickSort(arr,0,size-1); } void show(int size,int arr[]) { for(int i=0; i<size; i++) { if(i!=0) { printf(" "); } printf("%d",arr[i]); } } int main() { int a=1,b=2; swap(&a,&b); printf("a=%d b=%d\n",a,b); int arr[10]= {2,1,4,3,7,5,6,8,9,0}; show(10,arr); sort(10,arr); printf("\n"); show(10,arr); }
堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序算法。堆積是一個近似徹底二叉樹的結構,並同時知足堆積的性質:即子結點的鍵值或索引老是小於(或者大於)它的父節點。
//堆排序 #include<stdio.h> void swap(int *a,int *b) { int tmp =*a; *a=*b; *b=tmp; } //創建堆 void heapify(int arr[], int n, int i) { int largest = i; // 將最大元素設置爲堆頂元素 int l = 2*i + 1; // left = 2*i + 1 int r = 2*i + 2; // right = 2*i + 2 // 若是 left 比 root 大的話 if (l < n && arr[l] > arr[largest]) largest = l; // I若是 right 比 root 大的話 if (r < n && arr[r] > arr[largest]) largest = r; if (largest != i) { swap(arr+i, arr+largest); // 遞歸地定義子堆 heapify(arr, n, largest); } } void sort(int size,int arr[]) { // 創建堆 for (int i = size / 2 - 1; i >= 0; i--) heapify(arr, size, i); // 一個個從堆頂取出元素 for (int i=size-1; i>=0; i--) { swap(arr, arr+i); heapify(arr, i, 0); } } void show(int size,int arr[]) { for(int i=0; i<size; i++) { if(i!=0) { printf(" "); } printf("%d",arr[i]); } } int main() { int a=1,b=2; swap(&a,&b); printf("a=%d b=%d\n",a,b); int arr[10]= {2,1,4,3,7,5,6,8,9,0}; show(10,arr); sort(10,arr); printf("\n"); show(10,arr); }
計數排序不是基於比較的排序算法,其核心在於將輸入的數據值轉化爲鍵存儲在額外開闢的數組空間中。 做爲一種線性時間複雜度的排序,計數排序要求輸入的數據必須是有肯定範圍的整數。
//計數排序 #include<stdio.h> void swap(int *a,int *b) { int tmp =*a; *a=*b; *b=tmp; } void sort(int size,int arr[]) { int output[10]; int max = arr[0]; for (int i = 1; i < size; i++) { if (arr[i] > max) max = arr[i]; } // The size of count must be at least the (max+1) but // we cannot assign declare it as int count(max+1) in C as // it does not support dynamic memory allocation. // So, its size is provided statically. int count[10]; for (int i = 0; i <= max; ++i) { count[i] = 0; } for (int i = 0; i < size; i++) { count[arr[i]]++; } for (int i = 1; i <= max; i++) { count[i] += count[i - 1]; } for (int i = size - 1; i >= 0; i--) { output[count[arr[i]] - 1] = arr[i]; count[arr[i]]--; } for (int i = 0; i < size; i++) { arr[i] = output[i]; } } void show(int size,int arr[]) { for(int i=0; i<size; i++) { if(i!=0) { printf(" "); } printf("%d",arr[i]); } } int main() { int a=1,b=2; swap(&a,&b); printf("a=%d b=%d\n",a,b); int arr[10]= {2,1,4,3,7,5,6,8,9,0}; show(10,arr); sort(10,arr); printf("\n"); show(10,arr); }
計數排序是一個穩定的排序算法。當輸入的元素是 n 個 0到 k 之間的整數時,時間複雜度是O(n+k),空間複雜度也是O(n+k),其排序速度快於任何比較排序算法。當k不是很大而且序列比較集中時,計數排序是一個頗有效的排序算法。
桶排序是計數排序的升級版。它利用了函數的映射關係,高效與否的關鍵就在於這個映射函數的肯定。桶排序 (Bucket sort)的工做的原理:假設輸入數據服從均勻分佈,將數據分到有限數量的桶裏,每一個桶再分別排序(有可能再使用別的排序算法或是以遞歸方式繼續使用桶排序進行排)。
//桶排序 #include<stdio.h> void swap(int *a,int *b) { int tmp =*a; *a=*b; *b=tmp; } int getMax(int array[], int size) { int max = array[0]; for (int i = 1; i < size; i++) if (array[i] > max) max = array[i]; return max; } void sort(int size,int arr[]) { // The size of bucket must be at least the (max+1) but // we cannot assign declare it as int bucket(max+1) in C as // it does not support dynamic memory allocation. // So, its size is provided statically. int bucket[10]; const int max = getMax(arr, size); for (int i = 0; i <= max; i++) { bucket[i] = 0; } for (int i = 0; i < size; i++) { bucket[arr[i]]++; } for (int i = 0, j = 0; i <= max; i++) { while (bucket[i] > 0) { arr[j++] = i; bucket[i]--; } } } void show(int size,int arr[]) { for(int i=0; i<size; i++) { if(i!=0) { printf(" "); } printf("%d",arr[i]); } } int main() { int a=1,b=2; swap(&a,&b); printf("a=%d b=%d\n",a,b); int arr[10]= {2,1,4,3,7,5,6,8,9,0}; show(10,arr); sort(10,arr); printf("\n"); show(10,arr); }
桶排序最好狀況下使用線性時間O(n),桶排序的時間複雜度,取決與對各個桶之間數據進行排序的時間複雜度,由於其它部分的時間複雜度都爲O(n)。很顯然,桶劃分的越小,各個桶之間的數據越少,排序所用的時間也會越少。但相應的空間消耗就會增大。
基數排序是按照低位先排序,而後收集;再按照高位排序,而後再收集;依次類推,直到最高位。有時候有些屬性是有優先級順序的,先按低優先級排序,再按高優先級排序。最後的次序就是高優先級高的在前,高優先級相同的低優先級高的在前。
//基數排序 #include<stdio.h> void swap(int *a,int *b) { int tmp =*a; *a=*b; *b=tmp; } int getMax(int array[], int n) { int max = array[0]; for (int i = 1; i < n; i++) if (array[i] > max) max = array[i]; return max; } void countingSort(int array[], int size, int place) { int output[size + 1]; int max = (array[0] / place) % 10; for (int i = 1; i < size; i++) { if (((array[i] / place) % 10) > max) max = array[i]; } int count[max + 1]; for (int i = 0; i < max; ++i) count[i] = 0; for (int i = 0; i < size; i++) count[(array[i] / place) % 10]++; for (int i = 1; i < 10; i++) count[i] += count[i - 1]; for (int i = size - 1; i >= 0; i--) { output[count[(array[i] / place) % 10] - 1] = array[i]; count[(array[i] / place) % 10]--; } for (int i = 0; i < size; i++) array[i] = output[i]; } void sort(int size,int arr[]) { int max = getMax(arr, size); for (int place = 1; max / place > 0; place *= 10) countingSort(arr, size, place); } void show(int size,int arr[]) { for(int i=0; i<size; i++) { if(i!=0) { printf(" "); } printf("%d",arr[i]); } } int main() { int a=1,b=2; swap(&a,&b); printf("a=%d b=%d\n",a,b); int arr[10]= {2,1,4,3,7,5,6,8,9,0}; show(10,arr); sort(10,arr); printf("\n"); show(10,arr); }
基數排序基於分別排序,分別收集,因此是穩定的。但基數排序的性能比桶排序要略差,每一次關鍵字的桶分配都須要O(n)的時間複雜度,並且分配以後獲得新的關鍵字序列又須要O(n)的時間複雜度。假如待排數據能夠分爲d個關鍵字,則基數排序的時間複雜度將是O(d*2n) ,固然d要遠遠小於n,所以基本上仍是線性級別的。
基數排序的空間複雜度爲O(n+k),其中k爲桶的數量。通常來講n>>k,所以額外空間須要大概n個左右。