1. 程式人生 > 其它 >堆排序演算法Java實現

堆排序演算法Java實現

介紹堆排序的基本概念及其實現。

摘要 介紹堆排序的基本概念及其實現。

前言

  排序大的分類可以分為兩種:內排序和外排序。在排序過程中,全部記錄存放在記憶體,則稱為內排序,如果排序過程中需要使用外存,則稱為外排序。這裡講的排序是內排序中的堆排序演算法,它屬於選擇排序的一種。

  堆排序和插入排序一樣,是一種就地排序演算法(不需要額外的儲存空間)。堆是一種資料結構,它可以被視為一種完全二叉樹。最小堆又叫小頂堆,滿足小頂堆的條件是每個孩子節點的值都大於父節點。大頂堆則相反。

原始碼

import java.util.Arrays;

public class HeapSort {
    //使用陣列儲存堆中的資料
    private int[] data;

    public static void main(String[] args) {
        int[] a = {1, 50, 38, 78, 33, 12, 65, 97, 76, 13, 27, 32, 50, 63, 101};

        int lastIndex = a.length - 1;
        //迴圈建堆
        for (int i = 0; i < lastIndex; i++) {
            //建堆
            buildMaxHeap(a, lastIndex - i);
            //交換堆頂和最後一個元素
            swap(a, 0, lastIndex - i);
            System.out.println("第" + i + "次遍歷,執行結果:" + Arrays.toString(a));
        }
    }

    /**
     * 對data陣列從0到 lastIndex 建大頂堆
     */
    public static void buildMaxHeap(int[] data, int lastIndex) {
        //從lastIndex節點(最後一個節點)的父節點開始
        for (int i = (lastIndex - 1) / 2; i >= 0; i--) {
            //k儲存正在判斷的節點
            int k = i;
            //如果當前k節點的子節點存在
            while (k * 2 + 1 <= lastIndex) {
                //k節點的左子節點的下標
                int biggerIndex = left(k);
                //如果biggerIndex小於lastIndex,即biggerIndex+1代表的k節點的右子節點存在
                if (biggerIndex < lastIndex) {
                    //若右子節點的值較大
                    if (data[biggerIndex] < data[biggerIndex + 1]) {
                        //biggerIndex總是記錄較大子節點的下標
                        biggerIndex++;
                    }
                }
                //如果k節點的值小於其較大的子節點的值
                if (data[k] < data[biggerIndex]) {
                    //交換它們
                    swap(data, k, biggerIndex);
                    //將biggerIndex賦予k,開始while迴圈的下一次迴圈,重新保證k節點的值大於其左右子節點的值
                    k = biggerIndex;
                } else {
                    break;
                }
            }
        }
    }

    /**
     * 根據父節點下標獲取左孩子節點下標
     *
     * @param index 下標
     * @return 2 * index + 1
     */
    private static int left(int index) {
        // 左移1位相當於乘2
        return (index + 1) << 1 - 1;
    }

    //交換
    private static void swap(int[] data, int i, int j) {
        int tmp = data[i];
        data[i] = data[j];
        data[j] = tmp;
    }
}

基於Top k的最小堆演算法

/**
 * 最小堆
 *
 */
public class MinHeap {
    //使用陣列儲存堆中的資料
    private int[] data;

    public MinHeap(int[] data) {
        this.data = data;
        bulidHeap();
    }

    /**
     * 建立最小堆
     */
    private void bulidHeap() {
        for (int i = (data.length) / 2 - 1; i >= 0; i--) {//下標小於等於i的節點擁有子節點
            change(i);
        }
    }

    /**
     * 根據父節點判斷是否
     * 與左右孩子交換
     *
     * @param i
     */
    private void change(int i) {
        int temp = 0;
        int left = left(i);
        int right = right(i);
//存在右節點則存在左節點
        if (right < data.length) {
//拿到左右孩子中小的下標
            temp = min(left, right);
            if (data[i] > data[temp]) {
                swap(i, temp);
//如果和子節點發生交換,則要對子節點的左右孩子進行調整
                change(temp);
            }
        } else if (right < data.length) {
//不存在右節點但存在左節點,則左節點無孩子節點
            if (data[i] > data[left])
                swap(i, left);
//孩子節點大於父節點,直接交換位置
        }
    }

    /**
     * 獲取兩個節點中較小的節點的下標
     *
     * @param i
     * @param j
     * @return
     */
    private int min(int i, int j) {
        if (data[i] >= data[j])
            return j;
        return i;
    }

    /**
     * 根據父節點下標獲取
     * 左孩子節點下表
     *
     * @param i
     * @return
     */
    private int left(int i) {
        return ((i + 1) << 1) - 1;
    }

    /**
     * 根據父節點下表獲取
     * 右孩子節點下標
     *
     * @param i
     * @return
     */
    private int right(int i) {
        return (i + 1) << 1;//左移1位相當於乘2
    }

    /**
     * 根據下標交換陣列中的位置
     *
     * @param i
     * @param j
     */
    private void swap(int i, int j) {
        int temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }

    /**
     * 重置堆頂
     *
     * @param root
     */
    public void newRoot(int root) {
        data[0] = root;
        change(0);
    }

    /**
     * 獲取堆頂
     *
     * @return
     */
    public int getRoot() {
        return data[0];
    }

    /**
     * 獲取堆資料
     */
    public int[] getData() {
        return data;
    }

}

實戰

  小頂堆和大頂堆對於解決TOP-K問題擁有較高的效率,在N個數中找到K(K<N)個最大或最小的數可以分別使用小頂堆演算法和大頂堆演算法。下面我用上面的小頂堆演算法找出一個數組中最大的7個數。

public class TopK {

    public static void main(String[] args) {
        int[] num = {1, 3, 6, 7, 332, 355, 11, 325, 63, 25, 75, 32, 393, 759};
        int[] data = {1, 3, 6, 7, 332, 355, 11};//取前7個數據建立最小堆
        MinHeap heap = new MinHeap(data);
        for (int i = 7; i < num.length; i++) {
            //迴圈與堆頂比較,大於於堆頂則重置堆頂
            if (num[i] > heap.getRoot()) {
                heap.newRoot(num[i]);
            }
        }
        //迴圈輸出前7個最大的數
        for (int n : heap.getData()) {
            System.out.println(n);
        }
    }
}

測試結果如下所示:

63
325
75
393
332
355
759

小結

  對於Wiener以上的話題,大家又有什麼自己的獨特見解呢?歡迎在下方評論區留言!

Reference


  讀後有收穫,小禮物走一走,請作者喝咖啡。

讚賞支援