算法进阶之路(三):堆的数据结构、排序及应用

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 算法进阶之路(三):堆的数据结构、排序及应用,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

一、堆的数据结构

        堆就是用数组实现的二叉树,主要分为两种:最大堆和最小堆。两者的差别在于节点的排序方式,在最大堆中,父节点的值比每一个子节点的值都要大。在最小堆中,父节点的值比每一个子节点的值都要小。这就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立。
        需要注意的是,堆的根节点中存放的是最大或者最小元素,但是其他节点的排序顺序是未知的。例如,在一个最大堆中,最大的那一个元素总是位于 index 0 的位置,但是最小的元素则未必是最后一个元素。–唯一能够保证的是最小的元素是一个叶节点,但是不确定是哪一个。例如:
在这里插入图片描述

二、堆和二叉树的区别

1.节点的顺序,在二叉搜索树中,左子节点必须比父节点小,右子节点必须必比父节点大。但是在堆中并非如此。在最大堆中两个子节点都必须比父节点小,而在最小堆中,它们都必须比父节点大。

2.内存占用,普通树占用的内存空间比它们存储的数据要多。你必须为节点对象以及左/右子节点指针分配内存。堆仅仅使用一个数据来存储数组,且不使用指针。

3.平衡,二叉搜索树必须是“平衡”的情况下,其大部分操作的复杂度才能达到O(log n)。你可以按任意顺序位置插入/删除数据,或者使用 AVL 树或者红黑树,但是在堆中实际上不需要整棵树都是有序的。我们只需要满足堆属性即可,所以在堆中平衡不是问题。因为堆中数据的组织方式可以保证O(log n) 的性能。

4.搜索,在二叉树中搜索会很快,但是在堆中搜索会很慢。在堆中搜索不是第一优先级,因为使用堆的目的是将最大(或者最小)的节点放在最前面,从而快速的进行相关插入、删除操作。

三、代码实现堆

实现思路在注释中有完整说明:

/**
 * @author ljx
 * @Description: 自定义数组实现最大堆
 * @date 2021/10/21 10:41 上午
 */
public class MaxHeap {

    private int[] heap;
    private int limit;
    private int heapSize;

    public MaxHeap(int limit){
        heap = new int[limit];
        this.limit = limit;
        heapSize = 0;
    }

    // 往堆中添加元素
    public void push(int value){
        if(heapSize==limit){
            throw new IndexOutOfBoundsException("堆空间已满,无法添加");
        }
        heap[heapSize] = value;
        heapInsert(heap,heapSize++);
    }
    // 弹出元素
    public int pop(){
        // 弹出当前根节点的值
        int root = heap[0];
        // 将数组中最后一个节点放置到根节点
        swap(heap,0,--heapSize);
        // 最后一个元素移除
        heap[heapSize]=0;
        // 重新调整当前堆为最大堆
        heapify(heap,0,heapSize);
        return root;
    }

    // 向堆中插入元素,并完成最大堆的构建
    private void heapInsert(int[] arr, int index){
        // 计算出父节点的索引
        int parent = (index-1)/2;
        // 比较当前节点和父节点的大小,当前节点比父节点大时,当前节点和父节点交换位置
        // 只有两种情况下,停止循环 1.当前节点小于父节点 2.循环已经到达父节点,此时自己肯定不小于自己,因为两个条件都可以通过arr[index]<=arr[parent]来跳出循环
        while(arr[index]>arr[parent]){
            // 当前节点大于等于父节点,则和父节点交换位置
            swap(arr,index,parent);
            // 同时当前节点的索引位置调整到父节点的索引
            index = (index-1)/2;
        }
    }

    // 将一个数组调整为最大堆
    public void heapify(int[] arr,int index,int heapSize){
        // 从根节点开始,和两个子节点比较,如果子节点更大,则调换位置,直到没有子节点为止
        // 分别计算出两个子节点的索引位置
        int left = index*2+1;
        // 子节点不存在时跳出循环,只要左节点小于父节点,就一直循环
        while(left<heapSize){
            // 右边的子节点存在时,比较得出两个子节点的最大值
            int latest = left+1<heapSize&&arr[left]>arr[left+1]?left:left+1;
            // 再比较子节点和父节点的大小
            latest = arr[latest]>arr[index]?latest:index;
            // 父节点大于两个子节点时,跳出循环
            if(latest==index){
                break;
            }
            // 父节点小于子节点时,两个节点交换位置
            swap(arr,index,latest);
            // 同时index位置下沉
            index = latest;
            // 重新计算左节点的位置
            left = index*2+1;
        }

    }

    // 完成堆排序
    public void heapSort(int[] arr){
        if(arr==null||arr.length==1){
            return;
        }
        // 先将数组中的每一个元素放在最大堆中
        // 时间复杂度 O(N*logN)
        for(int i=0;i<arr.length;i++){
            heapInsert(arr,i);
        }
//        // 如果每次给的是数组中的一批数据,而不是一个个给的话,上述循环,可以优化如下,
//        for(int i=arr.length-1;i>=0;i--){
//            heapify(arr,i,arr.length);
//        }

        int heapSize = arr.length;
        // 每次把最大数,即0元素位置的数调整到数组的末尾,同时heapSize减1
        swap(arr,0,--heapSize);
        // 一直调整,直到heapSize>0
        while (heapSize>0){
            // 重新调整堆
            heapify(arr,0,heapSize);
            swap(arr,0,--heapSize);
        }

    }
    // 交换数组中两个位置的值
    private void swap(int[] arr, int son, int parent) {
        // 如果是值相同,或者是同一位置的,则无需交换
        if(arr[son]==arr[parent]||son == parent){
            return;
        }
        arr[son]=arr[son]^arr[parent];
        arr[parent]=arr[son]^arr[parent];
        arr[son]=arr[son]^arr[parent];
    }

    public int[] getHeap() {
        return heap;
    }

    public void setHeap(int[] heap) {
        this.heap = heap;
    }

    public int getLimit() {
        return limit;
    }

    public void setLimit(int limit) {
        this.limit = limit;
    }

    public int getHeapSize() {
        return heapSize;
    }

    public void setHeapSize(int heapSize) {
        this.heapSize = heapSize;
    }
}

测试代码:

    public static void main(String[] args) {
        MaxHeap maxHeap = new MaxHeap(10);
        maxHeap.push(1);
        maxHeap.push(9);
        maxHeap.push(6);
        maxHeap.push(3);
        maxHeap.push(7);
        maxHeap.push(8);
        System.out.println("打印堆中数据:" + Arrays.toString(maxHeap.getHeap()));
        System.out.println("打印取出的堆中最大数据:" + maxHeap.pop());
        System.out.println("打印取出堆中最大数据后,堆的数据:" + Arrays.toString(maxHeap.getHeap()));
        maxHeap.heapSort(maxHeap.getHeap());
        System.out.println("打印排序后堆的数据:" + Arrays.toString(maxHeap.getHeap()));
    }

测试结果:
在这里插入图片描述
分析可知,测试结果完全符合预期,代码无误。

四、总结

1.堆结构总结

在这里插入图片描述

2.堆排序总结

在这里插入图片描述

五、面试题练习

Java中也有堆的实现,java.util.PriorityQueue,默认是最小堆,不过可以通过设置内部的参数构造器comparator,来生成最大堆,下面的面试题中会带领大家使用一下此类。
在这里插入图片描述
解题思路及代码:

    private static void sort(int[] arr,int k){
        // 默认最小堆
        PriorityQueue<Integer> priorityQueue = new PriorityQueue();
        // 先往最小堆中添加k个元素
        int index = 0;
        for(;index<=Math.min(k,arr.length-1);index++){
            priorityQueue.add(arr[index]);
        }
        // 依次往数组中取出一个元素,再放置一个元素,直到数组遍历完成,则每次取出的最下值即是数组中顺序
        int i=0;
        while(index<arr.length){
            arr[i++]=priorityQueue.poll().intValue();
            priorityQueue.add(arr[index++]);
        }
        // 数组遍历完成后,i到arr.length位置的元素还保存在最小堆中,此时全部取出
        while(i<arr.length){
            arr[i++]=priorityQueue.poll().intValue();
        }
    }

注意: 语言提供的堆结构,在动态修改数据后,需要重新自旋,否则会失效;当有大量的动态修改堆中节点信息的需求时,建议使用自己手写的堆结构。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/130200.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!