Java常用排序算法及性能測試集合

週末天氣不好,在家無事,把常用排序算法理了一遍,收穫不小,特寫文章紀念。這些算法在學校的時候學過一遍,很多原理都忘記了。現在再回過頭理解,結合自己的體會, 選用最佳的方式描述這些算法,以方便理解它們的工作原理和程序設計技巧。本文適合做java面試準備的材料閱讀。

先附上一個測試報告:

  1. Array length: 20000  
  2. bubbleSort : 766 ms  
  3. bubbleSortAdvanced : 662 ms  
  4. bubbleSortAdvanced2 : 647 ms  
  5. selectSort : 252 ms  
  6. insertSort : 218 ms  
  7. insertSortAdvanced : 127 ms  
  8. insertSortAdvanced2 : 191 ms  
  9. binaryTreeSort : 3 ms  
  10. shellSort : 2 ms  
  11. shellSortAdvanced : 2 ms  
  12. shellSortAdvanced2 : 1 ms  
  13. mergeSort : 3 ms  
  14. quickSort : 1 ms  
  15. heapSort : 2 ms  
通過測試,可以認爲,冒泡排序完全有理由扔進垃圾桶。它存在的唯一理由可能是最好理解。希爾排序的高效性是我沒有想到的;堆排序比較難理解和編寫,要有宏觀的思維。


  1. package algorithm.sort;  
  2.   
  3. import java.lang.reflect.Method;  
  4. import java.util.Arrays;  
  5. import java.util.Date;  
  6.   
  7. /** 
  8.  * Java常用排序算法及性能測試集合 
  9.  *  
  10.  * 本程序集合涵蓋常用排序算法的編寫,並在註釋中配合極其簡單的特例講解了各種算法的工作原理,以方便理解和吸收; 
  11.  * 程序編寫過程中吸收了很多維基百科和別人blog上面的例子,並結合自己的思考,選擇或改進一個最容易讓人理解的寫法。 
  12.  * 同時包含一個集中式的性能測試和正確性測試方法,方便觀測。 
  13.  * @author http://blog.csdn.net/sunxing007 
  14.  * 轉載請註明來自http://blog.csdn.net/sunxing007 
  15.  */  
  16. public class SortUtil {  
  17.     // 被測試的方法集合  
  18.     static String[] methodNames = new String[]{  
  19.         "bubbleSort",  
  20.         "bubbleSortAdvanced",  
  21.         "bubbleSortAdvanced2",  
  22.         "selectSort",  
  23.         "insertSort",  
  24.         "insertSortAdvanced",  
  25.         "insertSortAdvanced2",  
  26.         "binaryTreeSort",  
  27.         "shellSort",  
  28.         "shellSortAdvanced",  
  29.         "shellSortAdvanced2",  
  30.         "mergeSort",  
  31.         "quickSort",  
  32.         "heapSort"  
  33.     };  
  34.     public static void main(String[] args) throws Exception{  
  35.         //correctnessTest();  
  36.         performanceTest(20000);  
  37.     }  
  38.       
  39.     /** 
  40.      * 正確性測試<br> 
  41.      * 簡單地測試一下各個算法的正確性<br> 
  42.      * 只是爲了方便觀測新添加的算法是否基本正確;<br> 
  43.      * @throws Exception 主要是反射相關的Exception;<br> 
  44.      */  
  45.     public static void correctnessTest() throws Exception{  
  46.         int len = 10;  
  47.         int[] a = new int[len];  
  48.         for(int i=0; i<methodNames.length; i++){  
  49.             for(int j=0; j<a.length; j++){  
  50.                 a[j] = (int)Math.floor(Math.random()*len*2);  
  51.             }  
  52.             Method sortMethod = null;  
  53.             sortMethod = SortUtil.class.getDeclaredMethod(methodNames[i], a.getClass());  
  54.             Object o = sortMethod.invoke(null, a);  
  55.             System.out.print(methodNames[i] + " : ");  
  56.             if(o==null){  
  57.                 System.out.println(Arrays.toString(a));  
  58.             }  
  59.             else{  
  60.                 //兼顧mergeSort,它的排序結果以返回值的形式出現;  
  61.                 System.out.println(Arrays.toString((int[])o));  
  62.             }  
  63.         }  
  64.     }  
  65.       
  66.     /** 
  67.      * 性能測試<br> 
  68.      * 數組長度用參數len傳入,每個方法跑20遍取耗時平均值;<br> 
  69.      * @param len 數組長度 建議取10000以上,否則有些算法會顯示耗時爲0;<br> 
  70.      * @throws Exception 主要是反射相關的Exception;<br> 
  71.      */  
  72.     public static void performanceTest(int len) throws Exception{  
  73.         int[] a = new int[len];  
  74.         int times = 20;  
  75.           
  76.         System.out.println("Array length: " + a.length);  
  77.         for(int i=0; i<methodNames.length; i++){  
  78.             Method sortMethod = null;  
  79.             sortMethod = SortUtil.class.getDeclaredMethod(methodNames[i], a.getClass());  
  80.             int totalTime = 0;  
  81.             for(int j=0; j<times; j++){  
  82.                 for(int k=0; k<len; k++){  
  83.                     a[k] = (int)Math.floor(Math.random()*20000);  
  84.                 }  
  85.                 long start = new Date().getTime();  
  86.                 sortMethod.invoke(null, a);  
  87.                 long end = new Date().getTime();  
  88.                 totalTime +=(end-start);  
  89.             }  
  90.             System.out.println(methodNames[i] + " : " + (totalTime/times) + " ms");  
  91.             //System.out.println(Arrays.toString(a));  
  92.         }  
  93.     }  
  94.       
  95.     /** 
  96.      * 最原始的冒泡交換排序;<br> 
  97.      * 兩層遍歷,外層控制掃描的次數,內層控制比較的次數;<br> 
  98.      * 外層每掃描一次,就有一個最大的元素沉底;所以內層的比較次數將逐漸減小;<br> 
  99.      * 時間複雜度: 平均:O(n^2),最好:O(n);最壞:O(n^2); 
  100.      * 空間複雜度: O(1); 
  101.      */  
  102.     public static void bubbleSort(int[] a){  
  103.         for(int i=0; i<a.length; i++){  
  104.             for(int j=0; j<a.length-i-1; j++){  
  105.                 if(a[j]>a[j+1]){  
  106.                     int tmp = a[j];  
  107.                     a[j] = a[j+1];  
  108.                     a[j+1] = tmp;  
  109.                 }  
  110.             }  
  111.         }  
  112.     }  
  113.       
  114.     /** 
  115.      * 改進的冒泡法<br> 
  116.      * 改進之處在於:設一個標誌位,如果某趟跑下來,沒有發生交換,說明已經排好了;<br> 
  117.      */  
  118.     public static void bubbleSortAdvanced(int[] a){  
  119.         int k = a.length-1;  
  120.         boolean flag = true;  
  121.         while(flag){  
  122.             flag = false;  
  123.             for(int i=0;i<k;i++){  
  124.                 if(a[i]>a[i+1]){  
  125.                     int tmp = a[i];  
  126.                     a[i] = a[i+1];  
  127.                     a[i+1] = tmp;  
  128.                     //有交換則繼續保持標誌位;  
  129.                     flag = true;  
  130.                 }  
  131.             }  
  132.             k--;  
  133.         }  
  134.     }  
  135.       
  136.     /** 
  137.      * 改進的冒泡法2<br> 
  138.      * 改進之處在於吸收上面的思想(沒有交換意味着已經有序),如果局部的已經是有序的,則後續的比較就不需要再比較他們了。<br> 
  139.      * 比如:3142 5678,假如剛剛做完了2和4交換之後,發現這趟比較後續再也沒有發生交換,則後續的比較只需要比到4即可;<br> 
  140.      * 該算法就是用一個標誌位記錄某趟最後發生比較的地點;<br> 
  141.      */  
  142.     public static void bubbleSortAdvanced2(int[] a){  
  143.         int flag = a.length - 1;  
  144.         int k;  
  145.         while(flag>0){  
  146.             k = flag;  
  147.             flag = 0;  
  148.             for(int i=0; i<k; i++){  
  149.                 if(a[i] > a[i+1]){  
  150.                     int tmp = a[i];  
  151.                     a[i] = a[i+1];  
  152.                     a[i+1] = tmp;  
  153.                     //有交換則記錄該趟最後發生比較的地點;  
  154.                     flag = i+1;  
  155.                 }  
  156.             }  
  157.         }  
  158.     }  
  159.       
  160.     /** 
  161.      * 插入排序 
  162.      *  
  163.      * 關於插入排序,這裏有幾個約定,從而可以快速理解算法:<br> 
  164.      * i: 無序表遍歷下標;i<n-1;<br> 
  165.      * j: 有序表遍歷下表;0<=j<i;<br> 
  166.      * a[i]:表示當前被拿出來做插入排序的無序表頭元素;<br> 
  167.      * a[j]:有序表中的任意元素;<br> 
  168.      * <br> 
  169.      * 算法關鍵點:把數組分割爲a[0~i-1]有序表,a[i~n-1]無序表;每次從無序表頭部取一個,<br> 
  170.      * 把它插入到有序表適當的位置,直到無序表爲空;<br> 
  171.      * 初始時,a[0]爲有序表,a[1~n-1]爲無序表;<br> 
  172.      *  
  173.      * 時間複雜度: 平均:O(n^2),最好:O(n);最壞:O(n^2); 
  174.      * 空間複雜度: O(1); 
  175.      */  
  176.     public static void insertSort(int[] a){  
  177.         //從無序表頭開始遍歷;  
  178.         for(int i=1; i<a.length; i++){  
  179.             int j;  
  180.             //拿a[i]和有序表元素依次比較,找到一個恰當的位置;  
  181.             for(j=i-1;j>=0; j--){  
  182.                 if(a[j] < a[i]){  
  183.                     break;  
  184.                 }  
  185.             }  
  186.             //如果找到恰當的位置,則從該位置開始,把元素朝後移動一格,爲插入的元素騰出空間;  
  187.             if(j!=(i-1)){  
  188.                 int tmp = a[i];  
  189.                 int k;  
  190.                 for(k = i-1; k>j;k--){  
  191.                     a[k+1] = a[k];  
  192.                 }  
  193.                 a[k+1] = tmp;  
  194.             }  
  195.         }  
  196.     }  
  197.       
  198.     /** 
  199.      * 改進的插入排序1 
  200.      * 改進的關鍵在於:首先拿無序表頭元素a[i]和有序表尾a[i-1]比較, 
  201.      * 如果a[i]<a[i-1],說明需要調整;調整的過程爲: 
  202.      * 從有序表尾開始,把有序表裏面比a[i]大的元素都朝後移動,直到找到恰當的位置; 
  203.      */  
  204.     public static void insertSortAdvanced(int[] a){  
  205.         //遍歷無序表;  
  206.         for(int i=1; i<a.length; i++){  
  207.             //如果無序表頭元素小於有序表尾,說明需要調整;  
  208.             if(a[i]<a[i-1]){  
  209.                 int tmp = a[i];  
  210.                 int j;  
  211.                 //從有序表尾朝前搜索並比較,並把大於a[i]的元素朝後移動以騰出空間;  
  212.                 for(j=i-1; j>=0&&a[j]>tmp;j--){  
  213.                     a[j+1] = a[j];  
  214.                 }  
  215.                 a[j+1] = tmp;  
  216.             }  
  217.         }  
  218.     }  
  219.       
  220.     /** 
  221.      * 改進的插入排序2 
  222.      * 總體思想和上面相似,拿無序表頭元素從有序表尾元素開始朝前比較, 
  223.      * 如果a[i]比a[i-1]小,則把a[i]從有序表尾用冒泡交換的方式朝前移動,直到到達恰當的位置; 
  224.      */  
  225.     public static void insertSortAdvanced2(int[] a){  
  226.         //遍歷無序表  
  227.         for(int i=1; i<a.length; i++){  
  228.             //拿a[i]從有序表尾開始冒泡;  
  229.             for(int j=i-1; j>=0 && a[j] > a[j+1]; j--){//a[j+1]就是a[i]  
  230.                 int tmp = a[j];  
  231.                 a[j] = a[j+1];  
  232.                 a[j+1] = tmp;  
  233.             }  
  234.         }  
  235.     }  
  236.   
  237.     /** 
  238.      * 快速排序<br> 
  239.      * 算法的思想在於分而治之:先找一個元素(一般來說都是數組頭元素),把比它大的都放到右邊,把比它小的都放到左邊;<br> 
  240.      * 然後再按照這樣的思想去處理兩個子數組; 下面說的子數組頭元素通指用來劃分數組的元素;<br> 
  241.      * <br> 
  242.      * 下面程序關鍵點就在於!forward, low0++, high0--這些運算; 這三個運算使得a[low0],a[high0]裏面總有一個指向子數組頭元素; <br>    
  243.      * 可以用極端的情況來方便理解這三個值的運作: <br> 
  244.      * 假如我的數列爲0123456789, 初始時forward=false,0作爲子數組劃分依據,很顯然第一輪的時候不會發生任何交換,low0一直指向0,<br> 
  245.      * high0逐漸下降直到它指向0爲止; 同理可思考9876543210這個例子;<br> 
  246.      * <br> 
  247.      * 時間複雜度: 平均:O(nlogn),最好:O(nlogn);最壞:O(n^2); 
  248.      * 空間複雜度: O(logn);要爲遞歸棧提供空間 
  249.      * @param a 待排序數組<br> 
  250.      * @param low 子數組開始的下標;<br> 
  251.      * @param high 子數組結束的下標;<br> 
  252.      */  
  253.     public static void quickSort(int[] a, int low, int high){  
  254.         if(low>=high){  
  255.             return;  
  256.         }  
  257.         int low0 = low;  
  258.         int high0 = high;  
  259.         boolean forward = false;  
  260.         while(low0!=high0){  
  261.             if(a[low0]>a[high0]){  
  262.                 int tmp = a[low0];  
  263.                 a[low0] = a[high0];  
  264.                 a[high0] = tmp;  
  265.                 forward = !forward;  
  266.             }  
  267.             if(forward){  
  268.                 low0++;  
  269.             }  
  270.             else{  
  271.                 high0--;  
  272.             }  
  273.         }  
  274.         low0--;  
  275.         high0++;  
  276.         quickSort(a, low, low0);  
  277.         quickSort(a, high0, high);  
  278.     }  
  279.       
  280.     /** 
  281.      * 快速排序的簡單調用形式<br> 
  282.      * 方便測試和調用<br> 
  283.      * @param a 
  284.      */  
  285.     public static void quickSort(int[] a){  
  286.         quickSort(a, 0, a.length-1);  
  287.     }  
  288.       
  289.     /** 
  290.      * 歸併排序<br> 
  291.      * 所謂歸併,就是合併兩個有序數組;歸併排序也用了分而治之的思想,把一個數組分爲若干個子數組;<br> 
  292.      * 當子數組的長度爲1的時候,則子數組是有序的,於是就可以兩兩歸併了;<br> 
  293.      * <br> 
  294.      * 由於歸併排序需要分配空間來轉儲歸併的結果,爲了算法上的方便,歸併算法的結果以返回值的形式出現;<br> 
  295.      */  
  296.       
  297.     /** 
  298.      * 合併兩個有序數組 
  299.      * @param a 有序數組1 
  300.      * @param b 有序數組2 
  301.      * @return 合併之後的有序數組; 
  302.      */  
  303.     public static int[] merge(int[] a, int[] b){  
  304.         int result[] = new int[a.length+b.length];  
  305.         int i=0,j=0,k=0;  
  306.         while(i<a.length&&j<b.length){  
  307.             if(a[i]<b[j]){  
  308.                 result[k++] = a[i];  
  309.                 i++;  
  310.             }  
  311.             else{  
  312.                 result[k++] = b[j];  
  313.                 j++;  
  314.             }  
  315.         }  
  316.         while(i<a.length){  
  317.             result[k++] = a[i++];  
  318.         }  
  319.         while(j<b.length){  
  320.             result[k++] = b[j++];  
  321.         }  
  322.         return result;  
  323.     }  
  324.       
  325.     /** 
  326.      * 歸併排序<br> 
  327.      * 把數組從中間一分爲二,並對左右兩部分遞歸調用,直到數組長度爲1的時候,開始兩兩歸併;<br> 
  328.      * 時間複雜度: 平均:O(nlogn),最好:O(nlogn);最壞:O(nlogn); 
  329.      * 空間複雜度: O(n);要爲歸併的結果分配空間 
  330.      * @param 待排序數組; 
  331.      * @return 有序數組; 
  332.      */  
  333.     public static int[] mergeSort(int[] a){  
  334.         if(a.length==1){  
  335.             return a;  
  336.         }  
  337.         int mid = a.length/2;  
  338.         int[] leftPart = new int[mid];  
  339.         int[] rightPart = new int[a.length-mid];  
  340.         System.arraycopy(a, 0, leftPart, 0, leftPart.length);  
  341.         System.arraycopy(a, mid, rightPart, 0, rightPart.length);  
  342.         leftPart = mergeSort(leftPart);  
  343.         rightPart = mergeSort(rightPart);  
  344.         return merge(leftPart, rightPart);  
  345.     }  
  346.       
  347.     /** 
  348.      * 選擇排序<br> 
  349.      * 和插入排序類似,它也把數組分割爲有序區和無序區,所不同的是:<br> 
  350.      * 插入排序是拿無序區的首元素插入到有序區適當的位置,而<br> 
  351.      * 選擇排序是從無序區中挑選最小的放到有序區最後;<br> 
  352.      * <br> 
  353.      * 兩層循環,外層控制有序區的隊尾,內層用來查找無序區最小元素;<br> 
  354.      *  
  355.      * 時間複雜度: 平均:O(n^2),最好:O(n);最壞:O(n^2); 
  356.      * 空間複雜度: O(1); 
  357.      * @param a 
  358.      */  
  359.     public static void selectSort(int[] a){  
  360.         for(int i=0; i<a.length; i++){  
  361.             int minIndex = i;  
  362.             for(int j=i+1; j<a.length; j++){  
  363.                 if(a[j]<a[minIndex]){  
  364.                     minIndex = j;  
  365.                 }  
  366.             }  
  367.             int tmp = a[i];  
  368.             a[i] = a[minIndex];  
  369.             a[minIndex]= tmp;  
  370.         }  
  371.     }  
  372.       
  373.     /** 
  374.      * 希爾排序<br> 
  375.      * 其思想是把數組按等步長(/間距)劃分爲多個子序列,對各個子序列做普通的插入排序,<br>逐次降低步長,直到爲1的時候最後再做一次普通的插入排序; 
  376.      * 用一個極端的例子作比方,我有數列如下:<br> 
  377.      * [1,2,3,4,5,6,7,8,9,10];<br> 
  378.      * 初始的時候,步長gap=5;則劃分的子數組爲[1,6], [2,7], [3,8], [4,9], [5,10];<br>對他們分別排序(當然由於本數組特殊,所以結果是不變的);<br> 
  379.      * 然後gap=2=5/2; 子數組爲[1,3,5,7,9], [2,4,6,8,10]; <br> 
  380.      * 最後gap=1=2/2; 做一次全局排序;<br> 
  381.      * <br> 
  382.      * 希爾排序克服了插入/冒泡排序的弱點(一次只能把元素移動一個相鄰的位置), <br>依靠大步長,可以把元素儘快移動到目標位置(或附近);<br> 
  383.      * 希爾排序實際上是插入排序的變種。它適用於:當數組總體有序,個別需要調整的情況;這時候利用插入排序的優勢,可以達到O(n)的效率;<br> 
  384.      * 影響希爾算法的一個重要的因素是步長選擇,一個好步長的優點是:後面的短步長排序不會破壞前面的長步長排序;<br> 
  385.      * 怎麼理解這種破壞呢?前面的長步長把一個較小的數移到了左面,但是在縮小步長之後有可能又被交換到了右面 (因爲它被分到了一個有很多比它更小的組);<br> 
  386.      * 關於步長,可以查看http://zh.wikipedia.org上面關於希爾排序的頁面;<br> 
  387.      * 下面的程序是希爾排序最基礎的寫法,適合用來理解希爾排序思想;<br> 
  388.      *  
  389.      * 時間複雜度: 受步長影響較大,n/2步長的平均複雜度爲n(logn)^2; 
  390.      */  
  391.     public static void shellSort(int[] a){  
  392.         // 控制間距;間距逐漸減小,直到爲1;  
  393.         for(int gap = a.length/2; gap>0; gap/=2){  
  394.             // 掃描每個子數組  
  395.             for(int i=0; i<gap; i++){  
  396.                 // 對每個字數組,掃描無序區;注意增量;  
  397.                 // a[i]是初始有序區;  
  398.                 for(int j=i+gap; j<a.length; j+=gap){  
  399.                     // 無序區首元素小於有序區尾元素,說明需要調整  
  400.                     if(a[j]<a[j-gap]){  
  401.                         int tmp = a[j];  
  402.                         int k = j-gap;  
  403.                         //從有序區尾向前搜索查找適當的位置;  
  404.                         while(k>=0&&a[k]>tmp){  
  405.                             a[k+gap] = a[k];  
  406.                             k-=gap;  
  407.                         }  
  408.                         a[k+gap] = tmp;  
  409.                     }  
  410.                 }  
  411.             }  
  412.         }  
  413.     }  
  414.       
  415.     /** 
  416.      * 改進的希爾排序<br> 
  417.      * 改進之處在於:上面的寫法用一個for循環來區別對待每個字數組;而實際上是不必要的;<br> 
  418.      * a[0,1,...gap-1]作爲所有子數組的有序區,a[gap,...n-1]作爲所有字數組的無序區;<br> 
  419.      * <br> 
  420.      * 該改進在時間效率上沒有改進;只是讓程序看起來更簡潔;<br> 
  421.      * @param a 
  422.      */  
  423.     public static void shellSortAdvanced(int[] a){  
  424.         // 控制步長  
  425.         for(int gap = a.length/2; gap>0; gap/=2){  
  426.             // 從無序區開始處理,把多個子數組放在一起處理;  
  427.             for(int j=gap; j<a.length; j++){  
  428.                 // 下面的邏輯和上面是一樣的;  
  429.                 if(a[j]<a[j-gap]){  
  430.                     int tmp = a[j];  
  431.                     int k = j-gap;  
  432.                     while(k>=0&&a[k]>tmp){  
  433.                         a[k+gap] = a[k];  
  434.                         k-=gap;  
  435.                     }  
  436.                     a[k+gap] = tmp;  
  437.                 }  
  438.             }  
  439.         }  
  440.     }  
  441.       
  442.     /** 
  443.      * 改進的希爾排序2<br> 
  444.      * 在吸收shellSortAdvanced思想的基礎上,採用insertAdvanced2的做法;<br>即無序區首元素通過朝前冒泡的形式移動的適當的位置;<br> 
  445.      * @param a 
  446.      */  
  447.     public static void shellSortAdvanced2(int[] a){  
  448.         for(int gap = a.length/2; gap>0; gap/=2){  
  449.             for(int i=gap; i<a.length; i++){  
  450.                 if(a[i]<a[i-gap]){  
  451.                     for(int j=i-gap; j>=0&&a[j+gap]>a[j]; j-=gap){  
  452.                         int tmp = a[j];  
  453.                         a[j] = a[j+gap];  
  454.                         a[j+gap] = tmp;  
  455.                     }  
  456.                 }  
  457.             }  
  458.         }  
  459.     }  
  460.       
  461.     /** 
  462.      * 堆排序<br> 
  463.      * 堆的定義:堆是一個完全,或近似完全的二叉樹,堆頂元素的值大於左右孩子的值,左右孩子也需要滿足這個條件;<br> 
  464.      * 按照堆的定義,堆可以是大頂堆(maxHeap),或小頂堆(minHeap);<br> 
  465.      * 一般用數組即可模擬二叉樹,對於任意元素i,左孩子爲2*i+1,右孩子爲2*i+2;父節點爲(i-1)/2; 
  466.      *  
  467.      * 時間複雜度: 平均:O(nlogn); 
  468.      * 空間複雜度: O(1); 
  469.      * @param a 
  470.      */  
  471.     public static void heapSort(int[] a){  
  472.   
  473.         // 先從最後一個非葉子節點往上調整,使滿足堆結構;  
  474.         for(int i=(a.length-2)/2; i>=0; i--){  
  475.             maxHeapAdjust(a, i, a.length);  
  476.         }  
  477.         // 每次拿最後一個節點和第一個交換,然後調整堆;直到堆頂;  
  478.         for(int i=a.length-1; i>0; i--){  
  479.             int tmp = a[i]; a[i] = a[0]; a[0] = tmp;  
  480.             maxHeapAdjust(a, 0, i);  
  481.         }  
  482.     }  
  483.       
  484.     /** 
  485.      * 調整堆<br> 
  486.      * 把以i爲跟節點的二叉樹調整爲堆;<br> 
  487.      * 可以這麼來思考這個過程:這個完全二叉樹就像一個金字塔,塔頂的小元素沿着樹結構,往下沉降;<br> 
  488.      * 調整的結果是最大的元素在金字塔頂,然後把它從堆中刪除(把它交換到堆尾,然後堆收縮一格);<br> 
  489.      * 堆排序快的原因就是根據二叉樹的特點,一個節點要沉降到合適的位置,只需要logn步;同時前期調整的結果(大小順序)會被記錄下來,從而加快後續的調整;<br> 
  490.      * @param a 待排數組 
  491.      * @param i 堆頂 
  492.      * @param len 堆長度 
  493.      */  
  494.     public static void maxHeapAdjust(int[] a, int i, int len){  
  495.         int tmp = a[i];  
  496.         // j是左孩子節點  
  497.         int j = i*2+1;  
  498.         //  
  499.         while(j<len){  
  500.             // 從左右孩子中挑選大的  
  501.             // j+1是右孩子節點  
  502.             if((j+1)<len && a[j+1]>a[j]){  
  503.                 j++;  
  504.             }  
  505.             // 找到恰當的位置就不再找  
  506.             if(a[j]<tmp){  
  507.                 break;  
  508.             }  
  509.             // 否則把較大者沿着樹往上移動;  
  510.             a[i] = a[j];  
  511.             // i指向剛纔的較大的孩子;  
  512.             i = j;  
  513.             // j指向新的左孩子節點;  
  514.             j = 2*i + 1;  
  515.         }  
  516.         // 把要調整的節點值下沉到適當的位置;  
  517.         a[i] = tmp;  
  518.     }  
  519.       
  520.     /** 
  521.      * 二叉樹排序<br> 
  522.      * 二叉樹的定義是嵌套的:<br>節點的值大於左葉子節點的值,小於右葉子節點的值;葉子節點同樣滿足這個要求;<br> 
  523.      * 二叉樹的構造過程就是排序的過程:<br> 
  524.      * 先構造跟節點,然後調用add方法添加後續節點爲跟節點的子孫節點;這個過程也是嵌套的;<br> 
  525.      * <br> 
  526.      * 中序遍歷二叉樹即得到有序結果;<br> 
  527.      * 二叉樹排序用法特殊,使用情形要視情況而定;<br> 
  528.      *  
  529.      * 時間複雜度: 平均:O(nlogn); 
  530.      * 空間複雜度: O(n); 
  531.      *  
  532.      * @param a 
  533.      */  
  534.     public static void binaryTreeSort(int[] a){  
  535.         // 構造一個二叉樹節點內部類來實現二叉樹排序算法;  
  536.         class BinaryNode{  
  537.             int value;  
  538.             BinaryNode left;  
  539.             BinaryNode right;  
  540.               
  541.             public BinaryNode(int value){  
  542.                 this.value = value;  
  543.                 this.left = null;  
  544.                 this.right = null;  
  545.             }  
  546.               
  547.             public void add(int value){  
  548.                 if(value>this.value){  
  549.                     if(this.right!=null){  
  550.                         this.right.add(value);  
  551.                     }  
  552.                     else{  
  553.                         this.right = new BinaryNode(value);  
  554.                     }  
  555.                 }  
  556.                 else{  
  557.                     if(this.left!=null){  
  558.                         this.left.add(value);  
  559.                     }  
  560.                     else{  
  561.                         this.left = new BinaryNode(value);  
  562.                     }  
  563.                 }  
  564.             }  
  565.             /** 
  566.              * 按中序遍歷二叉樹,就是有序的。 
  567.              */  
  568.             public void iterate(){  
  569.                 if(this.left!=null){  
  570.                     this.left.iterate();  
  571.                 }  
  572.                 // 在測試的時候要把輸出關掉,以免影響性能;  
  573.                 // System.out.print(value + ", ");  
  574.                 if(this.right!=null){  
  575.                     this.right.iterate();  
  576.                 }  
  577.             }  
  578.         }  
  579.           
  580.         BinaryNode root = new BinaryNode(a[0]);  
  581.         for(int i=1; i<a.length; i++){  
  582.             root.add(a[i]);  
  583.         }  
  584.         root.iterate();  
  585.     }  


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章