常用演算法(Java表述)
氣泡排序(Bubble Sort)
時間複雜度 O(n2),裡層迴圈每趟比較第 j 項和第 j+1項,如果前項大於後項,則發生交換。缺點是每次比較後都可能發生交換,交換次數太多了,值從小到大。
通俗概述:依次比較相鄰兩關鍵字,如果反序則立即發生交換,如果正序則繼續比較下一個相鄰項,雙重巢狀迴圈實現
參考程式碼:
package com.tyut; import java.util.Arrays; /** * 氣泡排序:利用雙重迴圈,如果前一個數比後一個數大,則發生交換,每次比較都發生交換 優化版:新增flag標記,迴圈外定義flag標記為true,進入迴圈後flag設為false,
若一次迴圈後發生了交換則將flag設為ture,進入下次迴圈;若整次迴圈只發生比較未發生交換,則說明陣列已經有序,後續迴圈比較則不必要了,減少比較次數,提升效能*/ public class BubbleSort { public static void bubbleSort(int[] arrs) { int temp = 0; boolean flag=true;//用flag作標記,當沒有發生交換的時候,說明陣列已經是有序的了。 for (int i = 0; i < arrs.length - 1&&flag; i++) { flag=false;//flag標記初始化為false; for (int j = 0; j < arrs.length - i - 1; j++) {//判斷當前項是否大於後一項 if (arrs[j] > arrs[j + 1]) {//是,則發生交換 temp = arrs[j]; arrs[j] = arrs[j + 1]; arrs[j + 1] = temp; flag=true;//發生交換flag標記設為true } } } } publicstatic void main(String[] args) { int[] arr=new int[]{2,6,5,4,2,4,6,9,7}; System.out.println(Arrays.toString(arr)); bubbleSort(arr); System.out.println(Arrays.toString(arr)); } }
簡單選擇排序(Simple Selection Sort)
簡單選擇排序:就是通過n-1次關鍵字間的比較,從n-i+1個記錄中選出關鍵字最小的記錄,並和i(1<=i<=n)個記錄交換值
時間複雜度 O(n2),在氣泡排序的基礎上,只不過每次比較後,將儲存最小值的索引指向小值,在一次迴圈結束時,將最小值與迴圈首項發生交換。整體分為已排序和未排序兩部分,值從小到大。
注意區別:氣泡排序跟直接選擇排序都是依次比較相鄰的記錄,但是氣泡排序是一有反序立即交換,而直接插入排序則是出現反序將最小值記錄下來,最後再發生交換。
參考程式碼:
package com.tyut; import java.util.Arrays; /** * 簡單選擇排序:在待排序中找出最小值,然後放到已排序的末尾 * 利用雙重for迴圈,找出最小值,再發生一次交換 */ public class SelectionSort { //需要遍歷獲得最小值的次數 //要注意一點,當要排序N個數,已經經過N-1次遍歷後,已經是有序數列 public static void selectSort(int[] arrs) { for (int i = 0; i < arrs.length - 1; i++) { int temp=0; int index=i;//用來儲存最小的索引,初始指向當前第i項 //從未排序數中找出最小值,這裡最大索引應該是陣列最後一位即length-1 for (int j = i+1; j <arrs.length; j++) { //如果後面的值比索引值還小,則將索引指向最小值 if (arrs[j]<arrs[index]){ index=j; } } //已經找到最小值,然後將最小值放到已排序的末尾 temp=arrs[i]; arrs[i]=arrs[index]; arrs[index]=temp; } } public static void main(String[] args) { int[] arrs=new int[]{2,6,8,99,4,46,4,5}; System.out.println(Arrays.toString(arrs)); selectSort(arrs); System.out.println(Arrays.toString(arrs)); } }
時間複雜度O(n2),利用雙重for迴圈,從第二個數開始遍歷,然後儲存待插入的數,這樣有序部分則可以向後覆蓋一個位置,然後裡層迴圈 j >= 0 && temp < arrs[j] 找到合適的位置後,只需要將待插入的數插入即可。
參考程式碼:
package com.tyut; import java.util.Arrays; /** * 插入排序: */ public class InsertionSort { public static void insertSort(int[] arrs) { //第一個肯定是有序的,所以從第二個數開始遍歷 for (int i = 1 ;i < arrs.length - 1; i++) { int j=0; int temp = arrs[i];//取出第i給數,和前i-1個數比較,插入合適位置。 //因為前i-1個數都是從小到大的有序序列,所以只要當前比較的數arr[j]比temp大,就把這個數後移一位 for (j = i - 1; j >= 0 && temp < arrs[j]; j--) { arrs[j+1]=arrs[j];//因為第i個數已經儲存為temp,所以可以向後覆蓋 } //將第i個值插入到合適位置,因為交換結束後再自減運算,所以這裡需要+1 arrs[j+1]=temp; } } public static void main(String[] args) { int[] arrs=new int[]{2,6,5,2,3,4,3,0,6}; System.out.println(Arrays.toString(arrs)); insertSort(arrs); System.out.println(Arrays.toString(arrs)); } }
希爾排序(Shell Sort)
時間複雜度為O(nlogn),該方法因DL.Shell於1959年提出而得名。超越了O(n2)的演算法的歷史。他是在直接插入排序的增強,將記錄按步長gap分組,然後在分組內進行直接插入排序。不穩定演算法。
參考程式碼:
package com.tyut; import java.util.Arrays; /** * 希爾排序:在直接插入排序的基礎上,將記錄按步長進行分組 * 所以可知,直接插入排序相當與步長為1的希爾排序 */ public class ShellSort { public static void shellSort(int[] arrs) { //初始化步長為陣列長度的一半 int gap = arrs.length / 2; //迴圈結束條件,當gap<1是,迴圈結束,即排序結束 while (gap >= 1) { //把距離為gap的元素編為一個組,掃描所有組 for (int i = gap; i < arrs.length; i++) { int j = 0; int temp = arrs[i];//儲存待插入元素,以便組內元素向後移動覆蓋 //對距離為gap的組內元素進行直接選擇排序 for (j = i - gap; j >= 0 && temp < arrs[j]; j = j - gap) { arrs[j+gap]=arrs[j]; } //將待插入元素插入合適位置 arrs[j+gap]=temp; } //一次迴圈結束,所有分組均已完成直接選擇排序,則可將將距離縮小為原本的一半 gap=gap/2; } } public static void main(String[] args) { int[] arrs=new int[]{3,6,8,4,6,7,9,8,7,5}; System.out.println(Arrays.toString(arrs)); shellSort(arrs); System.out.println(Arrays.toString(arrs)); } }
堆排序:就是利用堆進行排序的方法。它的基本思想是,將待排序的序列構造成一個大頂堆。此時,整個序列的最大值就是堆頂的根結點,將它移走(其實就是將其與堆陣列的末尾元素交換,此時末尾元素就是最大值),然後將剩餘的n-1給序列重新構造成一個堆,這樣就會得到n個元素中的次小值。如此反覆執行,便能得到一個有序序列。--------用直接插入排序,將陣列調整為堆結構,然後再簡單選擇排序,選擇最值交換,再調整堆結構。
參考程式碼:
package com.tyut; import java.util.Arrays; /** * 堆排序:利用堆(順序儲存的完全二叉樹)進行排序,每次取出堆頂最值後,對堆進行調整,使堆頂元素(根結點)為最值 * 先用簡單選擇排序篩選出子節點中的最小值,然後通過直接插入排序,將父結點(待插入的值)插入到合適的位置。 */ public class HeapSort { /** * 將陣列調整為符合堆規律的結構 * @param arr 傳入需要調整的陣列 * @param parent 父結點 * @param length 需要調整的陣列長度 */ public void heapAdjust(int[] arr, int parent, int length) { int temp = arr[parent];//先儲存父結點的值,以便後續移動交換 int child = parent * 2 + 1;//先獲取到該父結點的左子結點 while (child < length) { //如果存在右子結點,且右子結點大於左子結點,則選取右子結點 if (child + 1 < length && arr[child] < arr[child + 1]) { child++; } //判斷父結點(待插入的值)是否比子節點大 if(temp>arr[child]){ break;//父結點大,結束當前迴圈 }else { /*此處類似與直接插入排序的思想*/ arr[parent]=arr[child];//將子結點的值覆蓋父節點的值 parent=child; child=child *2+1; } } //此時已經找到合適的位置,將待插入的值插入合適的位置 arr[parent]=temp; } /** *堆排序 * @param list */ public void shellSort(int[] list){ /*迴圈建立初始堆,初始父結點為陣列的一半,即完全二叉樹的最右下的父結點, 然後遞減,依次向上調整,這樣任意指定父結點調整時,下面的子節點已經是符合堆規律的*/ //迴圈建立初始化堆 for (int i = list.length/2; i >=0 ; i--) { heapAdjust(list,i,list.length); } //進行n-1次迴圈,完成排序,這裡類似於選擇排序的思想 for (int i = list.length-1; i >0; i--) { //將最大值list[0]與最後一個元素交換 int temp=list[i]; list[i]=list[0]; list[0]=temp; //交換完之後,最大值已經在底層陣列的末尾,然後將交換後的堆進行調整 heapAdjust(list,0,i);//注意這裡的長度已經-1了,所以堆調整不包含最後一個元素 } } public static void main(String[] args) { int[] arr=new int[]{2,6,4,9,2,3,54,1,6,166,52,6,656,54,451,6,56}; System.out.println(Arrays.toString(arr)); HeapSort heapSort=new HeapSort(); heapSort.shellSort(arr); System.out.println(Arrays.toString(arr)); } }
快速排序(Quick Sort)
快速排序是一種交換排序。快速排序由C. A. R. Hoare在1962年提出。
它的基本思想是:通過一趟排序將要排序的資料分割成獨立的兩部分:分割點左邊都是比它小的數,右邊都是比它大的數。然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴 進行,以此達到整個資料變成有序序列。
參考程式碼:
package com.tyut; import java.util.Arrays; /** * 快速排序 */ public class QuickSort { public void quickSort(int[] arr) { if (arr.length > 0) { quickSort(arr, 0, arr.length-1); } } private void quickSort(int[] arr, int left, int right) { //遞迴條件,左小標小於右下標 if (left < right) { int base = division(arr, left, right); //對基準數左側的一組數值進行遞迴切割,以致於將這些數值完整的排序 quickSort(arr,left,base-1); //對基準數右側的一組數值進行遞迴切割,以致於將這些數值完整的排序 quickSort(arr,base+1,right); } } /** * 用基準數切割陣列 * @param arr * @param left * @param right * @return */ private int division(int[] arr, int left, int right) { //以最左邊作為基準數,儲存最左邊的值,以便後續覆蓋 int base = arr[left]; while (left < right) { //從序列右端開始,向左遍歷,直到找到小於base的數 while (left < right && arr[right] >= base) { right--; } arr[left] = arr[right];//將小於基準數的右值覆蓋最左邊的位置 //從序列左端開始,向右遍歷,知道找到大於base的數 while (left < right && arr[left] <= base) { left++; } arr[right]=arr[left]; } //最後將base放到left的位置,此時,left位置的左側數值應該都比letf小,反之亦然 arr[left]=base; return left; } public static void main(String[] args) { int[] arr=new int[]{2,5,5,4,6,44,9,65,62,6,9,56,56,2,62,65,95,5,23,9,562,6,95,6,59,465,65,95,9}; System.out.println(Arrays.toString(arr)); QuickSort quickSort=new QuickSort(); quickSort.quickSort(arr); System.out.println(Arrays.toString(arr)); } }
*Java系統提供的Arrays.sort函式。對於基礎型別,底層使用快速排序。對於非基礎型別,底層使用歸併排序。請問是為什麼?
答:這是考慮到排序演算法的穩定性。對於基礎型別,相同值是無差別的,排序前後相同值的相對位置並不重要,所以選擇更為高效的快速排序,儘管它是不穩定的排序演算法;而對於非基礎型別,排序前後相等例項的相對位置不宜改變,所以選擇穩定的歸併排序。