堆排序算法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

堆排序算法Java实现

上一篇:Java日志浅谈


下一篇:算法-二叉树