1. 程式人生 > 實用技巧 >js多個基本型別計算

js多個基本型別計算

https://blog.csdn.net/qq_28063811/article/details/93034625

1、首先了解堆是什麼

堆是一種資料結構,一種叫做完全二叉樹的資料結構。

2、堆的性質

這裡我們用到兩種堆,其實也算是一種。

大頂堆:每個節點的值都大於或者等於它的左右子節點的值。

小頂堆:每個節點的值都小於或者等於它的左右子節點的值。

小頂堆圖片是錯誤的.

如上所示,就是兩種堆。

如果我們把這種邏輯結構對映到陣列中,就是下邊這樣

這個陣列 arr 邏輯上就是一個堆。

從這裡我們可以得出以下性質(重點)

對於大頂堆:arr[i] >= arr[2i + 1] && arr[i] >= arr[2i + 2]

對於小頂堆:arr[i] <= arr[2i + 1] && arr[i] <= arr[2i + 2]

3、堆排序的基本思想

瞭解了以上內容,我們可以開始探究堆排序的基本思想了。

堆排序的基本思想是:1、將帶排序的序列構造成一個大頂堆,根據大頂堆的性質,當前堆的根節點(堆頂)就是序列中最大的元素;2、將堆頂元素和最後一個元素交換,然後將剩下的節點重新構造成一個大頂堆;3、重複步驟 2,如此反覆,從第一次構建大頂堆開始,每一次構建,我們都能獲得一個序列的最大值,然後把它放到大頂堆的尾部。最後,就得到一個有序的序列了。

假設給定的無序序列 arr 是:

1、將無序序列構建成一個大頂堆。

首先我們將現在的無序序列看成一個堆結構,一個沒有規則的二叉樹,將序列裡的值按照從上往下,從左到右依次填充到二叉樹中。

根據大頂堆的性質,每個節點的值都大於或者等於它的左右子節點的值。所以我們需要找到所有包含子節點的節點,也就是非葉子節點,然後調整他們的父子關係,非葉子節點遍歷的順序應該是從下往上,這比從上往下的順序遍歷次數少很多,因為,大頂堆的性質要求父節點的值要大於或者等於子節點的值,如果從上往下遍歷,當某個節點即是父節點又是子節點並且它的子節點仍然有子節點的時候,因為子節點還沒有遍歷到,所以子節點不符合大頂堆性質,當子節點調整後,必然會影響其父節點需要二次調整。但是從下往上的方式不需要考慮父節點,因為當前節點調整完之後,當前節點必然比它的所有子節點都大,所以,只會影響到子節點二次調整。相比之下,從下往上的遍歷方式比從上往下的方式少了父節點的二次調整。

那麼,該如何知道最後一個非葉子節點的位置,也就是索引值?

對於一個完全二叉樹,在填滿的情況下(非葉子節點都有兩個子節點),每一層的元素個數是上一層的二倍,根節點數量是 1,所以最後一層的節點數量,一定是之前所有層節點總數 + 1,所以,我們能找到最後一層的第一個節點的索引,即節點總數 / 2(根節點索引為 0),這也就是第一個葉子節點,所以第一個非葉子節點的索引就是第一個葉子結點的索引 - 1。那麼對於填不滿的二叉樹呢?這個計算方式仍然適用,當我們從上往下,從左往右填充二叉樹的過程中,第一個葉子節點,一定是序列長度 / 2,所以第一個非葉子節點的索引就是 arr.length / 2 -1。

現在找到了最後一個非葉子節點,即元素值為 2 的節點,比較它的左右節點的值,是否比他大,如果大就換位置。這裡因為 1<2,所以,不需要任何操作,繼續比較下一個,即元素值為 8 的節點,它的左節點值為 9 比它本身大,所以需要交換

交換後的序列為:

因為元素 8 沒有子節點,所以繼續比較下一個非葉子節點,元素值為 5 的節點,它的兩個子節點值都比本身小,不需要調整;然後是元素值為 4 的節點,也就是根節點,因為 9>4,所以需要調整位置

交換後的序列為:

9 5 4 2 3 8 7 1

此時,原來元素值為 9 的節點值變成 4 了,而且它本身有兩個子節點,所以,這時需要再次調整該節點

交換後的序列為:

到此,大頂堆就構建完畢了。滿足大頂堆的性質。

2、排序序列,將堆頂的元素值和尾部的元素交換

交換後的序列為:

然後將剩餘的元素重新構建大頂堆,其實就是調整根節點以及其調整後影響的子節點,因為其他節點之前已經滿足大頂堆性質。

交換後的序列為:

然後,繼續交換,堆頂節點元素值為 8 與當前尾部節點元素值為 1 的進行交換

交換後的序列為:

重新構建大頂堆

交換後的序列為:

繼續交換

交換後的序列為:

4、堆排序的程式碼實現(java 版本)

public class HeapSort {
 
    public static void heapSort(int[] arr) {
        if (arr == null || arr.length == 0) {
            return;
        }
        int len = arr.length;
        // 構建大頂堆,這裡其實就是把待排序序列,變成一個大頂堆結構的陣列
        buildMaxHeap(arr, len);
 
        // 交換堆頂和當前末尾的節點,重置大頂堆
        for (int i = len - 1; i > 0; i--) {
            swap(arr, 0, i);
            len--;
            heapify(arr, 0, len);
        }
    }
 
    private static void buildMaxHeap(int[] arr, int len) {
        // 從最後一個非葉節點開始向前遍歷,調整節點性質,使之成為大頂堆
        for (int i = (int)Math.floor(len / 2) - 1; i >= 0; i--) {
            heapify(arr, i, len);
        }
    }
 
    private static void heapify(int[] arr, int i, int len) {
        // 先根據堆性質,找出它左右節點的索引
        int left = 2 * i + 1;
        int right = 2 * i + 2;
        // 預設當前節點(父節點)是最大值。
        int largestIndex = i;
        if (left < len && arr[left] > arr[largestIndex]) {
            // 如果有左節點,並且左節點的值更大,更新最大值的索引
            largestIndex = left;
        }
        if (right < len && arr[right] > arr[largestIndex]) {
            // 如果有右節點,並且右節點的值更大,更新最大值的索引
            largestIndex = right;
        }
 
        if (largestIndex != i) {
            // 如果最大值不是當前非葉子節點的值,那麼就把當前節點和最大值的子節點值互換
            swap(arr, i, largestIndex);
            // 因為互換之後,子節點的值變了,如果該子節點也有自己的子節點,仍需要再次調整。
            heapify(arr, largestIndex, len);
        }
    }
 
    private static void swap (int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

5、複雜度分析

因為堆排序無關乎初始序列是否已經排序已經排序的狀態,始終有兩部分過程,構建初始的大頂堆的過程時間複雜度為 O(n),交換及重建大頂堆的過程中,需要交換 n-1 次,重建大頂堆的過程根據完全二叉樹的性質,[log2(n-1),log2(n-2)...1] 逐步遞減,近似為 nlogn。所以它最好和最壞的情況時間複雜度都是 O(nlogn),空間複雜度 O(1)。