Θ(n^2)html
原理java
將數組當作兩部分,一部分爲已排序好的數組,後面的部分爲未排序數組,每次從後面的數組中取出元素與前面的有序元素一一比較,若小於則向前移動,直到找到正確的位置插入。遍歷後面的數組直到整個數組排序完成。git
代碼算法
// 準備工做,交換函數
public static void exc(int[] a,int i, int j) {
if (a[i]!=a[j]) {
a[i]^=a[j];
a[j]^=a[i];
a[i]^=a[j];
}
}
// 插入排序
public static void insertSort(int[] a, int n) {
for (int i = 1; i < n; i++) {
for (int j = i; j>0&&a[j-1]>a[j]; j--) {
exc(a, j, j-1);
}
}
}
分析shell
時間複雜度api
評價:數組
插入排序與數組的逆序度有關,最好狀況爲 O(n),因此常常與快速排序一塊兒出現,詳見C語言的quickSort的實現數據結構
動畫演示app
原理ide
就像泡泡同樣,不斷把大的數字往上浮,遍歷完整個數組排序即完成。
代碼
public static void bubbleSort(int[] a, int n) {
boolean flag = true;
for (int i = 0; i < n-1&&flag; i++) {
flag = false;
for (int j = 0; j < n-i-1; j++) {
if (a[j]>a[j+1]) {
exc(a, j, j+1);
flag=true;
}
}
}
}
分析
時間複雜度:
評價:
你們也看到上述代碼有個標記變量 flag,這是冒泡排序的一種改進,若是在第二次循環中沒有發生交換說明排序已經完成,不須要再循環下去了。
動畫演示
原理
選擇排序的原理很簡單,就是從須要排序的數據中選擇
最小的(從小到大排序),而後放在第一個,選擇第二小的放在第二個……
代碼
// 選擇排序,穩定
public static void selectSort(int[] a,int n) {
for (int i = 0; i < n; i++) {
int min=i;
for (int j = i+1; j < n; j++) {
if(a[min]>a[j]){
min = j;
}
}
if (min!=i) {
exc(a, i, min);
}
}
}
分析
時間複雜度:
比較的次數: (n-1)+(n-2)+...+1= n(n-1)/2
交換的次數: n
評價:
Θ(nlogn)
圖片演示
原理
希爾排序是基於插入排序進行改進,又稱之爲遞減增量排序。在前面中咱們知道,插入排序是將小的元素往前挪動位置,而且每次只移動一個位置。那麼希爾排序是怎麼解決這個問題的呢?
希爾排序的理念和梳排序的理念有點相似。在梳排序中,咱們比較距離相差爲step
的兩個元素來完成交換。在希爾排序中,咱們的作法也是相似。咱們在數組中每隔h
取出數組中的元素,而後進行插入排序。當h=1時,則就是前面所寫的插入排序了。
代碼
// 6. 希爾排序
public static void shellSort(int[] a, int n) {
int h =1;
while (h<n/3) {
// 數組 1,4,13,40...
h = h*3+1;
}
while (h>=1) {
for (int i = h; i < n; i++) {
for(int j=i;j>=h&&a[j-h]>a[j];j-=h){
exc(a, j, j-h);
}
}
h/=3;
}
}
分析
是第一個突破時間複雜度O(n^2)的算法
思路--計算步長,對每次分組進行直接插入排序,減少逆序度
算法時間複雜度在插入排序和快速排序之間
動畫演示
原理
快速排序使用了, Divide and Conquer
(分治)策略,不斷地把數組分爲較大和較小的兩個子序列,而後對每一個子序列進行遞歸,直到不可再分。思路就是在拆分的同時進行排序 與 歸併排序
不一樣。
步驟:
挑選基準值:從數列中挑出一個元素,稱爲「基準」(pivot),
分割:從新排序數列,全部比基準值小的元素擺放在基準前面,全部比基準值大的元素擺在基準後面(與基準值相等的數能夠到任何一邊)。在這個分割結束以後,對基準值的排序就已經完成。
遞歸排序子序列:遞歸地將小於基準值元素的子序列和大於基準值元素的子序列排序。
遞歸到最底部的判斷條件是數列的大小是零或一,此時該數列顯然已經有序。
代碼
// 第一部分
public static int partition(int[] a,int l,int h) {
int mid = l+((h-l)>>1);
int pivot = a[mid];
exc(a, l, mid);
int i = l;
int j = h+1;
while (true) {
while (a[++i]<pivot) {
if(i==h) break;
}
while (a[--j]>pivot) {
if(j==l) break;
}
if (i>=j) {
break;
}
exc(a, i, j);
}
exc(a, l, j);
return j;
}
public static void quickSort(int[] a, int n) {
quickSort(a, 0, n-1);
}
// 第二部分
public static void quickSort(int[] a, int lo, int h) {
if (lo>=h) {
return;
}
int j = partition(a, lo, h);
quickSort(a, lo, j-1);
quickSort(a, j+1, h);
}
分析
快速排序的最壞時間複雜度爲O(n^2),平均時間複雜度爲 O(n logn),快速排序基本上被認爲是比較排序算法中,平均性能最好的。多種語言皆實現了快速排序的類庫。
動畫演示
原理
採用分治法:
快速排序
是邊拆分邊排序代碼
// 第一部分 合併
public static void merge(int[] a, int low, int mid, int high) {
// 第一種寫法
int i = low;
int j = mid + 1;
int k = 0;
int[] a2 = new int[high - low + 1];
while (i <= mid && j <= high) {
if (a[i] < a[j]) {
a2[k] = a[i];
i++;
k++;
} else {
a2[k] = a[j];
j++;
k++;
}
}
while (i <= mid) {
a2[k] = a[i];
i++;
k++;
}
while (j <= high) {
a2[k] = a[j];
j++;
k++;
}
for (k = 0, i = low; i <= high; k++, i++) {
a[i] = a2[k];
}
}
public static void mergeSort(int[] a, int n) {
mergeSort(a, 0, n - 1);
}
// 第二部分 遞歸
public static void mergeSort(int[] a, int low, int high) {
if (low >= high)
return;
int mid = (high + low) / 2;
mergeSort(a, low, mid);
mergeSort(a, mid + 1, high);
merge(a, low, mid, high);
}
分析
歸併排序是一種穩定的且十分高效的排序。時間複雜度老是 O(nlogn),不論好壞,但缺點是,它不是原地排序,佔用額外的空間,空間複雜度爲 O(n)
動畫演示
原理
堆排序是藉助堆這一數據結構實現的排序
咱們利用大頂堆(堆頂元素最大)實現排序,在一個堆中,位置k的結點的父元素的位置是(k+1)/2-1
,而它的兩個子節點的位置分別是2k+1
和2k+2
,這樣咱們就能夠經過計算數組的索引在樹中上下移動。
思路: 不斷把堆頂的元素與最後的元素交換位置,從新堆化,不斷獲得第k(=1,2,3...)大的元素。至關於一個將大的元素 sink(下沉) 的過程。
代碼
// 建堆
public static void buildHeap(int[] a, int n) {
for (int i = n / 2; i >= 0; i--) {
heapify(a, n - 1, i);
}
}
// 堆化
public static void heapify(int[] a, int n, int i) {
while (true) {
int maxPos = i;
if (i * 2 + 1 <= n && a[i] < a[2 * i + 1]) {
maxPos = i * 2 + 1;
}
if (i * 2 + 2 <= n && a[maxPos] < a[i * 2 + 2]) {
maxPos = i * 2 + 2;
}
if (i == maxPos) {
break;
}
exc(a, i, maxPos);
i = maxPos;
}
}
public static void heapSort(int[] a, int n) {
buildHeap(a, n);
int k = n - 1;
while (k > 0) {
// 交換堆頂元素,把第1,2,3...大元素放到底部
exc(a, 0, k);
--k;
heapify(a, k, 0);
}
}
分析
Θ(n)
動畫演示
原理
計數排序使用一個額外的數組C,其中 C 中第i個元素是待排序數組A中值等於i的元素的個數。而後根據數組C 來將A中的元素排到正確的位置。
tips:固然,若是數據比較集中的話,咱們大可沒必要建立那麼大的數組,咱們找出最小和最大的元素,以最小的元素做爲基底以減少數組的大小。
代碼
// 非比較排序
public static void countSort(int[] a, int n) {
int max = a[0];
for (int i = 0; i < n; i++) {
if (a[i] > max) {
max = a[i];
}
}
int[] c = new int[max + 1];
int indexArray = 0;
for (int i = 0; i < n; i++) {
c[a[i]]++;
}
for (int i = 0; i <= max; i++) {
if (c[i] != 0) {
a[indexArray] = i;
c[i]--;
indexArray++;
}
}
}
圖片演示
原理
桶排序的基本思想是假設數據在[min,max]之間均勻分佈,其中min、max分別指數據中的最小值和最大值。那麼將區間[min,max]等分紅n份,這n個區間便稱爲n個桶。將數據加入對應的桶中,而後每一個桶內單獨排序。因爲桶之間有大小關係,所以能夠從大到小(或從小到大)將桶中元素放入到數組中。
代碼
public static void bucketSort(int[] a, int n, int bucketSize) {
int max = a[0];
int min = a[1];
for (int v : a) {
if (v > max) {
max = v;
} else if (v < min) {
min = v;
}
}
// 桶的大小
int bucketCount = (max - min) / bucketSize + 1;
int bucket[][] = new int[bucketCount][bucketSize];
int indexArr[] = new int[bucketCount];
// 將數字放到對應的桶中
for (int v : a) {
int j = (v - min) / bucketSize;
if (indexArr[j] == bucket[j].length) {
ensureCapacity(bucket, j);
}
bucket[j][indexArr[j]++] = v;
}
// 每一個桶快排
// 也可使用插入保證穩定性
int k = 0;
for (int i = 0; i < bucketCount; i++) {
if (indexArr[i] == 0) {
continue;
}
quickSort(bucket[i], indexArr[i]);
for (int j = 0; j < indexArr[i]; j++) {
a[k++] = bucket[i][j];
}
}
}
// 擴容函數
private static void ensureCapacity(int[][] bucket, int j) {
int[] tempArr = bucket[j];
int[] newArr = new int[tempArr.length * 2];
for (int k = 0; k < tempArr.length; k++) {
newArr[k] = tempArr[k];
}
bucket[j] = newArr;
}
分析
桶排序是線性排序的一種,桶排序的核心就是根據數據的範圍 (m) ,把數據 (大小爲n),儘量均勻得放到 K個桶
裏,每一個桶再各自實現排序,而後把桶從小到大的列出來,即完成排序。
動畫演示
原理
在平常的使用中,咱們接觸基數排序比較少,它也是桶排序的一種變形。
它的具體實現分爲 LSD (Least sgnificant digital) , MSD (Most sgnificant digital) 兩種方法,上面的演示是第一種(LSD),從低位到高位,根據每一位上的數字將元素放入桶中,再按順序取出,直到比較完最高位,完成排序。
代碼
/** * * @param x 每一位上的值 * @param d 第d位 * @param dg 輔助數組 * @return 對應的桶的標號 */
public static int getDigit(int x, int d, int[] dg) {
return (x / dg[d - 1] % 10);
}
/** * * @param a 待排序數組 * @param n 數組長度 */
public static void radixSort(int[] a, int n) {
// 最大的數
int max = 0;
int j = 0, i = 0;
// 默認十進制
final int radix = 10;
for (int val : a) {
if (val > max) {
max = val;
}
}
// 求最大位數
int N;
if (max == 0) {
N = 1;
} else {
N = (int) Math.log10(max) + 1;
}
// 設置輔助數組
int dg[] = new int[N + 1];
for (i = 1, dg[0] = 1; i < N + 1; i++) {
dg[i] = 10 * dg[i - 1];
}
// 初始化桶
int bucket[][] = new int[radix][n];
int indexArr[] = new int[radix];
for (int d = 1; d <= N; d++) {
for (int var : a) {
j = getDigit(var, d, dg);
bucket[j][indexArr[j]++] = var;
}
int count = 0;
for (i = 0; i < radix; i++) {
if (indexArr[i] != 0) {
for (int k = 0; k < indexArr[i]; k++) {
a[count++] = bucket[i][k];
}
indexArr[i] = 0;
}
}
}
}
分析
時間複雜度爲 O(k*n),空間複雜度爲O(n),當處理較大(位數多)的數字排序時,比計數排序更好用。