幾種排序演算法的對比
名稱 |
最差時間複雜度 |
最佳時間複雜度 |
空間複雜度 |
備忘 |
選擇排序 |
O(n^2) |
O |
|
|
插入排序 |
O(n^2) |
|
|
|
冒泡排序 |
O(n^2) |
O(n) |
|
|
歸併排序 |
O(nlogn) |
|
|
時間效率高,但是歸併時需要臨時數組,空間複雜度較高 |
快速排序 |
O(n^2) |
O(nlogn) (平均時間) |
|
|
堆排序 |
O(nlogn) |
|
|
|
桶排序 |
O(n+N) |
|
|
限於小整數排序,時間效率高於前幾種 |
基數排序 |
O(dn) |
|
|
限於小整數排序,時間效率高於前幾種 |
1. 選擇排序(Selection Sort)
選擇排序的思想是從數組中選擇最小,次小,次次小, .......,的元素依次加到前面的數組中。
Code:
package sort;public class SelectionSort {public static void selectionSort(int[] list) {int index;int currentElement;for (int i = 0; i < list.length - 1; i++) {index = i;currentElement = list[i];for (int j = i + 1; j < list.length; j++) {if (list[j] < currentElement) {currentElement = list[j];index = j;}}if (index != i) {list[index] = list[i];list[i] = currentElement;}}}public static void main(String[] args) {int[] list = {7,3,5,7,3,2,1,2};selectionSort(list);for (int i = 0; i < list.length; i++) {System.out.println(list[i]);}}}
2.插入排序(Insertion Sort)
插入演算法的思想是從第二個元素開始,依次插入到前面排好序的序列中,注意插入遍曆從後往前遍曆數組,還有下標越界問題。。程式就參考教材上的比較簡單。
Code:
package sort;public class InsertionSort {public static int[] insertionSort(int[] list) {for (int i = 1; i < list.length; i++) {int k;int currentElement = list[i];for (k = i - 1; k >= 0 && list[k] > currentElement; k--) {list[k + 1] = list[k];}list[k + 1] = currentElement;}return list;}public static void main(String[] args) {int[] list = {7,3,5,7,3,2,1,2};list = insertionSort(list);for (int i = 0; i < list.length; i++) {System.out.println(list[i]);}}}
3. 冒泡排序(Bubble Sort)
冒泡排序演算法需要遍曆幾次數組,在每次遍曆中,比較 連續相鄰 的元素。如果某一對元素是降序,則互換它們的位置;否則,保持不變。冒泡排序法需要遍曆 n-1 次數組,每次遍曆最佳情況是不用交換,則時間複雜度為 O(n) ,最差情況是每次都要交換,對於第 k 個要交換 n-1-k 次,此時時間複雜度是 O(n^2)
Trick:如果在某次遍曆中沒有發生交換,說明所有元素都已排好序了,不需再次交換。使用此特性可以改進演算法。
Code:
package test;public class Test {public static void main(String[] args) {int[] list = {3,2,1};list = bubbleSort(list);for (int i = 0; i < list.length; i++) {System.out.println(list[i]);}}public static int[] bubbleSort (int[] list) {boolean nextPass = true;for (int i = 0; i < list.length - 1 && nextPass; i++) {nextPass = false;for (int j = 0; j < list.length - 1 - i; j++) {if (list[j] > list[j + 1]) {int tmp = list[j];list[j] = list[j + 1];list[j + 1] = tmp;nextPass = true;}}}return list;}}
4. 歸併演算法(MergeSort)
歸併演算法可以遞迴地描述為:演算法將數組分為兩半,對每部分遞迴地應用歸併排序。在兩部分都排好序後,對它們進行歸併。歸併排序的複雜度為 O(n log n)。
Code:
package sort;import java.awt.List;public class MergeSort {public static int[] merge(int[] list) {int[] firstHalf = new int[list.length / 2];System.arraycopy(list, 0, firstHalf, 0, firstHalf.length);if (firstHalf.length > 1)firstHalf = merge(firstHalf);int[] secondHalf = new int[list.length - list.length / 2];System.arraycopy(list, list.length / 2, secondHalf, 0, secondHalf.length);if (secondHalf.length > 1)secondHalf = merge(secondHalf);list = merge(firstHalf, secondHalf);return list;}public static int[] merge(int[] list1, int[] list2) {int[] tmp = new int[list1.length + list2.length];int current1 = 0, current2 = 0, current3 = 0;while (current1 < list1.length && current2 < list2.length) {if (list1[current1] < list2[current2])tmp[current3++] = list1[current1++];else tmp[current3++] = list2[current2++];}while (current1 < list1.length) {tmp[current3++] = list1[current1++];}while (current2 < list2.length) {tmp[current3++] = list2[current2++];}return tmp;}public static void main(String[] args) {int[] list = {4,2,7,4,7,9,8,1};list = merge(list);for (int i = 0; i < list.length; i++) {System.out.println(list[i]);}}}
5. 快速排序 (QuickSort)
快速排序演算法在數組中選擇一個稱為主元(pivot)的元素,將數組分為兩部分,使得 第一部分中的所有元素都小於或等於主元,而第二部分的所有元素都大於主元。對第一部分遞迴地應用快速排序演算法,然後對第二部分遞迴地應用快速排序演算法。
在最差情況下,劃分由 n 個元素構成的數組需要進行 n 次比較和 n 次移動。因此劃分所需時間為 O(n) 。最差情況下,每次主元會將數組劃分為一個大的子數組和一個空數組。這個大的子數組的規模是在上次劃分的子數組的規模減 1 。該演算法需要 (n-1)+(n-2)+...+2+1= O(n^2) 時間。
在最佳情況下,每次主元將數組劃分為規模大致相等的兩部分。設 T(n) 表示使用快速排序演算法對包含 n 個元素的數組排序所需的時間,因此,和歸併排序的分析相似,快速排序的 T(n)= O(nlogn)。
package sort;import java.awt.List;public class QuickSort {public static int[] quickSort(int[] list) {quickSort(list, 0, list.length - 1);return list;}public static int[] quickSort(int[] list, int first, int last) {if (first < last) {//遞迴地對主元(pivot)前後的數組進行快排int pivotIndex = partition(list, first, last);quickSort(list, first, pivotIndex - 1);quickSort(list, pivotIndex + 1, last);}return list;}public static int partition(int[] list, int first, int last) {//操作過程見下圖int pivot = list[first], low = first + 1, high = last;//尋找前半數組中大於主元的元素下標和後半數組中小於或等於主元的元素下標while (high > low) {while (pivot >= list[low] && low <= high) low++;while (pivot < list[high] && low <= high)high--;//交換兩個元素if (low < high) {int tmp = list[low];list[low] = list[high];list[high] = tmp;}}//插入主元進適當位置while (list[high] >= pivot && high > first)high--;if (list[high] < pivot) {list[first] = list[high];list[high] = pivot;return high;}else {return first;}}public static void main(String[] args) {int[] list = {2,6,3,5,4,1,8,45,2};list = quickSort(list);for (int i = 0; i < list.length; i++) {System.out.println(list[i]);}}}
---------------------------------------------------------------------------------修改於2016/5/21--------------------- 現在看來上面的快排代碼不是很清晰,在下面附上個清晰點兒的代碼:
public class QuickSort {public static void main(String[] args) {int[] array = {1,1,1,1,1,1,1,1};QuickSort qs = new QuickSort();qs.quickSort(array);for(int ele : array)System.out.print(ele + " ");}public void quickSort(int[] array) {quickSort(array, 0, array.length - 1);}public void quickSort(int[] array, int start, int end) {if(start > end)//注意判斷終止條件return;int index = partation(array, start, end);//主元所在位置quickSort(array, start, index - 1);//遞迴地對主元前的數組進行快排quickSort(array, index + 1, end);//遞迴地對主元後的數組進行快排}public int partation(int[] array, int start, int end) {int pivot = array[start];int low = start;int high = end + 1;while(high > low) {//這裡寫法一定要注意,首先確保沒有數組指標越界訪問,然後要確保當low和high指向元素大小相同時不陷入死迴圈如while(low < end && array[++low] < pivot) {}while(high > start && array[--high] > pivot) {}/* 如果用下面兩行代碼代替上面兩行代碼的話,當array[low] == array[high] 的時候就會陷入死迴圈while(array[low] < pivot) low++;while(array[high] > pivot) high--;*/if(high > low)swap(array, low, high);}swap(array, start, high);return high;}public void swap(int[] array, int a, int b) {int tmp = array[a];array[a] = array[b];array[b] = tmp;}}
辨析:歸併 VS 快排
歸併和快排都用了分治法。
對于歸並排序,大量的工作是將兩個子線性表進行歸併,歸併是在子線性表都排好序之後進行的。
對於快速排序,大量的工作是將線性表劃分為兩個子線性表,劃分是在子線性表排好序後進行的。
在最差情況下, 歸併時間效率 > 快排時間效率 ,但是在平均情況下,兩者的效率相同。
空間效率上, 歸併空間效率 < 快排空間效率。 原因是 歸併排序在歸併兩個子數組時需要一個臨時數組,而快速排序不需要額外的數組空間。
6. 堆排序(HeapSort)
堆排序使用的是二元堆積,它是一棵完全二叉樹。首先瞭解一下堆的結構和特性,這對我們理解堆排序有著重要的意義。
======================================擴充=============================================
擴充:堆(heap)是一棵具有以下屬性的二叉樹:
它是一棵完全二叉樹(除了最後一層沒填滿以及最後一層的葉子都是偏左放置的,如果一棵二叉樹的每一層都是滿的,那麼這顆二叉樹就是完全的) 每個節點大於或等於它的任意一個孩子 堆的序號特點: 對於位置 i 處的結點,它的左孩子在位置 2i+1 處,右孩子在位置 2i+2 處,它的父親在 (i-2)/2 處。 向堆添加一個新結點: 首先將新結點加到堆的末尾 將它與它的父親比較,如果比它父親大,就交換兩者位置 重複 2 直到它小於或等於父親 例:
刪除根結點: 刪除根結點 將根結點替換為堆的最後一個元素 比較替換的元素與子結點大小,交換替換的元素和較小元素的位置 重複 3 ,直到替換的元素到合適的位置 例:
=======================================================================================================
下面繼續正題,由於堆保持 父結點總是大於子結點 的特性,我們可以將 list 中的元素依次 add 到堆中,重建堆的過程中已經排好序了,接下來我們利用 刪除根結點 的辦法依次擷取由大到小的元素。
堆排序的時間複雜度與歸併排序相同,為 O(n logn),但是堆排序不需要額外的數組空間,相較于歸並排序,堆排序的空間效率更高。
7.桶排序(BucketSort)
上面所講的四種演算法可以用在比較任何索引值類型(如 整數、字串以及任何 Comparable 的對象)上,但是這些基於比較的排序演算法中沒有時間複雜度好過 O(n logn) 的演算法。桶排序適用於 小整數 的排序,無需比較索引值。
桶排序的工作方式如下。假設索引值的範圍是 0 到 N-1 。我們需要 N 個標記為 0, 1, ...,N-1 的桶。如果元素的索引值是 i, 那麼久將鈣元素放入桶 i 中。每個桶中都存在和索引值具有相同值的元素,可以使用 ArrayList 來實現一個桶。
桶排序的時間複雜度為 O(n+N) ,空間複雜度也為 O(n+N) ,其中 n 是指線性表的大小。
8. 基數排序
當 N 過大時,在 5 中介紹的桶排序就不是很可取了。此時可以用基數排序。
基數排序是基於桶排序的,不過它只用 十個 桶。
通常,基數排序需要耗費 O(dn) 時間對帶整數索引值的 n 個元素排序,其中 d 是所有索引值中基數位置的最大值。