1. 程式人生 > >堆排序的JAVA實現及時間複雜度分析

堆排序的JAVA實現及時間複雜度分析

堆排序是一個比較常用的排序方式,下面是其JAVA的實現:

1. 建堆

// 對輸入的陣列進行建堆的操作
    private static void buildMaxHeap(int[] array, int length) {
        // 遍歷所有的內部節點,每一個都進行siftdown操作
        for (int i = (int)Math.floor(length / 2 - 1); i >= 0; i--) {
            siftdown(array, length, i);
        }
    }
	
	private static void siftdown(int[] array, int length, int index) {
        // 1. 判斷是否為葉子節點
        if (isLeaf(array, length, index)) {
            return;
        }

        // 2. 計算左右兩個值的大小
        int max = Integer.MIN_VALUE;
        int maxIndex = index;
        // 判斷該內部節點是否有兩個子樹
        if (2 * (index + 1) > length - 1) {
            max = array[2 * index + 1];
            maxIndex = 2 * index + 1;
        } else {
            // 對兩個子樹的值進行比較
            if (array[2 * index + 1] > array[2 * (index + 1)]) {
                max = array[2 * index + 1];
                maxIndex = 2 * index + 1;
            } else {
                max = array[2 * (index + 1)];
                maxIndex = 2 * (index + 1);
            }
        }

        // 3. 如需交換,交換後繼續向下遞迴
        if (array[index] < array[maxIndex]) {
            array[maxIndex] = array[index];
            array[index] = max;
            siftdown(array, length, maxIndex);
        }
    }

    // 判斷是否為葉子節點
    private static boolean isLeaf(int[] array, int length, int index) {
        return index > (int)Math.floor(length / 2 - 1);
    }

2. 堆排序

    private static void heapSort(int[] array) {
        buildMaxHeap(array, array.length);
        // 將堆頂的最大值每次都交換到最後,對剩餘的數字進行新的建堆操作
        for (int i = 0; i < array.length; i++) {
            int temp = array[array.length - i - 1];
            array[array.length - i - 1] = array[0];
            array[0] = temp;
            buildMaxHeap(array, array.length - i - 1);
        }
    }

3. 時間複雜度

堆排序的時間複雜度主要有兩個部分:

  • 初始化建堆
  • 每次取堆頂元素後,剩餘部分的排序

因此,我們來分別看一下這兩個部分的時間複雜度

  • 對於初始化建堆:我們需要對除了葉子節點這一層的其他節點都進行遍歷,每一個點假設都遞迴到了葉子節點,需要 k - i 次siftdown操作,而每一層有 2^(i - 1) 個節點,(i代表層數,設根節點為第一層)即,我們需要計算 2^(i - 1) * (k - i) ,當 i 取值為 1 到 n - 1時的值的和。 即求: i=1n1(ni)2i1\sum_{i=1}^{n-1} (n-i)2^{i-1} 計算過程如下: i=1n1(ni)2i1=i=1n1n2i1i=1n1i2i1\sum_{i=1}^{n-1} (n-i)2^{i-1} = \sum_{i=1}^{n-1} n*2^{i-1} - \sum_{i=1}^{n-1} i*2^{i-1} 對於前半部分: i=1n1n2i1=ni=1n12i1=n1(12n1)12=n2n1n\sum_{i=1}^{n-1} n*2^{i-1} = n*\sum_{i=1}^{n-1} 2^{i-1} = n * \frac{1*(1-2^{n-1})}{1-2}=n*2^{n-1} - n 對於後半部分: Sn=i=1n1i2i1=20+221+322+...+(n1)2n2S_n = \sum_{i=1}^{n-1} i*2^{i-1}=2^0+2*2^1+3*2^2+...+(n-1)*2^{n-2} 2Sn=2i=1n1i2i1=21+222+323+...+(n1)2n12* S_n = 2*\sum_{i=1}^{n-1} i*2^{i-1}=2^1+2*2^2+3*2^3+...+(n-1)*2^{n-1} Sn2Sn=Sn=20+21+22+...+2n2(n1)2n1=2n1n2n1S_n - 2* S_n =-S_n=2^0+2^1+2^2+...+2^{n-2}-(n-1)*2^{n-1}=2^n-1-n*2^{n-1} 即,兩部分相加,有: n2n1n+2n1n2n1=2n1nn*2^{n-1} - n+2^n-1-n*2^{n-1}=2^n-1-n 由於,n代表的是層數,它可以由 n = log N 求得,代入,有:N - 1 - log N,故時間複雜度 O(N) = N
  • 第二部分,是關於排序的,我們交換堆頂元素不斷放置到堆的末尾。即堆的元素是越來越少,我們比較的次數也減少,即求: log(n1)+log(n2)+log3+log2log(n!)log(n-1)+log(n-2)…+log3+log2≈log(n!) 可以證明log(n!)和nlog(n)是同階函式:   (n/2)n/2n!nnn/2log(n/2)log(n!)nlog(n)∵(n/2)^{n/2}≤n!≤n^{n} ∴n/2\log(n/2) \leq \log(n!) \leq n\log(n)   即可以知道第二部分的時間複雜度為O(nlogn)

將兩部分結合起來,我們可以獲得時間複雜度為O(n) = O(n + nlogn) = O(nlogn)