原地址為:Java常見排序方法
日常操作中常見的排序方法有:冒泡排序、快速排序、選擇排序、插入排序、希爾排序,甚至還有基數排序、歸並排序、二分排序、堆排序、計數排序等。
以下常見算法的定義
- 1. 插入排序:插入排序基本操作就是將一個數據插入到已經排好序的有序數據中,從而得到一個新的、個數加一的有序數據,算法適用於少量數據的排序,時間復雜度為O(n^2)。是穩定的排序方法。插入排序的基本思想是:每步將一個待排序的紀錄,按其關鍵碼值的大小插入前面已經排序的文件中適當位置上,直到全部插入完為止。
- 2. 選擇排序:選擇排序(Selection sort)是一種簡單直觀的排序算法。它的工作原理是每一次從待排序的數據元素中選出最小(或最大)的一個元素,存放在序列的起始位置,直到全部待排序的數據元素排完。 選擇排序是不穩定的排序方法。
- 3. 冒泡排序:冒泡排序(Bubble Sort),是一種計算機科學領域的較簡單的排序算法。它重復地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重復地進行直到沒有再需要交換,也就是說該數列已經排序完成。這個算法的名字由來是因為越大的元素會經由交換慢慢“浮”到數列的頂端。
- 4. 快速排序:快速排序(Quicksort)是對冒泡排序的一種改進。它的基本思想是:通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然后再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。
- 5. 歸並排序:歸並排序是建立在歸並操作上的一種有效的排序算法,該算法是采用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合並,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合並成一個有序表,稱為二路歸並。
- 6. 希爾排序:希爾排序(Shell Sort)是插入排序的一種。也稱縮小增量排序,是直接插入排序算法的一種更高效的改進版本。希爾排序是非穩定排序算法。希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序算法排序;隨着增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個文件恰被分成一組,算法便終止。
以下常見算法
一、冒泡排序
冒泡排序是一種簡單的排序算法。它重復地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重復地進行直到沒有再需要交換,也就是說該數列已經排序完成。這個算法的名字由來是因為越小的元素會經由交換慢慢“浮”到數列的頂端。
/** * 冒泡排序(大的值從前往后冒泡) * 比較相鄰的元素。如果第一個比第二個小,就交換他們兩個。 * 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最后一對。在這一點,最后的 元素應該會是最小的數。 * 針對所有的元素重復以上的步驟,除了最后一個。 * 持續每次對越來越少的元素重復上面的步驟,直到沒有任何一對數字需要比較。 * 優點:穩定排序;適用於數組存儲的數據和鏈表存儲的數據; */ public static int[] bubbleSort(int[] a) { for (int end = a.length - 1; end > 0; end--) { boolean flag = false; //增加一個判斷是否發生過交換的標記 for (int j = 0; j < end; j++) { if (a[j] > a[j + 1]) { swap(a, j, j + 1); flag = true; } } if (!flag) { //如果掃描一遍發現沒有發生交換則說明序列已經有序,退出循環 break; } } return a; } /** * 冒泡排序(小的值從后往前下沉) * 優點:穩定排序;適用於數組存儲的數據和鏈表存儲的數據; */ public static int[] bubbleSort2(int[] a) { for (int start = 0; start < a.length - 1; start++) { boolean flag = false; //增加一個判斷是否發生過交換的標記 for (int j = a.length - 1; j > start; j--) { if (a[j] < a[j - 1]) { swap(a, j, j - 1); flag = true; } } if (!flag) { //如果掃描一遍發現沒有發生交換則說明序列已經有序,退出循環 break; } } return a; }
二、快速排序
快速排序使用分治法策略來把一個序列分為兩個子序列。
/**
* 快速排序<br/>
* <ul>
* <li>從數列中挑出一個元素,稱為“基准”</li>
* <li>重新排序數列,所有元素比基准值小的擺放在基准前面,所有元素比基准值大的擺在基准的后面(相同的數可以到任一邊)。在這個分割之后,
* 該基准是它的最后位置。這個稱為分割(partition)操作。</li>
* <li>遞歸地把小於基准值元素的子數列和大於基准值元素的子數列排序。</li>
* </ul>
*/
/** * 快速排序 * * 從數列中挑出一個元素,稱為“基准” * 重新排序數列,所有元素比基准值小的擺放在基准前面,所有元素比基准值大的擺在基准的后面(相同的數可以到任一邊)。在這個分割之后, * 該基准是它的最后位置。這個稱為分割(partition)操作。 * 遞歸地把小於基准值元素的子數列和大於基准值元素的子數列排序。 */ public static int[] quickSort(int[] a) { if (a.length > 0) { quickSortRecursion(a, 0, a.length - 1); } return a; } public static void quickSortRecursion(int[] data, int low, int high) { if (low < high) { int middle = partition(data, low, high); quickSortRecursion(data, low, middle - 1); quickSortRecursion(data, middle + 1, high); } } public static int partition(int[] data, int low, int high) { int temp = data[low]; // 數組的第一個作為中軸 while (low < high) { while (low < high && data[high] >= temp) { high--; } data[low] = data[high]; // 比中軸小的記錄移到低端 while (low < high && data[low] <= temp) { low++; } data[high] = data[low]; // 比中軸大的記錄移到高端 } data[low] = temp; return low; // 返回中軸的位置 } /** * 快速排序的第二種寫法 */ public static int[] quickSort2(int[] a) { qSort(a, 0, a.length - 1); return a; } public static void qSort(int[] sequence, int low, int high) { int pivot = sequence[low]; // 取首元素的為基准 int left = low, right = high; if (low >= high) { return; } swap(sequence, low, high); //將基准與最后一個元素交換 while (true) { //將序列中比基准小的移到基准左邊,比基准大的移到基准右邊 while (low < high && sequence[low] <= pivot) { low++; } while (low < high && sequence[high] >= pivot) { high--; } if (low < high) { swap(sequence, low, high); } else { break; } } swap(sequence, low, right); //將最后的基准換到正確的位置 //分別對兩個子集進行快排 qSort(sequence, left, low - 1); qSort(sequence, low + 1, right); }
三、選擇排序
選擇排序是一種簡單直觀的排序方法,每次尋找序列中的最小值,然后放在最末尾的位置。
/** * 選擇排序 * 在未排序序列中找到最小元素,存放到排序序列的起始位置 * 再從剩余未排序元素中繼續尋找最小元素,然后放到排序序列起始位置。 * 以此類推,直到所有元素均排序完畢。 * / public static int[] selectionSort(int[] a) { for (int i = 0; i < a.length; i++) { for (int j = i + 1; j < a.length; j++) { if (a[i] > a[j]) { swap(a, i, j); } } System.out.println(Arrays.toString(a)); } return a; }
四、插入排序
插入排序的工作原理是通過構建有序序列,對於未排序數據,在已排序序列中從后向前掃描,找到相應位置並插入。其具體步驟參見代碼及注釋。
/** * 插入排序 * * 從第一個元素開始,該元素可以認為已經被排序 * 取出下一個元素,在已經排序的元素序列中從后向前掃描 * 如果該元素(已排序)大於新元素,將該元素移到下一位置 * 重復步驟3,直到找到已排序的元素小於或者等於新元素的位置 * 將新元素插入到該位置中 * 重復步驟2 */ public static int[] insertSort(int[] a) { for (int i = 1; i < a.length; i++) { int temp = a[i]; int j = i; while (j > 0 && temp < a[j - 1]) { a[j] = a[j - 1]; j--; } a[j] = temp; } return a; }
五、歸並排序
歸並排序是建立在歸並操作上的一種有效的排序算法,歸並是指將兩個已經排序的序列合並成一個序列的操作。
/** * 歸並排序 * * 申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合並后的序列 * 設定兩個指針,最初位置分別為兩個已經排序序列的起始位置 * 比較兩個指針所指向的元素,選擇相對小的元素放入到合並空間,並移動指針到下一位置 * 重復步驟3直到某一指針達到序列尾 * 將另一序列剩下的所有元素直接復制到合並序列尾 * / public static int[] mergingSort(int[] a) { if (a.length > 0) { mergingSortRecursion(a, 0, a.length - 1); } return a; } public static void mergingSortRecursion(int[] data, int left, int right) { if (left < right) { int middle = (left + right) / 2; mergingSortRecursion(data, left, middle); mergingSortRecursion(data, middle + 1, right); merge(data, left, middle, right); } } public static void merge(int[] data, int left, int middle, int right) { int[] tempArray = new int[data.length]; int i = left; // 左邊序列的游標 int j = middle + 1; // 右邊序列的游標 int k = left; // 臨時序列的游標 // 從兩個數組中取出最小的放入中間數組 while (i <= middle && j <= right) { if (data[i] <= data[j]) { tempArray[k++] = data[i++]; } else { tempArray[k++] = data[j++]; } } // 剩余部分依次放入中間數組 while (j <= right) { tempArray[k++] = data[j++]; } while (i <= middle) { tempArray[k++] = data[i++]; } // 將中間數組中的內容復制回原數組 while (left <= right) { data[left] = tempArray[left++]; } }
六、二分排序
二分排序是指利用二分法的思想對插入排序進行改進的一種插入排序算法,不同於二叉排序,可以利用數組的特點快速定位指定索引的元素 。
/** * 二分排序 * 也稱折半插入排序,查找次數為O(n log n),移動次數為O(n^2) * Time complexity: O(n^2) * 穩定性:穩定排序 */ public static int[] binarySort(int[] a) { int i, j; int low, high, mid; int temp; for (i = 1; i < a.length; i++) { temp = a[i]; low = 0; high = i - 1; while (low <= high) { mid = (low + high) / 2; if (a[mid] > temp) { high = mid - 1; } else { low = mid + 1; } } for (j = i - 1; j > high; j--) { a[j + 1] = a[j]; } a[high + 1] = temp; } return a; }
七、希爾排序
希爾排序是插入排序的一種又稱“縮小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一種更高效的改進版本。希爾排序是非穩定排序算法。該方法因D.L.Shell於1959年提出而得名。
/** * 希爾排序 */ public static int[] shellSort(int[] a) { int gap = a.length / 2; while (gap >= 1) { for (int i = gap; i < a.length; i++) { int j; int temp = a[i]; for (j = i - gap; j >= 0 && temp < a[j]; j = j - gap) { a[j + gap] = a[j]; } a[j + gap] = temp; } gap /= 2; } return a; }
八、堆排序
堆排序是指利用堆這種數據結構所設計的一種排序算法。堆是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。
/** * 堆排序 */ public static int[] heapSort(int[] a) { buildMaxHeap(a, a.length - 1); swap(a, 0, a.length - 1); for (int i = 1; i < a.length - 1; i++) { adjustMaxHeap(a, 0, a.length - 1 - i); swap(a, 0, a.length - 1 - i); } return a; } public static void buildMaxHeap(int[] data, int lastIndex) { for (int i = (lastIndex - 1) / 2; i >= 0; i--) { adjustMaxHeap(data, i, lastIndex); } } public static void adjustMaxHeap(int[] data, int parent, int lastIndex) { /* * 通常堆是通過一維數組來實現的。在數組起始位置為 0 的情形中: * 父節點 i 的左子節點在位置 (2*i+1); * 父節點 i 的右子節點在位置 (2*i+2); * 子節點 i 的父節點在位置 floor((i-1)/2); */ while (2 * parent + 1 <= lastIndex) { int maxChildIndex = 2 * parent + 1; // 如果當前左孩子不是末尾元素 if (maxChildIndex < lastIndex) { // 如果左孩子小於右孩子,取右孩子下標 if (data[maxChildIndex] < data[maxChildIndex + 1]) { maxChildIndex++; } } // 比較當前父節點和最大孩子節點 if (data[parent] < data[maxChildIndex]) { swap(data, parent, maxChildIndex); parent = maxChildIndex; } else { break; } } } public static void swap(int[] data, int i, int j) { int temp = data[i]; data[i] = data[j]; data[j] = temp; }
九、基數排序
基數排序是屬於“分配式排序”(distribution sort),又稱“桶子法”(bucket sort)或bin sort,顧名思義,它是透過鍵值的部份資訊,將要排序的元素分配至某些“桶”中,藉以達到排序的作用,基數排序法是屬於穩定性的排序,
其時間復雜度為O (nlog(r)m),其中r為所采取的基數,而m為堆數,在某些時候,基數排序法的效率高於其它的穩定性排序法指利用堆這種數據結構所設計的一種排序算法。堆是一個近似完全二叉樹的結構,並同時滿足堆積
的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。
/** * 基數排序 */ public static int[] radixSort(int[] a) { int max = 0; for (int i = 0; i < a.length; i++) { max = a[i] > max ? a[i] : max; } int time = 0; while (max > 0) { time++; max /= 10; } List<ArrayList<Integer>> queue = new ArrayList<>(); for (int i = 0; i < 10; i++) { queue.add(new ArrayList<>()); } for (int i = 0; i < time; i++) { // 按某位對原數組進行一趟排序 for (int j = 0; j < a.length; j++) { int d = a[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i); ArrayList<Integer> list = queue.get(d); list.add(a[j]); queue.set(d, list); } // 把queue進行過一趟排序的數據拷貝回原數組 int count = 0; for (int k = 0; k < 10; k++) { while (queue.get(k).size() > 0) { a[count] = queue.get(k).get(0); queue.get(k).remove(0); count++; } } } return a; }
十、計數排序
計數排序是一個非基於比較的排序算法,該算法於1954年由 Harold H. Seward 提出。它的優勢在於在對一定范圍內的整數排序時,它的復雜度為Ο(n+k)(其中k是整數的范圍),快於任何比較排序算法。
/** * 計數排序法1 取出序號用了少量的比較和循環 */ public static int[] countingSort1(int[] a) { int max = 0; for (int i = 0; i < a.length; i++) { max = a[i] > max ? a[i] : max; } int[] count = new int[max + 1]; for (int i = 0; i < a.length; i++) { count[a[i]]++; } int sum = 0; for (int i = 0; i < count.length; i++) { if (count[i] > 0) { for (int j = 0; j < count[i]; j++) { a[sum + j] = i; } } sum += count[i]; } return a; } /** * 計數排序法2 完全沒有使用比較和循環 */ public static int[] countingSort2(int[] a) { int max = 0; for (int i = 0; i < a.length; i++) { max = a[i] > max ? a[i] : max; } int[] count = new int[max + 1]; for (int i = 0; i < a.length; i++) { count[a[i]]++; } for (int i = 1; i < count.length; i++) { count[i] += count[i - 1]; } int[] b = new int[a.length]; for (int i = 0; i < a.length; i++) { b[count[a[i]] - 1] = a[i]; count[a[i]]--; } return b; }
各個方法的測試代碼實現如下:
public static void main(String[] args) { int a[] = {49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 5, 4, 62, 99, 98, 54, 56, 17, 18, 23, 34, 15, 35, 25, 53, 51}; System.out.println(Arrays.toString(a)); System.out.println(Arrays.toString(binarySort(a))); }
運行結果如下:
[49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 5, 4, 62, 99, 98, 54, 56, 17, 18, 23, 34, 15, 35, 25, 53, 51] [4, 5, 12, 13, 15, 17, 18, 23, 25, 27, 34, 34, 35, 38, 49, 49, 51, 53, 54, 56, 62, 64, 65, 76, 78, 97, 98, 99] Process finished with exit code 0