Java常用排序算法
在排序過程中,全部記錄存放在內存,則稱為內排序,如果排序過程中需要使用外存,則稱為外排序。
一般來說外排序分為兩個步驟:預處理和合並排序。首先,根據可用內存的大小,將外存上含有n個紀錄的文件分成若幹長度為t的子文件(或段);其次,利用內部排序的方法,對每個子文件的t個紀錄進行內部排序。這些經過排序的子文件(段)通常稱為順串(run),順串生成後即將其寫入外存。這樣在外存上就得到了m個順串(m=[n/t])。最後,對這些順串進行歸並,使順串的長度逐漸增大,直到所有的待排序的幾率成為一個順串為止。
內排序可以分為以下幾類:
(1)、插入排序:直接插入排序、折半插入排序、希爾排序。
(2)、選擇排序:簡單選擇排序、堆排序。
(3)、交換排序:冒泡排序、快速排序。
外排序可以分為一下幾類(既使用內部存儲也使用外部存儲,內存不夠時建議使用):
(4)、歸並排序
(5)、基數排序
穩定性:就是能保證排序前兩個相等的數據其在序列中的先後位置順序與排序後它們兩個先後位置順序相同。再簡單具體一點,如果A i == A j,Ai 原來在 Aj 位置前,排序後 Ai 仍然是在 Aj 位置前。
不穩定:簡單選擇排序、快速排序、希爾排序、堆排序不是穩定的排序算法
穩定:冒泡排序、直接插入排序、折半插入排序,歸並排序和基數排序都是穩定的排序算法。
平均時間復雜度
O(n^2):直接插入排序,簡單選擇排序,冒泡排序。
在數據規模較小時(9W內),直接插入排序,簡單選擇排序差不多。當數據較大時,冒泡排序算法的時間代價最高。性能為O(n^2)的算法基本上是相鄰元素進行比較,基 本上都是穩定的。
O(nlogn):快速排序,歸並排序,希爾排序,堆排序。
其中,快排是最好的, 其次是歸並和希爾,堆排序在數據量很大時效果明顯。
一、插入排序
?思想:每步將一個待排序的記錄,按其順序碼大小插入到前面已經排序的字序列的合適位置,直到全部插入排序完為止。
?關鍵問題:在前面已經排好序的序列中找到合適的插入位置。
?方法:
–直接插入排序
–折半插入排序
–希爾排序
1.直接插入排序
圖例: 關鍵代碼:private static void directInsertSort(int[] array) { for(int i=0;i<array.length;i++){ for(int j=0;j<i;j++){ if(array[i]<array[j]){ int temp=array[i]; System.arraycopy(array,j,array,j+1,i-j); array[j]=temp; } } } }
2.折半插入排序(二分法插入排序)
折半插入排序(binary insertion sort)是對插入排序算法的一種改進,由於排序算法過程中,就是不斷的依次將元素插入前面已排好序的序列中。由於前半部分為已排好序的數列,這樣我們不用按順序依次尋找插入點,可以采用折半查找的方法來加快尋找插入點的速度。
圖例: 關鍵代碼:private static void binaryInsertSort(int[] array) { for(int i=1;i<array.length;i++){ int tempData=array[i]; int low=0; int high=i-1; while(low<=high){ int middle=(low+high)/2; if(array[middle]<tempData) low=middle+1; else high=middle-1; } System.arraycopy(array,low,array,low+1,i-low); array[low]=tempData; } }
3.希爾排序
希爾排序(Shell Sort)是插入排序的一種,是針對直接插入排序算法的改進,是將整個無序列分割成若幹小的子序列分別進行插入排序,希爾排序並不穩定。該方法又稱縮小增量排序,因DL.Shell於1959年提出而得名。The worst-case number of compares used by shellsort withthe 3x+1 increments is O(N 3/2).
圖例:
關鍵代碼:
/** * shell排序算法 * 增量h=(h*3)+1; 這個增量公式是由Knuth給出的 * 如果不是很了解的話請百度一下吧 * @param array */ private static void shellSort(int[] array) { //首先根據數組的長度確定增量的最大值 int h=1; // 按h * 3 + 1得到增量序列的最大值 while(h <= array.length / 3) h = h * 3 + 1; //進行增量查找和排序 while(h>=1){ for(int i=h;i<array.length;i++){ for(int j=i;j >= h && array[j] < array[j-h];j -= h){ int temp = array[j]; array[j] = array[j-h]; array[j-h] = temp; } } h = h/3; } }
二、選擇排序
?思想:每趟從待排序的記錄序列中選擇關鍵字最小的記錄放置到已排序表的最前位置,直到全部排完。 ?關鍵問題:在剩余的待排序記錄序列中找到最小關鍵碼記錄。 ?方法: –直接選擇排序 –堆排序1.直接選擇排序
在要排序的一組數中,選出最小的一個數與第一個位置的數交換;然後在剩下的數當中再找最小的與第二個位置的數交換,如此循環到倒數第二個數和最後一個數比較為止。 圖例: 關鍵代碼:public static void directSelectionSort(int[] array){ for(int i=0;i<array.length-1;i++){ for(int j=i+1;j<array.length;j++){ if(array[i]>array[j]){ int temp = array[i]; array[i] = array[j]; array[j] = temp; } } } }
2.堆排序
初始時把要排序的數的序列看作是一棵順序存儲的二叉樹,調整它們的存儲序,使之成為一個 堆,這時堆的根節點的數最大。然後將根節點與堆的最後一個節點交換。然後對前面(n-1)個數重新調整使之成為堆。依此類推,直到只有兩個節點的堆,並對 它們作交換,最後得到有n個節點的有序序列。堆排序也是一種不穩定的排序算法。堆排序優於簡單選擇排序的原因:直接選擇排序中,為了從R[1..n]中選出關鍵字最小的記錄,必須進行n-1次比較,然後在R[2..n]中選出關鍵字最小的記錄,又需要做n-2次比較。事實上,後面的n-2次比較中,有許多比較可能在前面的n-1次比較中已經做過,但由於前一趟排序時未保留這些比較結果,所以後一趟排序時又重復執行了這些比較操作。堆排序可通過樹形結構保存部分比較結果,可減少比較次數。堆排序的最壞時間復雜度為O(nlogn)。堆序的平均性能較接近於最壞性能。由於建初始堆所需的比較次數較多,所以堆排序不適宜於記錄數較少的文件。
圖例: 建堆: 交換,從堆中踢出最後一個數: 關鍵代碼:public static void buildMInHeap(int[] array){ for(int i=array.length-1;i>0;i--){ for(int j=(i-1)/2;j>=0;j--){ //i是奇數,存在一個節點只有一個葉子節點 if((2*j+1==i)&&(i%2!=0)){ if(array[j]<array[2*j+1]) swap(array,j,2*j+1); }else{ if(array[j]<array[2*j+1]) swap(array,j,2*j+1); if(array[j]<array[2*j+2]) swap(array,j,2*j+2); } } swap(array,0,i); } } private static void swap(int[] array,int i,int j){ int temp=array[i]; array[i]=array[j]; array[j]=temp; }
三、交換排序
1.冒泡排序
圖例:關鍵代碼:
public static int[] bubbleSort(int[] array){ if(array == null) return array; int len = array.length; for(int i = len - 1; i > 0; i--){ for(int j = 0; j < i; j++){ if(array[j] > array[j + 1]){ int temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; } } } return array; }
分析:
冒泡排序是一種穩定的排序方法。
?若文件初狀為正序,則一趟起泡就可完成排序,排序碼的比較次數為n-1,且沒有記錄移動,時間復雜度是O(n) ?若文件初態為逆序,則需要n-1趟起泡,每趟進行n-i次排序碼的比較,且每次比較都移動三次,比較和移動次數均達到最大值∶O(n2) ?起泡排序平均時間復雜度為O(n2)2.快速排序
選擇一個基準元素,通常選擇第一個元素或者最後一個元素,通過一趟掃描,將待排序列分成兩部分,一部分比基準元素小,一部分大於等於基準元素,此時基準元素在其排好序後的正確位置,然後再用同樣的方法遞歸地排序劃分的兩部分。 傳統方法圖例: 關鍵代碼:public static void quickSort(int[] array,int start,int end) { if(start==end) return; int i=start+1; int j=end; while(i<j){ for(;array[i]<array[start]&&i<j;i++); for(;array[j]>array[start]&&j>i;j--); swap(array,i,j); } if(array[i]<array[start]) swap(array,i,start); quickSort(array,start,i-1); quickSort(array,i,end); } public static void swap(int[] array, int i, int j){ int temp = array[i]; array[i] = array[j]; array[j] = temp; }
分析:
快速排序是不穩定的排序。
快速排序的時間復雜度為O(nlogn)。
當n較大時使用快排比較好,當序列基本有序時用快排反而不好。
四、歸並排序
歸並(Merge)排序法是將兩個(或兩個以上)有序表合並成一個新的有序表,即把待排序序列分為若幹個子序列,每個子序列是有序的。然後再把有序子序列合並為整體有序序列。 圖例: 關鍵代碼:private static void sort(int[] array,int low,int high) { if(low < high){ int middle=(low + high)/2; //遞歸處理相關的合並事項 sort(array,low,middle); sort(array,middle+1,high); merge(array,low,middle,high); } } private static void merge(int[] array, int low, int middle, int high) { //創建一個臨時數組用來存儲合並後的數據 int[] temp=new int[array.length]; int i = low; int j = middle+1; int k = low; while(i <= middle && j <= high){ if(array[i]<array[j]) temp[k++]=array[i++]; else temp[k++]=array[j++]; } //處理剩余未合並的部分 while(i <= middle) temp[k++]=array[i++]; while(j <= high) temp[k++]=array[j++]; //將臨時數組中的內容存儲到原數組中 while(low <= high) array[low]=temp[low++]; }
分析:
歸並排序是穩定的排序方法。
歸並排序的時間復雜度為O(nlogn)。
速度僅次於快速排序,為穩定排序算法,一般用於對總體無序,但是各子項相對有序的數列。
五、基數排序
將所有待比較數值(正整數)統一為同樣的數位長度,數位較短的數前面補零。然後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後,數列就變成一個有序序列。 圖例: 關鍵代碼:public static int[] radixSort(int[] array){ //找到最大數,確定要排序幾趟 int max = 0; for (int i = 0; i < array.length; i++){ if(max<array[i]) max = array[i]; } //判斷位數 int times = 0; while(max>0){ max = max/10; times++; } //建立十個隊列 List<ArrayList<Integer>> alist = new ArrayList<ArrayList<Integer>>(); for (int i = 0; i < 10; i++){ ArrayList<Integer> queue = new ArrayList<Integer>(); alist.add(queue); } //進行times次分配和收集 for (int i = 0; i < times; i++){ //分配 for (int j = 0; j < array.length; j++){ int x = array[j]%(int)Math.pow(10, i+1)/(int)Math.pow(10, i); alist.get(x).add(array[j]); } //收集 int count = 0; for (int j = 0; j < 10; j++){ ArrayList<Integer> queue = alist.get(j); while(queue.size()>0){ array[count++] = queue.remove(0); } } } return array; }
分析:
基數排序是穩定的排序算法。
基數排序的時間復雜度為O(d(n+r)),d為位數,r為基數。
總結:工作中基本用不到但是面試常面考試常考的算法題整理。考前面前臨時突擊。
Java常用排序算法