簡介算法
其中排序算法總結以下:shell
交換排序的基本思想都爲經過比較兩個數的大小,當知足某些條件時對它進行交換從而達到排序的目的。數組
基本思想:比較相鄰的兩個數,若是前者比後者大,則進行交換。每一輪排序結束,選出一個未排序中最大的數放到數組後面。性能
#include<stdio.h> //冒泡排序算法 void bubbleSort(int *arr, int n) { for (int i = 0; i<n - 1; i++) for (int j = 0; j < n - i - 1; j++) { //若是前面的數比後面大,進行交換 if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } int main() { int arr[] = { 10,6,5,2,3,8,7,4,9,1 }; int n = sizeof(arr) / sizeof(int); bubbleSort(arr, n); printf("排序後的數組爲:\n"); for (int j = 0; j<n; j++) printf("%d ", arr[j]); printf("\n"); return 0;
分析:優化
最差時間複雜度爲O(n^2),平均時間複雜度爲O(n^2)。穩定性:穩定。輔助空間O(1)。ui
升級版冒泡排序法:經過從低到高選出最大的數放到後面,再從高到低選出最小的數放到前面,如此反覆,直到左邊界和右邊界重合。當數組中有已排序好的數時,這種排序比傳統冒泡排序性能稍好。spa
#include<stdio.h> //升級版冒泡排序算法 void bubbleSort_1(int *arr, int n) { //設置數組左右邊界 int left = 0, right = n - 1; //當左右邊界未重合時,進行排序 while (left<right) { //從左到右遍歷選出最大的數放到數組右邊 for (int i =left; i < right; i++) { if (arr[i] > arr[i + 1]) { int temp = arr[i]; arr[i] = arr[i + 1]; arr[i + 1] = temp; } } right--; //從右到左遍歷選出最小的數放到數組左邊 for (int j = right;j> left; j--) { if (arr[j + 1] < arr[j]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } left++; } } int main() { int arr[] = { 10,6,5,2,3,8,7,4,9,1 }; int n = sizeof(arr) / sizeof(int); bubbleSort_1(arr, n); printf("排序後的數組爲:\n"); for (int j = 0; j<n; j++) printf("%d ", arr[j]); printf("\n"); return 0; }
基本思想:選取一個基準元素,一般爲數組最後一個元素(或者第一個元素)。從前向後遍歷數組,當遇到小於基準元素的元素時,把它和左邊第一個大於基準元素的元素進行交換。在利用分治策略從已經分好的兩組中分別進行以上步驟,直到排序完成。下圖表示了這個過程。code
#include<stdio.h> void swap(int *x, int *y) { int tmp = *x; *x = *y; *y = tmp; } //分治法把數組分紅兩份 int patition(int *a, int left,int right) { int j = left; //用來遍歷數組 int i = j - 1; //用來指向小於基準元素的位置 int key = a[right]; //基準元素 //從左到右遍歷數組,把小於等於基準元素的放到左邊,大於基準元素的放到右邊 for (; j < right; ++j) { if (a[j] <= key) swap(&a[j], &a[++i]); } //把基準元素放到中間 swap(&a[right], &a[++i]); //返回數組中間位置 return i; } //快速排序 void quickSort(int *a,int left,int right) { if (left>=right) return; int mid = patition(a,left,right); quickSort(a, left, mid - 1); quickSort(a, mid + 1, right); } int main() { int a[] = { 10,6,5,7,12,8,1,3,11,4,2,9,16,13,15,14 }; int n = sizeof(a) / sizeof(int); quickSort(a, 0,n-1); printf("排序好的數組爲:"); for (int l = 0; l < n; l++) { printf("%d ", a[l]); } printf("\n"); return 0; }
分析: blog
最差時間複雜度:每次選取的基準元素都爲最大(或最小元素)致使每次只劃分了一個分區,須要進行n-1次劃分才能結束遞歸,故複雜度爲O(n^2);最優時間複雜度:每次選取的基準元素都是中位數,每次都劃分出兩個分區,須要進行logn次遞歸,故時間複雜度爲O(nlogn);平均時間複雜度:O(nlogn)。穩定性:不穩定的。輔助空間:O(nlogn)。排序
當數組元素基本有序時,快速排序將沒有任何優點,基本退化爲冒泡排序,可在選取基準元素時選取中間值進行優化。
基本思想:和交換排序不一樣的是它不用進行交換操做,而是用一個臨時變量存儲當前值。當前面的元素比後面大時,先把後面的元素存入臨時變量,前面元素的值放到後面元素位置,再到最後把其值插入到合適的數組位置。
#include<stdio.h> void InsertSort(int *a, int n) { int tmp = 0; for (int i = 1; i < n; i++) { int j = i - 1; if (a[i] < a[j]) { tmp = a[i]; a[i] = a[j]; while (tmp < a[j-1]) { a[j] = a[j-1]; j--; } a[j] = tmp; } } } int main() { int a[] = { 11,7,9,22,10,18,4,43,5,1,32}; int n = sizeof(a)/sizeof(int); InsertSort(a, n); printf("排序好的數組爲:"); for (int i = 0; i < n; i++) { printf(" %d", a[i]); } printf("\n"); return 0; }
分析:
最壞時間複雜度爲數組爲逆序時,爲O(n^2)。最優時間複雜度爲數組正序時,爲O(n)。平均時間複雜度爲O(n^2)。輔助空間O(1)。穩定性:穩定。
基本思想爲在直接插入排序的思想下設置一個最小增量dk,剛開始dk設置爲n/2。進行插入排序,隨後再讓dk=dk/2,再進行插入排序,直到dk爲1時完成最後一次插入排序,此時數組完成排序。
#include<stdio.h> // 進行插入排序 // 初始時從dk開始增加,每次比較步長爲dk void Insrtsort(int *a, int n,int dk) { for (int i = dk; i < n; ++i) { int j = i - dk; if (a[i] < a[j]) { // 比較先後數字大小 int tmp = a[i]; // 做爲臨時存儲 a[i] = a[j]; while (a[j] > tmp) { // 尋找tmp的插入位置 a[j+dk] = a[j]; j -= dk; } a[j+dk] = tmp; // 插入tmp } } } void ShellSort(int *a, int n) { int dk = n / 2; // 設置初始dk while (dk >= 1) { Insrtsort(a, n, dk); dk /= 2; } } int main() { int a[] = { 5,12,35,42,11,2,9,41,26,18,4 }; int n = sizeof(a) / sizeof(int); ShellSort(a, n); printf("排序好的數組爲:"); for (int j = 0; j < n; j++) { printf("%d ", a [j]); } return 0; }
分析:
最壞時間複雜度爲O(n^2);最優時間複雜度爲O(n);平均時間複雜度爲O(n^1.3)。輔助空間O(1)。穩定性:不穩定。希爾排序的時間複雜度與選取的增量有關,選取合適的增量可減小時間複雜度。
基本思想:依次選出數組最小的數放到數組的前面。首先從數組的第二個元素開始日後遍歷,找出最小的數放到第一個位置。再從剩下數組中找出最小的數放到第二個位置。以此類推,直到數組有序。
#include<stdio.h> void SelectSort(int *a, int n) { for (int i = 0; i < n; i++) { int key = i; // 臨時變量用於存放數組最小值的位置 for (int j = i + 1; j < n; j++) { if (a[j] < a[key]) { key = j; // 記錄數組最小值位置 } } if (key != i) { int tmp = a[key]; a[key] = a[i]; a[i] = tmp; // 交換最小值 } } } int main() { int a[] = { 12,4,15,2,6,22,8,10,1,33,45,24,7 }; int n = sizeof(a) / sizeof(int); SelectSort(a, n); printf("排序好的數組爲: "); for (int k = 0; k < n; k++) printf("%d ", a[k]); printf("\n"); return 0; }
分析:
最差、最優、平均時間複雜度都爲O(n^2)。輔助空間爲O(1)。穩定性:不穩定。
基本思想:先把數組構形成一個大頂堆(父親節點大於其子節點),而後把堆頂(數組最大值,數組第一個元素)和數組最後一個元素交換,這樣就把最大值放到了數組最後邊。把數組長度n-1,再進行構造堆,把剩餘的第二大值放到堆頂,輸出堆頂(放到剩餘未排序數組最後面)。依次類推,直至數組排序完成。
下圖爲堆結構及其在數組中的表示。能夠知道堆頂的元素爲數組的首元素,某一個節點的左孩子節點爲其在數組中的位置*2,其右孩子節點爲其在數組中的位置*2+1,其父節點爲其在數組中的位置/2(假設數組從1開始計數)。
下圖爲怎麼把一個無序的數組構形成一個大堆頂結構的數組的過程,注意其是從下到上,從右到左,從右邊第一個非葉子節點開始構建的。
#include<stdio.h> // 建立大堆頂,i爲當節點,n爲堆的大小 // 從第一個非葉子結點i從下至上,從右至左調整結構 // 從兩個兒子節點中選出較大的來與父親節點進行比較 // 若是兒子節點比父親節點大,則進行交換 void CreatHeap(int a[], int i, int n) { // 注意數組是從0開始計數,因此左節點爲2*i+1,右節點爲2*i+2 for (; i >= 0; --i) { int left = i * 2 + 1; //左子樹節點 int right = i * 2 + 2; //右子樹節點 int j = 0; //選出左右子節點中最大的 if (right < n) { a[left] > a[right] ? j= left : j = right; } else j = left; //交換子節點與父節點 if (a[j] > a[i]) { int tmp = a[i]; a[i] = a[j]; a[j] = tmp; } } } // 進行堆排序,依次選出最大值放到最後面 void HeapSort(int a[], int n) { //初始化構造堆 CreatHeap(a, n/2-1, n); //交換第一個元素和最後一個元素後,堆的大小減1 for (int j = n-1; j >= 0; j--) { //最後一個元素和第一個元素進行交換 int tmp = a[0]; a[0] = a[j]; a[j] = tmp; int i = j / 2 - 1; CreatHeap(a, i, j); } } int main() { int a[] = { 10,6,5,7,12,8,1,3,11,4,2,9,16,13,15,14 }; int n = sizeof(a) / sizeof(int); HeapSort(a, n); printf("排序好的數組爲:"); for (int l = 0; l < n; l++) { printf("%d ", a[l]); } printf("\n"); return 0; }
分析:
最差、最優‘平均時間複雜度都爲O(nlogn),其中堆的每次建立重構花費O(lgn),須要建立n次。輔助空間O(1)。穩定性:不穩定。
基本思想:歸併算法應用到分治策略,簡單說就是把一個答問題分解成易於解決的小問題後一個個解決,最後在把小問題的一步步合併成總問題的解。這裏的排序應用遞歸來把數組分解成一個個小數組,直到小數組的數位有序,在把有序的小數組兩兩合併而成有序的大數組。
下圖爲展現如何歸併的合成一個數組。
下圖展現了歸併排序過程各階段的時間花費。
#include <stdio.h> #include <limits.h> // 合併兩個已排好序的數組 void Merge(int a[], int left, int mid, int right) { int len = right - left + 1; // 數組的長度 int *temp = new int[len]; // 分配個臨時數組 int k = 0; int i = left; // 前一數組的起始元素 int j = mid + 1; // 後一數組的起始元素 while (i <= mid && j <= right) { // 選擇較小的存入臨時數組 temp[k++] = a[i] <= a[j] ? a[i++] : a[j++]; } while (i <= mid) { temp[k++] = a[i++]; } while (j <= right) { temp[k++] = a[j++]; } for (int k = 0; k < len; k++) { a[left++] = temp[k]; } } // 遞歸實現的歸併排序 void MergeSort(int a[], int left, int right) { if (left == right) return; int mid = (left + right) / 2; MergeSort(a, left, mid); MergeSort(a, mid + 1, right); Merge(a, left, mid, right); } int main() { int a[] = { 5,1,9,2,8,7,10,3,4,0,6 }; int n = sizeof(a) / sizeof(int); MergeSort(a, 0, n - 1); printf("排序好的數組爲:"); for (int k = 0; k < n; ++k) printf("%d ", a[k]); printf("\n"); return 0; }
分析:
最差、最優、平均時間複雜度都爲O(nlogn),其中遞歸樹共有lgn+1層,每層須要花費O(n)。輔助空間O(n)。穩定性:穩定。