標籤:tool 傳統 直接 www 就是 ons fonts 總結 尋找
在排序過程中,全部記錄存放在記憶體,則稱為內排序,如果排序過程中需要使用外存,則稱為外排序。
一般來說外排序分為兩個步驟:預先處理和合并排序。首先,根據可用記憶體的大小,將外存上含有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常用排序演算法