7種常見的排序算法

package test.arithmetic;
public class SortMethod {
    
    public static void printArray(int[] array) {  
        System.out.print("{");  
        for (int i = 0; i < array.length; i++) {  
            System.out.print(array[i]);  
            if (i < array.length - 1) {  
                System.out.print(", ");  
            }  
        }  
        System.out.println("}");  
    } 
    
 /**
  * 1.冒泡排序 
  * 優勢:穩定,比較次數已知;
  * 缺點:慢,每次只能移動相鄰兩個數據,移動數據的次數多。 
  * 初始關鍵字 [49 38 65 97 76 13 27 49]
  * 第一趟排序後 [38 49 65 76 13 27 49] 97
  * 第二趟排序後 [38 49 65 13 27 49] 76 97
  * 第三趟排序後 [38 49 13 27 49] 65 76 97 
  * 第四趟排序後 [38 13 27 49] 49 65 76 97 
  * 第五趟排序後 [38 13 27] 49 49 65 76 97 
  * 第六趟排序後 [13 27]38 49 49 65 76 97 
  * 第七趟排序後 [13] 27 38 49 49 65 76 97 
  * 最後排序結果 13 27 38 49 49 76 76 97
  */
 public static void bubbleSort() {
  int a[] = { 49, 38, 65, 97, 76, 13, 27, 49 };
  for (int i = a.length - 1; i >= 0; i--) {
   for (int j = 0; j < i; j++) {
    if (a[j] > a[j + 1]) {
     int k = a[j];
     a[j] = a[j + 1];
     a[j + 1] = k;
    }
   }
  }
  for (int i = 0; i < a.length; i++) {
   System.out.print(a[i] + ", ");
  }
 }
 /**
  * 2.選擇排序
  * ①初始狀態:無序區爲R[1..n],有序區爲空。
  * ②第1趟排序
  * 在無序區R[1..n]中選出關鍵字最小的記錄R[k],將它與無序區的第1個記錄R[1]交換,使R[1..1]和R[2..n]分別變爲記錄個數增長1個的新有序區和記錄個數減小1個的新無序區。
  * ……
  * ③第i趟排序
  * 第i趟排序開始時,當前有序區和無序區分別爲R[1..i-1]和R[i..n](1≤i≤n-1)。該趟排序從當前無序區中選出關鍵字最小的記錄R[k],將它與無序區的第1個記錄R交換,使R[1..i]和R[i+1..n]分別變爲記錄個數增長1個的新有序區和記錄個數減小1個的新無序區.這樣,n個記錄的文件的直接選擇排序可通過n-1趟直接選擇排序獲得有序結果。
  * 優勢:穩定,比較次數與冒泡排序同樣;
  * 缺點:相對之下仍是慢。
  * 初始關鍵字 [49 38 65 97 76 13 27 49]
  * 第一趟排序後 13 [38 65 97 76 49 27 49]
  * 第二趟排序後 13 27 [65 97 76 49 38 49]
  * 第三趟排序後 13 27 38 [97 76 49 65 49]
  * 第四趟排序後 13 27 38 49 [49 97 65 76]
  * 第五趟排序後 13 27 38 49 49 [97 76 76]
  * 第六趟排序後 13 27 38 49 49 76 [76 97]
  * 第七趟排序後 13 27 38 49 49 76 76 [ 97]
  * 最後排序結果 13 27 38 49 49 76 76 97
 */
 public static void selectionSort() {
  int a[] = { 49, 38, 65, 97, 76, 13, 27, 49 };
  int k = 0;
  for (int i = 0; i < a.length - 1; i++) {
   k = i;
   for(int j=i+1; j<a.length; j++){
    if(a[k] > a[j]){
     k = j;
    }
   }
   if(k != i){
    int temp = a[k];
    a[k] = a[i];
    a[i] = temp;
   }
   System.out.println("\n" + (i + 1) + ": ");
   for (int m = 0; m < a.length; m++) {
    System.out.print(a[m] + ", ");
   }
  }
 }
 
 /**
  * 3.插入排序
  * 已知一組,一組無序數據b[1]、b[2]、……b[m],需將其變成一個升序數列。先建立一個變量a。首先將不b[1]與b[2],若是b[1]大於b[2]則交換位置,不然不變;
  * 再比較b[2]與b[3], 若是b[3]小於b[2],則將b[3]賦值給a,再將a與b[1]比較,若是a小於b[1];則將b[2],b[1]依次後移;在將a放在b[1]處以此類推,直到排序結束。
  * 初始關鍵字 [49 38 65 97 76 13 27 59]
  * a b[1] b[2]? b[3]? b[4] b[5]? b[6]? b[7] b[8]
  * 1—–49? 49 >? 38 65 97 ? 76? 13 ? 27 59
  * 38 49 49 ……….
  * 38 38 49 ……….
  * 2—–38? 38 ? 49 < 65? 97 ? 76 13 27 59
  * 3—–38? 38 ? 49? 65 <97 ? 76 13 27 59
  * 4—-38? 38 49? 65 97> 76 13 27 59
  * 76 38 49 65 97 97……..
  * 76 38 49 65 76 97……..
 */
 public static void straightInsertionSort(){  
  int[] array = { 3, -1, 0, -8, 2, 1 };
     int sentinel , j;  
     for (int i = 1; i < array.length; i++) {  
         j = i - 1;  
         sentinel = array[i];//哨兵位  
         while (j >= 0 && sentinel < array[j]) {  
             array[j+1] = array[j];//將大於sentinel的值總體後移一個單位   
             j--;  
         }  
         array[j+1] = sentinel;  
     }  
     printArray(array);
 } 
 
 /**
  * 4.縮小增量排序(希爾排序)
  * 由希爾在1959年提出,又稱希爾排序。
  * 已知一組無序數據a[1]、a[2]、……a[n],需將其按升序排列。發現當n不大時,插入排序的效果很好。
  * 首先取一增量d(d<n),將a[1]、a[1+d]、a[1+2d]……列爲第一組,a[2]、a[2+d]、a[2+2d]……列爲第二組……,a[d]、a[2d]、a[3d]……列爲最後一組以次類推,
  * 在各組內用插入排序,而後取d’<d,重複上述操做,直到d=1。增量d(1, 3, 7,15, 31, …, 2^k-1)是使用最普遍的增量序列之一.
  * 優勢:快,數據移動少;
  * 缺點:不穩定,d的取值是多少,應取多少個不一樣的值,都沒法確切知道,只能憑經驗來取。
  * 初始:d=5
  * 49 38 65? 97 76 13? 27 49 55? 44
  * 一趟結果
  * 13 27 49 55 44 49? 38 65 97 76
  * d=3 |———————-|———————-|———————|
  * 二趟結果
  * 13 44 49 38 27 49? 55 65 97? 76
  * d=1
  * 三趟結果
  * 13 27 38 44 49? 49 55 65 76? 97
  * 希爾排序的原理:根據需求,若是你想要結果從大到小排列,它會首先將數組進行分組,而後將較大值移到前面,較小值 
  * 移到後面,最後將整個數組進行插入排序,這樣比起一開始就用插入排序減小了數據交換和移動的次數,能夠說希爾排序是增強 
  * 版的插入排序 
  * 拿數組5, 2, 8, 9, 1, 3,4來講,數組長度爲7,當increment爲3時,數組分爲兩個序列 
  * 5,2,8和9,1,3,4,第一次排序,9和5比較,1和2比較,3和8比較,4和比其下標值小increment的數組值相比較 
  * 此例子是按照從大到小排列,因此大的會排在前面,第一次排序後數組爲9, 2, 8, 5, 1, 3,4 
  * 第一次後increment的值變爲3/2=1,此時對數組進行插入排序, 
  */
 public static void shellSort(){
  int j = 0;
  int temp = 0;
  int[] data = new int[] { 5, 2, 8, 9, 1, 3, 4 };
  for (int increment = data.length / 2; increment > 0; increment /= 2) {
   for (int i = increment; i < data.length; i++) {
    temp = data[i];
    for (j = i; j >= increment; j -= increment) {
     if (temp > data[j - increment]) {
      data[j] = data[j - increment];
     } else {
      break;
     }
    }
    data[j] = temp;
   }
  }
 }
 /**
  * 5.快速排序
  * 快速排序是冒泡排序的改進版,是目前已知的最快的排序方法。
  * 已知一組無序數據a[1]、a[2]、……a[n],需將其按升序排列。
  * 首先任取數據a[x]做爲基準。比較a[x]與其它數據並排序,使a[x]排在數據的第k位,而且使a[1]~a[k-1]中的每個數據<a[x],a[k+1]~a[n]中的每個數據>a[x],
  * 而後採用分治的策略分別對a[1]~a[k-1]和a[k+1]~a[n]
  * 兩組數據進行快速排序。
  * 優勢:極快,數據移動少;
  * 缺點:不穩定。
  * 分段插入排序
  * @param args
  */
 public static void quicksort(int n[], int left, int right) {
  int dp;
  if (left < right) {
   dp = partition(n, left, right);
   quicksort(n, left, dp - 1);
   quicksort(n, dp + 1, right);
  }
 }
 public static int partition(int[] n, int left, int right) {
  int pivotkey = n[left];
  // 樞軸選定後永遠不變,最終在中間,前小後大
  while (left < right) {
   while (left < right && n[right] >= pivotkey)
    --right;
   // 將比樞軸小的元素移到低端,此時right位至關於空,等待低位比pivotkey大的數補上
   n[left] = n[right];
   while (left < right && n[left] <= pivotkey)
    ++left;
   // 將比樞軸大的元素移到高端,此時left位至關於空,等待高位比pivotkey小的數補上
   n[right] = n[left];
  }
  // 當left == right,完成一趟快速排序,此時left位至關於空,等待pivotkey補上
  n[left] = pivotkey;
  return left;
 }
 public static void quicksortMain(){
  int array[] = { 9, 5, 4, 8, 7, 3, 2, 10 };
  quicksort(array, 0, array.length-1);
  printArray(array);
 }
 
 
 /**
  * 6.歸併排序算法
  * 合併排序(MERGESORT)是又一類不一樣的排序方法,合併的含義就是將兩個或兩個以上的有序數據序列合併成一個新的有序數據序列,所以它又叫歸併算法。
  * 它的基本思想就是假設數組A有N個元素,那麼能夠當作數組A是又N個有序的子序列組成,每一個子序列的長度爲1,而後再兩兩合併,獲得了一個 N/2? 個長度爲2或1的有序子序列,再兩兩合併,如此重複,值得獲得一個長度爲N的有序數據序列爲止,這種排序方法稱爲2—路合併排序。
  * 例如數組A有7個數據,分別是: 49 38 65 97 76 13? 27,那麼採用歸併排序算法的操做過程如圖7所示:
  * 初始值 [49] [38]? [65] [97] [76]? [13] [27]
  * 第一次合併以後 [38 ? 49] ? [65 ? 97]? [13 76] [27]
  * 第二次合併以後 [38 ? 49 ? 65 ? 97]? [13 27 76]
  * 第三次合併以後 [13 ? 27 ? 38 ? 49 ? 65 ? 76 ? 97]
  * 合併算法的核心操做就是將一維數組中先後相鄰的兩個兩個有序序列合併成一個有序序列。合併算法也能夠採用遞歸算法來實現,形式上較爲簡單,但實用性不好。
  * 合併算法的合併次數是一個很是重要的量,根據計算當數組中有3到4個元素時,合併次數是2次,當有5到8個元素時,合併次數是3次,當有9到16個元素時,合併次數是4次,按照這一規律,當有N個子序列時能夠推斷出合併的次數是X(2? >=N,符合此條件的最小那個X)。
  * 其時間複雜度爲:O(nlogn).所需輔助存儲空間爲:O(n)
  */
 public static void merge(int a[], int low, int poi, int high) {
  int l1 = poi - low + 1;
  int l2 = high - poi;
  int[] array1 = new int[l1 + 1];
  int[] array2 = new int[l2 + 1];
  for (int i = 0; i < l1; i++) {
   array1[i] = a[low + i];
  }
  for (int i = 0; i < l2; i++) {
   array2[i] = a[poi + 1 + i];
  }
  // 將數組中的最後一個元素設置爲最大值,這樣就沒必要擔憂會有數據元素剩餘,
  // 同時k的取值範圍決定了無窮值不會被添加到原數據中
  array1[l1] = Integer.MAX_VALUE;
  array2[l2] = Integer.MAX_VALUE;
  int i = 0;
  int j = 0;
  for (int k = low; k <= high; k++) {
   if (array1[i] <= array2[j]) {
    a[low++] = array1[i];
    i++;
   } else {
    a[low++] = array2[j];
    j++;
   }
  }
 }
 
 public static void mergeSort(int a[], int low, int high) {
  if (low == high) {
   return;
  } else {
   int poi = (low + high) / 2;
   mergeSort(a, low, poi);
   mergeSort(a, poi + 1, high);
   merge(a, low, poi, high);
  }
 }
 
 public static void mergeSortTest() {
  int[] array = { 1, 3, 6, 4, 13, 14, 8, 12, 9, 7, 0};
  mergeSort(array, 0, array.length - 1);
  printArray(array);
 }
 
 
 /**
  * 7. 堆排序
  * 根結點(亦稱爲堆頂)的關鍵字是堆裏全部結點關鍵字中最小者的堆稱爲小根堆。
  * 根結點(亦稱爲堆頂)的關鍵字是堆裏全部結點關鍵字中最大者,稱爲大根堆。
  * n個關鍵字序列Kl,K2,…,Kn稱爲堆,當且僅當該序列知足以下性質(簡稱爲堆性質):
  * (1) ki≤K2i且ki≤K2i+1 或(2)Ki≥K2i且ki≥K2i+1(1≤i≤ )
  * 堆排序利用了大根堆(或小根堆)堆頂記錄的關鍵字最大(或最小)這一特徵,使得在當前無序區中選取最大(或最小)關鍵字的記錄變得簡單。
  * (1)用大根堆排序的基本思想
  * ① 先將初始文件R[1..n]建成一個大根堆,此堆爲初始的無序區
  * ② 再將關鍵字最大的記錄R[1](即堆頂)和無序區的最後一個記錄R[n]交換,由此獲得新的無序區R[1..n-1]和有序區R[n],且知足R[1..n-1].keys≤R[n].key
  * ③ 因爲交換後新的根R[1]可能違反堆性質,故應將當前無序區R[1..n-1]調整爲堆。而後再次將R[1..n-1]中關鍵字最大的記錄R[1]和該區間的最後一個記錄R[n-1]交換,由此獲得新的無序區R[1..n-2]和有序區R[n-1..n],且仍知足關係R[1..n-2].keys≤R[n-1..n].keys,一樣要將R[1..n-2]調整爲堆。
  * 直到無序區只有一個元素爲止。
  * (2)大根堆排序算法的基本操做:
  * ① 初始化操做:將R[1..n]構造爲初始堆;
  * ② 每一趟排序的基本操做:將當前無序區的堆頂記錄R[1]和該區間的最後一個記錄交換,而後將新的無序區調整爲堆(亦稱重建堆)。
  * 注意:
  * ①只需作n-1趟排序,選出較大的n-1個關鍵字便可以使得文件遞增有序。
  * ②用小根堆排序與利用大根堆相似,只不過其排序結果是遞減有序的。堆排序和直接選擇排序相反:在任什麼時候刻,堆排序中無序區老是在有序區以前,且有序區是在原向量的尾部由後往前逐步擴大至整個向量爲止。
  */
 public static void heapSort(int[] array) {
  buildHeap(array);// 構建堆
  int n = array.length;
  int i = 0;
  for (i = n - 1; i >= 1; i--) {
   swap(array, 0, i);
   heapify(array, 0, i);
  }
 }
 public static void buildHeap(int[] array) {
  int n = array.length;// 數組中元素的個數
  for (int i = n / 2 - 1; i >= 0; i--)
   heapify(array, i, n);
 }
 public static void heapify(int[] A, int idx, int max) {
  int left = 2 * idx + 1;// 左孩子的下標(若是存在的話)
  int right = 2 * idx + 2;// 左孩子的下標(若是存在的話)
  int largest = 0;// 尋找3個節點中最大值節點的下標
  if (left < max && A[left] > A[idx])
   largest = left;
  else
   largest = idx;
  if (right < max && A[right] > A[largest])
   largest = right;
  if (largest != idx) {
   swap(A, largest, idx);
   heapify(A, largest, max);
  }
 }
 public static void swap(int[] array, int i, int j) {
  int temp = 0;
  temp = array[i];
  array[i] = array[j];
  array[j] = temp;
 }
 //---------------------------------
 
 
 public static void main(String[] args) {
  int array[] = {11,2,3,14,5,6,1,8,9};
  heapSort(array);
  printArray(array);
 }
}
相關文章
相關標籤/搜索