1. 程式人生 > >徹底搞懂堆排序

徹底搞懂堆排序

排序 數據結構 計算 成了 max 分享圖片 n+1 節點 繼續

一、準備知識

1.堆

堆(英語:heap)是計算機科學中一類特殊的數據結構的統稱。堆通常是一個可以被看做一棵樹的數組對象。堆總是滿足下列性質:

  • 堆中某個節點的值總是不大於或不小於其父節點的值;

  • 堆總是一棵完全二叉樹。

將根節點最大的堆叫做最大堆或大根堆,根節點最小的堆叫做最小堆或小根堆。常見的堆有二叉堆、斐波那契堆等。

堆是線性數據結構,相當於一維數組,有唯一後繼。

堆的定義如下:n個元素的序列{k1,k2,ki,…,kn}當且僅當滿足下關系時,稱之為堆。

(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4...n/2)

若將和此次序列對應的一維數組(即以一維數組作此序列的存儲結構)看成是一個完全二叉樹,則堆的含義表明,完全二叉樹中所有非終端結點的值均不大於(或不小於)其左、右孩子結點的值。由此,若序列{k1,k2,…,kn}是堆,則堆頂元素(或完全二叉樹的根)必為序列中n個元素的最小值(或最大值)。

2.最大堆

把根節點最大的堆叫最大堆。

二、堆排序的思想

堆排序是將一個數組通過數組下標關系虛構出一個最大堆,這樣這個最大堆的根節點是最大的,然後將這個堆的最後一個節點和根節點交換,這個最大值就到了數組的最後,然後數組的長度減一,減一後的數組在調整順序,使其再次成為一個最大堆,這是根節點就是第二大的元素,然後將重復上面的步驟,知道全部交換完畢,數組就排好了順序。

三、排序

1.將數組構造為一個虛擬的最大堆

通過下標來構造這個最大堆。

(1)數組下標和堆元素的對應關系。

技術分享圖片

通過上面的圖,我們可以分析出,一個節點,我們可以使用(n-1)/2來計算它的父節點的坐標、使用2*n+1來計算它的左節點的坐標、使用2*n+2來計算它的右節點的坐標。

(2)構造最大堆

遍歷整個數組,構造一個最大堆,每次插入一個數,然後使堆重新成為一個最大堆。構造過程如下:

  • 首先將1加入堆,這時堆中只有一個元素,數組現在為[1,2,3,4,5,6,7]。

  • 將2加入堆中,計算2的父節點(1-1)/2=0,2的父節點是數組下標為0的元素。這時候2成為了1的左節點,根節點小於子節點,不滿足最大堆的定義,所以調整這個堆,讓根節點最大,所以1,2交換位置,數組現在為[2,1,3,4,5,6,7]。

  • 將3加入堆中,計算3的父節點(2-1)/2=0,3的父節點是數組下標為0的元素。這時候3成為了2的右節點,發現根節點2小於3,調整堆,將根節點2和3交換位置,現在數組為[3,1,2,4,5,6,7]。

  • 重復上面的步驟,將每一個元素加入這個堆,加入一個節點後,對比加入的節點和它的父節點的大小,如果新加入的節點大於它的父節點,則將兩者交換,然後在比較交換後的節點和它的父節點的大小,知道使堆重新成為一個最大堆。

    構造過程代碼:

技術分享圖片

2. 通過堆的結構調整來排序。
通過上面的構造,我們已將一個數組通過下標關系構造成為了一個虛擬的最大堆,這時候我們知道這個最大堆的根節點(也就是數組的第一個元素)現在肯定是所有數字中最大的一個,然後根據這個已知關系調整這個堆,來達到排序的目的。

調整過程如下:

  • 將數組的第一個元素和數組的最後一個元素交換位置,這樣最大的那個數就到了數組的最後,然後數組長度減一,將最後一個數排除在外,剩余的數重新調整順序,重新成為一個最大堆,這樣根節點就變成了一個次大的元素。

  • 然後再次將根元素和現在的最後一個元素(原數組的倒數第二個元素)交換位置,數組長度在減一,然後重新調整堆使其再次成為一個最大堆。

  • 重復上面的步驟,直到所有的數字都調整完畢,這時數組就排好了順序。

數組調整過程:

  • 找出當前節點和它的左節點、右節點三者中最大的那個節點,如果最大的節點是它自己則不做任何調整,如果最大的節點是它的左節點或者右節點,則和該節點交換位置,然後將交換後的節點作為當前節點在重復判斷。

  • 重復上面的步驟,使其重新成為一個最大堆。

調整過程代碼如下:

技術分享圖片

四、完整代碼

            public class HeapSort {
                 public void heapSort(int[] arr) {
                     for (int i = 0; i < arr.length; i++) {
                         heapInsert(arr, i);
                     }
                     int heapSize = arr.length;
                     while (heapSize > 1) {
                         heapify(arr, --heapSize);
                     }
                 }

                 private void heapInsert(int[] arr,int i) {
                   int last = (i - 1) / 2; //計算父節點
                   while (arr[i] > arr[last]) { // 比較當前節點和父節點
                       //調整堆
                      swap(arr,i,last);
                           //繼續判斷他的上一層節點是否滿足最大堆
                       i = last;
                       last = (i - 1) / 2;
                   }
               }

             public void heapify(int[] arr, int heapSize) {
               swap(arr, 0, heapSize--);
               int cur = 0;
               while (2 * cur + 1 <= heapSize) {
                   int left = 2 * cur + 1;
                   int right = 2 * cur + 2;
                   int lastMax = heapSize >= right && arr[left] > arr[right] ?
                           arr[left] > arr[cur] ? left : cur
                           : heapSize >= right ?
                           arr[right] > arr[cur] ? right : cur :
                           arr[left] > arr[cur]
                           ? left : cur;
                   if (lastMax == cur) {
                       break;
                   }
                   int temp = arr[cur];
                   arr[cur] = arr[lastMax];
                   arr[lastMax] = temp;
                   cur = lastMax;
               }
             }

             public void swap(int[] arr, int i, int j) {
               int temp = arr[i];
               arr[i] = arr[j];
               arr[j] = temp;
             }

             @Test
             public void test() {
                 int[] arr = {1,2,3,4,5,6,7};
                 int[] arr1 = Arrays.copyOf(arr, arr.length);
                 heapSort(arr);
                 Arrays.sort(arr1);
                 Assert.assertArrayEquals(arr, arr1);
               }
              }  

五、復雜度。

時間復雜度:O(nlogn)

空間復雜度:O(1)

徹底搞懂堆排序