首先,這篇博客的來源是由於我在學習排序算法的時候,看到了一位大神寫的十大經典排序算法,寫的真的很不錯,這是我整理後用於本身之後觀看學習的,若有錯誤,歡迎指正。html
本文轉自http://www.javashuo.com/article/p-apvjfrqu-dq.html 感謝大佬的分享。java
十種常見排序算法能夠分爲兩大類:算法
比較類排序:經過比較來決定元素間的相對次序,因爲其時間複雜度不能突破O(nlogn),所以也稱爲非線性時間比較類排序。
非比較類排序:不經過比較來決定元素間的相對次序,它能夠突破基於比較排序的時間下界,以線性時間運行,所以也稱爲線性時間非比較類排序。
shell
穩定:若是a本來在b前面,而a=b,排序以後a仍然在b的前面。
不穩定:若是a本來在b的前面,而a=b,排序以後 a 可能會出如今 b 的後面。
時間複雜度:對排序數據的總的操做次數。反映當n變化時,操做次數呈現什麼規律。
空間複雜度:是指算法在計算機
內執行時所需存儲空間的度量,它也是數據規模n的函數。api
冒泡排序是一種簡單的排序算法。它重複地走訪過要排序的數列,一次比較兩個元素,若是它們的順序錯誤就把它們交換過來。走訪數列的工做是重複地進行直到沒有再須要交換,也就是說該數列已經排序完成。這個算法的名字由來是由於越小的元素會經由交換慢慢「浮」到數列的頂端。數組
比較相鄰的元素。若是第一個比第二個大,就交換它們兩個;
對每一對相鄰元素做一樣的工做,從開始第一對到結尾的最後一對,這樣在最後的元素應該會是最大的數;
針對全部的元素重複以上的步驟,除了最後一個;
重複步驟1~3,直到排序完成。數據結構
public static int[] bubbleSort(int[] arr) { int temp; for(int i = 0; i < arr.length- 1; i++) {//排序arr.length-1個元素,因此循環arr.length-1次 for(int j = 0; j < arr.length- 1 - i; j++) { if(arr[j] > arr[j+1]) {// 相鄰元素兩兩對比 temp = arr[j+1];// 元素交換 arr[j+1] = arr[j]; arr[j] = temp; } } } return arr; }
選擇排序(Selection-sort)是一種簡單直觀的排序算法。它的工做原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,而後,再從剩餘未排序元素中繼續尋找最小(大)元素,而後放到已排序序列的末尾。以此類推,直到全部元素均排序完畢。ide
n個記錄的直接選擇排序可通過n-1趟直接選擇排序獲得有序結果。具體算法描述以下:函數
初始狀態:無序區爲R[1..n],有序區爲空;
第i趟排序(i=1,2,3…n-1)開始時,當前有序區和無序區分別爲R[1..i-1]和R(i..n)。該趟排序從當前無序區中-選出關鍵字最小的記錄 R[k],將它與無序區的第1個記錄R交換,使R[1..i]和R[i+1..n)分別變爲記錄個數增長1個的新有序區和記錄個數減小1個的新無序區;
n-1趟結束,數組有序化了。
2.2 動圖演示
性能
public static int[] selectionSort(arr) { int minIndex, temp; for (int i = 0; i < arr.length - 1; i++) { minIndex = i; for (int j = i + 1; j < arr.length; j++) { if (arr[j] < arr[minIndex]) { // 尋找最小的數 minIndex = j; // 將最小數的索引保存 } } temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } return arr; }
表現最穩定的排序算法之一,由於不管什麼數據進去都是O(n^{2}n2 )的時間複雜度,因此用到它的時候,數據規模越小越好。惟一的好處可能就是不佔用額外的內存空間了吧。理論上講,選擇排序可能也是平時排序通常人想到的最多的排序方法了吧。
插入排序(Insertion-Sort)的算法描述是一種簡單直觀的排序算法。它的工做原理是經過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。
通常來講,插入排序都採用in-place在數組上實現。具體算法描述以下:
重複步驟2~5。
public static int[] insertionSort(int arr) { int preIndex, current; for (int i = 1; i < arr.length; i++) { preIndex = i - 1; current = arr[i]; while (preIndex >= 0 && arr[preIndex] > current) { arr[preIndex + 1] = arr[preIndex]; preIndex--; } arr[preIndex + 1] = current; } return arr; }
插入排序在實現上,一般採用in-place排序(即只需用到O(1)的額外空間的排序),於是在從後向前掃描過程當中,須要反覆把已排序元素逐步向後挪位,爲最新元素提供插入空間。
對於已經有序或基本有序的數據來講,插入排序要好的多。當數據有序的時候,while循環條件老是假,因此它變成了外層循環中的一個簡單語句,執行N-1次。在這種狀況下,算法運行只須要O(N)的時間。
然而,對於逆序排列的數據,每次比較和移動都會執行,因此插入排序不比冒泡排序快。
(時間上)
冒泡排序算法:當數據量很小的時候它會有些應用的價值。
選擇排序:當數據量很小,而且交換數據相對於比較數據更加耗時的狀況下,能夠應用選擇排序;
在大多數狀況下,假設當數據比較小或基本有序時,插入排序算法是三種簡單排序算法中最好的選擇。對於更大數據量的排序來講,「快速排序」一般是最快的方法;
1959年Shell發明,第一個突破O(n2)的排序算法,是簡單插入排序的改進版。它與插入排序的不一樣之處在於,它會優先比較距離較遠的元素。希爾排序又叫縮小增量排序。
先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序,具體算法描述:
選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;
按增量序列個數k,對序列進行k 趟排序;
每趟排序,根據對應的增量ti,將待排序列分割成若干長度爲m 的子序列,分別對各子表進行直接插入排序。僅增量因子爲1 時,整個序列做爲一個表來處理,表長度即爲整個序列的長度。
public static int[] shellSort(int[] arr) { for (int gap = Math.floor(arr.length/ 2); gap > 0; gap = Math.floor(gap / 2)) { // 注意:這裏和動圖演示的不同,動圖是分組執行,實際操做是多個分組交替執行; for (int i = gap; i < arr.length; i++) {//分組的組員之間間隔爲gap,由於i++因此可使數組的每一個元素與前面間隔爲gap的值進行比較; int j = i;// int current = arr[i]; while (j - gap >= 0 && current < arr[j - gap]) {//與間隔爲gap的數組的值進行比較 arr[j] = arr[j - gap]; j = j - gap; } arr[j] = current; } } return arr; }
希爾排序的核心在於間隔序列的設定。既能夠提早設定好間隔序列,也能夠動態的定義間隔序列。動態定義間隔序列的算法是《算法(第4版)》的合著者Robert Sedgewick提出的。
歸併排序是創建在歸併操做上的一種有效的排序算法。該算法是採用分治法(Divide and Conquer)的一個很是典型的應用。將已有序的子序列合併,獲得徹底有序的序列;即先使每一個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲2-路歸併。
把長度爲n的輸入序列分紅兩個長度爲n/2的子序列;
對這兩個子序列分別採用歸併排序;
將兩個排序好的子序列合併成一個最終的排序序列。
public static int[] mergeSort(int[] arr) { if (arr.length < 2) { return arr; } int middle = Math.floor(arr.length / 2), left = arr.slice(0, middle), right = arr.slice(middle); return merge(mergeSort(left), mergeSort(right)); } function merge(left, right) { int result = []; while (left.length>0 && right.length>0) { if (left[0] <= right[0]) { result.push(left.shift()); } else { result.push(right.shift()); } } while (left.length) result.push(left.shift()); while (right.length) result.push(right.shift()); return result; }
歸併排序是一種穩定的排序方法。和選擇排序同樣,歸併排序的性能不受輸入數據的影響,但表現比選擇排序好的多,由於始終都是O(nlogn)的時間複雜度。代價是須要額外的內存空間。
快速排序的基本思想:經過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另外一部分的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。
快速排序使用分治法來把一個串(list)分爲兩個子串(sub-lists)。具體算法描述以下:
從數列中挑出一個元素,稱爲 「基準」(pivot);
從新排序數列,全部元素比基準值小的擺放在基準前面,全部元素比基準值大的擺在基準的後面(相同的數能夠到任一邊)。在這個分區退出以後,該基準就處於數列的中間位置。這個稱爲分區(partition)操做;
遞歸地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。
public static int[] quickSort(int[] arr,int left,int right) { int partitionIndex; if (left < right) { partitionIndex = partition(arr, left, right); quickSort(arr, left, partitionIndex-1); quickSort(arr, partitionIndex+1, right); } return arr; } public static int partition(int[] arr,int left ,int right) { // 分區操做 int pivot = left, // 設定基準值(pivot) index = pivot + 1; for (int i = index; i <= right; i++) { if (arr[i] < arr[pivot]) { swap(arr, i, index); index++; } } swap(arr, pivot, index - 1); return index-1; } public static void swap(int[] arr,int i,int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }
堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序算法。堆積是一個近似徹底二叉樹的結構,並同時知足堆積的性質:即子結點的鍵值或索引老是小於(或者大於)它的父節點。
將初始待排序關鍵字序列(R1,R2….Rn)構建成大頂堆,此堆爲初始的無序區;
將堆頂元素R[1]與最後一個元素R[n]交換,此時獲得新的無序區(R1,R2,……Rn-1)和新的有序區(Rn),且知足R[1,2…n-1]<=R[n];
因爲交換後新的堆頂R[1]可能違反堆的性質,所以須要對當前無序區(R1,R2,……Rn-1)調整爲新堆,而後再次將R[1]與無序區最後一個元素交換,獲得新的無序區(R1,R2….Rn-2)和新的有序區(Rn-1,Rn)。不斷重複此過程直到有序區的元素個數爲n-1,則整個排序過程完成。
//堆排序 static int len; // 由於聲明的多個函數都須要數據長度,因此把len設置成爲全局變量 public static void buildMaxHeap(int[] arr) { // 創建大頂堆 len = arr.length; for (double i = Math.floor(len/2); i >= 0; i--) { heapify(arr, (int)i); } } public static void heapify(int[] arr,int i) { // 堆調整 int left = 2 * i + 1, right = 2 * i + 2, largest = i; if (left < len && arr[left] > arr[largest]) { largest = left; } if (right < len && arr[right] > arr[largest]) { largest = right; } if (largest != i) { swap(arr, i, largest); heapify(arr, largest); } } public static void swap(int[] arr,int i,int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } public static int[] heapSort(int[] arr) { buildMaxHeap(arr); for (int i = arr.length - 1; i > 0; i--) { swap(arr, 0, i); len--; heapify(arr, 0); } return arr; }
計數排序不是基於比較的排序算法,其核心在於將輸入的數據值轉化爲鍵存儲在額外開闢的數組空間中。 做爲一種線性時間複雜度的排序,計數排序要求輸入的數據必須是有肯定範圍的整數。
找出待排序的數組中最大和最小的元素;
統計數組中每一個值爲i的元素出現的次數,存入數組C的第i項;
對全部的計數累加(從C中的第一個元素開始,每一項和前一項相加);
反向填充目標數組:將每一個元素i放在新數組的第C(i)項,每放一個元素就將C(i)減去1。
public static int[] countSort(int[] array) { //1.獲得數列的最大值與最小值,並算出差值d int max = array[0]; int min = array[0]; for (int i = 1; i < array.length; i++) { if (array[i] > max) { max = array[i]; } if(array[i] < min) { min = array[i]; } } int d = max - min; //2.建立統計數組並計算統計對應元素個數 int[] countArray = new int[d + 1]; for (int i = 0; i < array.length; i++) { countArray[array[i] - min]++; } //3.統計數組變形,後面的元素等於前面的元素之和 int sum = 0; for (int i = 0; i < countArray.length; i++) { sum += countArray[i]; countArray[i] = sum; } //4.倒序遍歷原始數組,從統計數組找到正確位置,輸出到結果數組 int[] sortedArray = new int[array.length]; for (int i = array.length - 1; i >= 0; i--) { sortedArray[countArray[array[i] - min] - 1] = array[i]; countArray[array[i] - min]--; } return sortedArray; }
計數排序是一個穩定的排序算法。當輸入的元素是 n 個 0到 k 之間的整數時,時間複雜度是O(n+k),空間複雜度也是O(n+k),其排序速度快於任何比較排序算法。當k不是很大而且序列比較集中時,計數排序是一個頗有效的排序算法。