java實現算法之堆排序

kinghowe 8年前發布 | 20K 次閱讀 算法 Java開發 Java

堆排序快速排序歸并排序一樣都是時間復雜度為O(N*logN)的幾種常見排序方法。學習堆排序前,先講解下什么是數據結構中的二叉堆。

 

二叉堆的定義

 

二叉堆是完全二叉樹或者是近似完全二叉樹。

 

二叉堆滿足二個特性:

1.父結點的鍵值總是大于或等于(小于或等于)任何一個子節點的鍵值。

2.每個結點的左子樹和右子樹都是一個二叉堆(都是最大堆或最小堆)。

當父結點的鍵值總是大于或等于任何一個子節點的鍵值時為最大堆。當父結點的鍵值總是小于或等于任何一個子節點的鍵值時為最小堆。下圖展示一個最小堆:

由于其它幾種堆(二項式堆,斐波納契堆等)用的較少,一般將二叉堆就簡稱為堆。

 

堆的存儲

 

一般都用數組來表示堆,i結點的父結點下標就為(i – 1) / 2。它的左右子結點下標分別為2 * i + 1和2 * i + 2。如第0個結點左右子結點下標分別為1和2。

堆的操作——插入刪除

下面先給出《數據結構C++語言描述》中最小堆的建立插入刪除的圖解,再給出本人的實現代碼,最好是先看明白圖后再去看代碼。

 

 

 

堆的插入

每次插入都是將新數據放在數組最后。可以發現從這個新數據的父結點到根結點必然為一個有序的數列,現在的任務是將這個新數據插入到這個有序數據中——這就類似于 直接插入排序 中將一個數據并入到有序區間中, 代碼:

/**
     * 新加入i結點  其父結點為(i - 1) / 2  
     * 新插入的節點都是位於數組末尾,從該節點開始檢查,類似“上浮”的過程
     * @param a
     * @param i
     */
    static void minHeapFixup (int[] a, int i) {
        int j, temp;
        temp = a[i];
        j = (i - 1) / 2;//父節點
        while (j > 0 && i != 0) {
            if (a[j] <= temp) {
                break;
            }
            a[i] = a[j]; //把較大的子結點往下移動,替換它的子結點  
            i = j;
            j = (i - 1) / 2;
        }
        a[i] = temp;
    }


插入時:

 

 

//在最小堆中插入元素時
    static void minHeapAddNumber(int[] a, int n, int value) {
        a[n] = value;
        minHeapFixup(a, n);
    }

 

 

 

堆的刪除

按定義,堆中每次都只能刪除第0個數據。為了便于重建堆,實際的操作是將最后一個數據的值賦給根結點,然后再從根結點開始進行一次從上向下的調整。調整時先在左右兒子結點中找最小的,如果父結點比這個最小的子結點還小說明不需要調整了,反之將父結點和它交換后再考慮后面的結點。相當于從根結點將一個數據的“下沉”過程。下面給出代碼:

 

 

// 從i節點開始調整,n為節點總數 從0開始計算 。i節點的子節點為 2*i+1, 2*i+2。刪除的調整過程有點類似“下沉”過程
    static void minHeapFixdown(int[] a, int i, int n) {
        int temp = a[i];
        int j = 2 * i + 1;//左兒子
        while (j < n) {
            if (j + 1 < n && a[j] > a[j + 1]) {//選出兩個孩子中較小的孩子
                j++;
            }
            if (a[j] >= temp) {
                break;
            }
            a[i] = a[j]; //把較小的子結點往上移動,替換它的父結點  
            i = j;
            j = 2 * i + 1;
        }
        a[i] = temp;
    }

    /**
     * 刪除小頂堆,是刪除第一個元素,用最后的元素填充到第一個元素。
     * 其實,也就是第一個與最后一個元素交換,然后長度縮短一個就可以實現。
     * 小頂堆采取這種方式排序出來,最后得到的是從大到小的逆序。因為每次都將最小的交換到最后。
     * @param a
     * @param first
     * @param n
     */
    static void minHeapDeleteNumber(int a[], int n) {  
        int temp = a[0];
        a[0] = a[n - 1];
        a[n - 1] = temp;
        minHeapFixdown(a, 0, n - 1);  
    }

 

堆化數組

 

有了堆的插入和刪除后,再考慮下如何對一個數據進行堆化操作。要一個一個的從數組中取出數據來建立堆吧,不用!先看一個數組,如下圖:

很明顯,對葉子結點來說,可以認為它已經是一個合法的堆了即20,60, 65, 4, 49都分別是一個合法的堆。只要從A[4]=50開始向下調整就可以了。然后再取A[3]=30,A[2] = 17,A[1] = 12,A[0] = 9分別作一次向下調整操作就可以了。下圖展示了這些步驟:

寫出堆化數組的代碼:

/**
     * 數組元素來生成小頂堆,從第n/2 - 1個元素開始,因為葉子元素已是合法的堆了
     * @param a
     * @param n
     */
    static void makeMinHeap(int[] a, int n) {
        for (int j = n / 2 - 1; j >= 0 ; j--) {
            minHeapFixdown(a, j, n);
        }
    }

 

至此,堆的操作就全部完成了(注1),再來看下如何用堆這種數據結構來進行排序。

 

 

堆排序

 

首先可以看到堆建好之后堆中第0個數據是堆中最小的數據。取出這個數據再執行下堆的刪除操作。這樣堆中第0個數據又是堆中最小的數據,重復上述步驟直至堆中只有一個數據時就直接取出這個數據。

由于堆也是用數組模擬的,故堆化數組后,第一次將A[0]與A[n - 1]交換,再對A[0…n-2]重新恢復堆。第二次將A[0]與A[n – 2]交換,再對A[0…n - 3]重新恢復堆,重復這樣的操作直到A[0]與A[1]交換。由于每次都是將最小的數據并入到后面的有序區間,故操作完成后整個數組就有序了。有點類似于直接選擇排序

 

//堆排序,小頂堆,排序形成的是逆序的數組,過程就是交換第一個和最后一個元素,每次數組長度減少1
    static void minheapSortToDescendArray(int[] a, int n) {
        int temp;
        for (int j = n - 1; j >= 0; j--) {
            //交換
            temp = a[0];
            a[0] = a[j];
            a[j] = temp;
            //調整
            minHeapFixdown(a, 0, j);
        }
    }

 

 

 

注意使用最小堆排序后是遞減數組,要得到遞增數組,可以使用最大堆。

由于每次重新恢復堆的時間復雜度為O(logN),共N - 1次重新恢復堆操作,再加上前面建立堆時N / 2次向下調整,每次調整時間復雜度也為O(logN)。二次操作時間相加還是O(N * logN)。故堆排序的時間復雜度為O(N * logN)。

完整的實現代碼如下:

 

package Algorithm.ylh.com;

import java.util.Arrays;

public class HeapSort {

    public static void main(String[] args) {
        int[] a = GenerateIntArray.getArray();
        System.out.println(Arrays.toString(a));
        makeMinHeap(a, a.length);//建堆
        System.out.println(Arrays.toString(a));
        minHeapDeleteNumber(a, a.length);//刪除一個元素
        System.out.println(Arrays.toString(a));
        minHeapAddNumber(a, a.length - 1, 20);//添加一個元素
        System.out.println(Arrays.toString(a));
        minheapSortToDescendArray(a, a.length);//排序
        System.out.println(Arrays.toString(a));
    }


    /**
     * 新加入i結點  其父結點為(i - 1) / 2  
     * 新插入的節點都是位於數組末尾,從該節點開始檢查,類似“上浮”的過程
     * @param a
     * @param i
     */
    static void minHeapFixup (int[] a, int i) {
        int j, temp;
        temp = a[i];
        j = (i - 1) / 2;//父節點
        while (j > 0 && i != 0) {
            if (a[j] <= temp) {
                break;
            }
            a[i] = a[j]; //把較大的子結點往下移動,替換它的子結點  
            i = j;
            j = (i - 1) / 2;
        }
        a[i] = temp;
    }

    //在最小堆中插入元素時
    static void minHeapAddNumber(int[] a, int n, int value) {
        a[n] = value;
        minHeapFixup(a, n);
    }

    // 從i節點開始調整,n為節點總數 從0開始計算 。i節點的子節點為 2*i+1, 2*i+2。刪除的調整過程有點類似“下沉”過程
    static void minHeapFixdown(int[] a, int i, int n) {
        int temp = a[i];
        int j = 2 * i + 1;//左兒子
        while (j < n) {
            if (j + 1 < n && a[j] > a[j + 1]) {//選出兩個孩子中較小的孩子
                j++;
            }
            if (a[j] >= temp) {
                break;
            }
            a[i] = a[j]; //把較小的子結點往上移動,替換它的父結點  
            i = j;
            j = 2 * i + 1;
        }
        a[i] = temp;
    }

    /**
     * 刪除小頂堆,是刪除第一個元素,用最后的元素填充到第一個元素。
     * 其實,也就是第一個與最后一個元素交換,然后長度縮短一個就可以實現。
     * 小頂堆采取這種方式排序出來,最后得到的是從大到小的逆序。因為每次都將最小的交換到最后。
     * @param a
     * @param first
     * @param n
     */
    static void minHeapDeleteNumber(int a[], int n) {  
        int temp = a[0];
        a[0] = a[n - 1];
        a[n - 1] = temp;
        minHeapFixdown(a, 0, n - 1);  
    } 

    /**
     * 數組元素來生成小頂堆,從第n/2 - 1個元素開始,因為葉子元素已是合法的堆了
     * @param a
     * @param n
     */
    static void makeMinHeap(int[] a, int n) {
        for (int j = n / 2 - 1; j >= 0 ; j--) {
            minHeapFixdown(a, j, n);
        }
    }

    //堆排序,小頂堆,排序形成的是逆序的數組,過程就是交換第一個和最后一個元素,每次數組長度減少1
    static void minheapSortToDescendArray(int[] a, int n) {
        int temp;
        for (int j = n - 1; j >= 0; j--) {
            //交換
            temp = a[0];
            a[0] = a[j];
            a[j] = temp;
            //調整
            minHeapFixdown(a, 0, j);
        }
    }
}

來源: http://blog.csdn.net/rebirth_love/article/details/51347109http://blog.csdn.net/rebirth_love/article/details/51347109 

 本文由用戶 kinghowe 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!