常用的排序算法實現(2)-堆排序

堆排序相對來說比較複雜。這裏使用了兩種是實現方案,方案1,heapSort1()需要藉助於二叉堆類,這個算是一個入門;有助於理解堆排序。
方案2,heapSort2()將數組看成是一棵二叉樹,首先要對樹轉換,使其變成大頂堆。然後再進行排序。
這裏需要注意
1.數組索引從0開始。所以計算元素索引的時候要轉換,而且要注意越界問題。
2.大頂堆和堆排序都只用了下沉操作;
2.1在構建大頂堆的時候,要從最後一棵子樹開始構建子二叉堆。
2.2在構建二叉堆的時候,如果樹的高度大於1的時候,要比較樹的子節點,以便讓元素放在合適的位置上。
假設高度爲h的子樹均已完成二叉堆化,那麼對於高度爲h+1的子樹,把其根節點沿着最大子節點的分枝做調整,最多需要h步完成二叉堆化。

還是親自寫一下,然後在本子上自己畫一下,會比較深刻。否則很難很好的理解這個過程。


/**
 * @auther 王輝
 * @create 2020-04-15 20:47
 * @Description
 */

import java.lang.reflect.Array;
import java.util.Arrays;

/**
 * 描述:
 * 堆排序
 * 首先構建一個二叉堆。
 * 二叉堆是完全二元樹(二叉樹)或者是近似完全二元樹(二叉樹)。
 * <p>
 * 二叉樹的一些性質:
 * 位置爲K的節點的父節點爲K/2,而它的子節點的位置爲2k(左節點)和2k+1(右節點)
 * <p>
 * 二叉堆有兩種:最大堆和最小堆。
 * 最大堆:父結點的鍵值總是大於或等於任何一個子節點的鍵值;----升序排序的時候用大頂堆
 * 最小堆:父結點的鍵值總是小於或等於任何一個子節點的鍵值。----降序排序的時候用小頂堆
 * <p>
 * 此處實現的是大頂堆
 *
 * @author wanghui email:[email protected]
 * @create 2020-04-15 上午10:54
 */
public class MaxPQ<Key extends Comparable<Key>> {
    /**
     * 通過數組實現二叉樹
     */
    private Key[] pq;
    /**
     * 存儲在pq中的元素個數。0沒有使用。
     */
    private int N = 0;

    public MaxPQ(int maxN) {
        this.pq = (Key[]) new Comparable[maxN + 1];
    }

    /**
     * 判斷二叉樹是否爲空
     *
     * @return
     */
    public boolean isEmpty() {
        return N == 0;
    }

    /**
     * 後去二叉樹大小
     *
     * @return
     */
    public int size() {
        return N;
    }

    /**
     * 插入二叉樹,從尾部插入,需要根據插入元素跟父節點元素的大小,做上浮操作。
     * 當v大於父節點時,上浮
     * 插入元素比較次數不超過lgN+1
     *
     * @param v
     */
    public void insert(Key v) {
        pq[++N] = v;
        swim(N);

    }

    /**
     * 獲取並移除最大元素
     * 移除最大元素後,將二叉樹最後一個節點的元素設置爲樹的根節點;此時,樹的根節點小於其子節點,所以要做下沉操作
     * 刪除最大元素操作不超過2lgN次比較
     *
     * @return
     */
    public Key delMax() {
        if (N == 1) {
            Key max = pq[1];
            pq[N--] = null;
            return max;
        } else if (N > 1) {
            Key max = pq[1];
            exch(1, N--);
            pq[N + 1] = null;
            sink(1);
            return max;
        } else {
            return null;
        }

    }


    /**
     * 元素大小比較
     *
     * @param i
     * @param j
     * @return
     */
    private boolean less(int i, int j) {
        return pq[i].compareTo(pq[j]) < 0;
    }

    /**
     * 元素位置交換
     *
     * @param i
     * @param j
     */
    private void exch(int i, int j) {
        Key t = pq[i];
        pq[i] = pq[j];
        pq[j] = t;
    }

    /**
     * 上浮操作,自下而上的調整元素順序,使其符合二叉堆的條件
     *
     * @param k
     */
    private void swim(int k) {
        while (k > 1 && less(k / 2, k)) {
            exch(k / 2, k);
            k /= 2;
        }
    }

    /**
     * 下沉操作,自上而下的調整元素順序,使其符合二叉堆的條件
     *
     * @param k
     */
    private void sink(int k) {
        while (2 * k <= N) {
            int j = 2 * k;
            if (j < N && less(j, j + 1)) {
                j++;
            }
            if (!less(k, j)) {
                break;
            }
            exch(k, j);
            k = j;
        }
    }


    /**
     * 堆排序實現1
     * 1、調用insert(),將數組變成二叉堆(堆有序操作)
     * 2、下沉排序
     *
     * @param a
     */
    public void heapSort1(Comparable[] a) {
        int temp = a.length;
        MaxPQ aa = new MaxPQ(temp);
        for (int i = 0; i < temp; i++) {
            aa.insert(a[i]);
        }
        System.out.println("堆有序:" + Arrays.toString(aa.pq));
        for (int j = temp - 1; j >= 0; j--) {
            a[j] = aa.delMax();
        }
        System.out.println("排序後:" + Arrays.toString(a));
    }

    /**
     * 堆排序實現2
     * 1、構建二叉堆,從右至左的通過下沉操作構建二叉堆
     *
     * @param a
     */
    public void heapSort2(Comparable[] a) {

        int n = a.length;
        //step1、子二叉堆下沉操作實現大頂堆
        for (int k = n / 2; k >= 1; k--) {
            sink(a, k, n);
        }
        System.out.println("堆有序:" + Arrays.toString(a));
        //step2、堆排序
        while (n > 1) {
            exch(a, 1, n--);
            sink(a, 1, n);
        }
        System.out.println("排序後:" + Arrays.toString(a));
    }


    /**
     * 子二叉堆下沉操作,交換時,要跟子節點中小的一方交換
     * 選出的子二叉樹在變換成子二叉堆的時候,
     * 子節點大的跟父節點比較,如果大於父節點,則替換
     * @param a 數組a內部實現下沉
     * @param i 下沉開始節點
     * @param j 下沉結束節點
     */
    private void sink(Comparable[] a, int i, int j) {

        int v = i - 1;
        Comparable rootNode = a[v];
        for (int k = 2 * v + 1; k < j; k = 2 * k + 1) {
            //右子節點大於左子節點
            if(k+1 < j && a[k].compareTo(a[k+1])<0) {
                k++;
            }
            //父節點小於子節點中最大的
            if(rootNode.compareTo(a[k]) < 0) {
                a[v] = a[k];
                //將索引指向下一個子二叉樹的父節點
                v = k;
            }else {
                break;
            }
        }
        a[v] = rootNode;
    }

    /**
     * 元素交換
     *
     * @param a 數組a內部實現元素交換
     * @param i 需要交換元素1
     * @param j 需要交換元素1
     */
    private void exch(Comparable[] a, int i, int j) {
        Comparable temp = a[i - 1];
        a[i - 1] = a[j - 1];
        a[j - 1] = temp;
    }


    public static void main(String[] args) {
        String[] a = {"S", "O", "R", "T", "E", "X", "A", "M", "P", "L", "E"};
        System.out.println("排序前:" + Arrays.toString(a));
        int temp = a.length;
        new MaxPQ(temp).heapSort2(a);


    }
}

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