带有优先级的完全二叉树-堆

堆的概念

堆是一种完全二叉树(除了最后一层每个节点的子节点都是满的,最后一层的节点也是集中在左边)

虽然堆是一棵二叉树,但也由于它是一棵完全二叉树的原因,可以使用一个数组来表示堆。(层次遍历)

我们常用到的堆是最大堆和最小堆

最小堆

最小堆要求,对于任意一个父结点来说,其子结点的值都大于这个父节点。

带有优先级的完全二叉树-堆

对于这个最小堆,可以用数组表示为[1,3,30,22,10,50]

最大堆

最大堆和最小堆相反,对于任意一个父结点来说,其子结点的值都小于这个父节点。

堆的操作

插入

我们看如何构建一个最小堆:

对于[30,15,5,11,22,2]

最小堆的构建过程

可以看出,我们依次按数组的顺序放入二叉树的节点,一旦出现子节点比父节点的值小,交换子节点和父节点。

我们称这个操作为向上堆化,堆化后的数组为[2,11,5,30,22,15]

删除

将堆树中待删除的节点A与堆树中的最后一个节点B互换,然后调整节点B到合适的位置,最后从堆树中删除排在堆数组最后的节点A。

不过在堆这个数据结构中,我们通常只需要删除堆顶元素。

删除堆顶元素

向下堆化

堆的应用

优先级队列

实现优先级队列的方法有很多,但是用堆来实现是最直接、最高效的。堆和优先级队列非常相似,一个堆就可以看作一个优先级队列。从优先级队列中取出优先级最高的元素,就相当于取出堆顶元素。

堆排序

堆排序实际上就是不断获取堆顶元素。

对于最大堆来说,我们知道最大堆的队首元素是整个堆最大的元素,当我们删除并得到堆顶元素时,剩下的元素会重新堆化,得到一个新的堆顶元素,这样不断删除并获取堆顶元素,最终拿到的就是一个从大到小的有序序列。

当然,用数组代表堆,我们在排序的时候可以不必删除元素,只需要将原堆顶元素移动到数组末尾,将剩下的元素重新堆化,然后重复该步骤,最后就能得到一个从小到大排序的数组。

附:代码示例

实现一个完整的最大堆并堆排序一个数组

public class MaxHeap {
    private int[] heap;
    private int size;

    // 用数组来初始化一个堆
    public MaxHeap(int[] source){
        heap = source;
        size = source.length;
        heapify();
    }

    public int get(int index){
        return heap[index];
    }

    public int size() {
        return size;
    }
    //移除一个元素,并从这个元素开始向下堆化
    public int removeAt(int index){
        int node = heap[index];
        swap(index,size-1);
        size--;
        downShift(index);
        return node;
    }

    //构建堆
    public void heapify(){
        for(int i = 0; i < size; i++){
            int position = i;
            upShift(position);
        }
    }

    //向上堆化
    public void upShift(int position){
        int parent = getParent(position);
        while (position>0 && (heap[position]>heap[parent])){
            swap(position,parent);
            position = parent;
            parent = getParent(position);
        }
    }

    //向下堆化
    public void downShift(int position){
        int children1 = position*2+1;
        int children2 = position*2+2;
        while (children1<size){
            int toSwap = children1;
            if(children2<size){
                toSwap = heap[children1]>heap[children2]?children1:children2;
            }
            if(heap[toSwap]>heap[position]){
                swap(position,toSwap);
                position = toSwap;
                children1 = position*2+1;
                children2 = position*2+2;
            }else break;
        }
    }

    public void swap(int source,int destination){
        int cache = heap[source];
        heap[source] = heap[destination];
        heap[destination] = cache;
    }

    /**
     * 计算父节点下标
     */
    private int getParent(int child) {
        return (child + 1) / 2 - 1;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0;i<size;i++){
            sb.append(heap[i]);
            if(i!=size-1) sb.append(",");
        }
        return sb.append("]").toString();
    }

    public static void main(String[] args) {
        MaxHeap heap = new MaxHeap(new int[]{30,15,5,11,22,2});
        System.out.println(heap);
        while (heap.size()!=0){
            //堆排序得到一个序列
            int head = heap.removeAt(0);
            System.out.println(head);
        }
        //操作完后数组里元素的是从小到大排序的
        System.out.println(Arrays.toString(heap.heap));
    }
}

运行结果:

[30,22,5,11,15,2]
30
22
15
11
5
2
[2, 5, 11, 15, 22, 30]

堆排序算法Java实现

实际的堆排序中,我们肯定不用写这么长一段代码,专门把排序的逻辑提取出来就行了:

把数组看成一个最大堆(无序部分)和一个有序部分,每次将无序部分堆化一次(第一次构建堆的堆化是最耗时的,后面的堆化只需要向下堆化就好),然后第一个元素就是无序部分中最大的元素,与无序部分最后一个元素进行交换,然后并入有序部分

代码参考堆排序算法思路以及Java实现_long_lance的博客-CSDN博客,但是这里面的代码有错误,我进行了修改。

public class HeapSorting {
    public static void main(String[] args) {
        int[] a=new int[]{9,8,1,5,6,10,2};
        Sort(a);
        for(int i:a){
            System.out.println(i);
        }
    }

    //通过递归向下堆化
    public static void MaxHeapify(int[] a,int index,int size){
        int l=2*index;
        int r=2*index+1;
        int largest=index;
        if(l<=size && a[l]>a[index]){
            largest=l;
        }
        if(r<=size && a[r]>a[largest]){
            largest=r;
        }
        if(largest!=index){
            int temp=a[largest];
            a[largest]=a[index];
            a[index]=temp;
            MaxHeapify(a,largest,size);
        }
    }

    //数组堆化,注意上面实现堆的代码中构建堆是通过向上堆化,我们可以优化成将所有非叶节点向下堆化
    public static void HeapBuild(int[] a){
        for(int i=(a.length-1)/2;i>=0;i--){
            MaxHeapify(a,i,a.length-1);
        }
    }

    public static void Sort(int[] a){
        //构建堆
        HeapBuild(a);
        for(int i=a.length-1;i>=1;i--){
            //每次将堆顶元素交换到未排序部分末尾,然后再向下堆化
            int temp=a[i];
            a[i]=a[0];
            a[0]=temp;
            MaxHeapify(a,0,i-1);
        }
    }
}

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/%e5%b8%a6%e6%9c%89%e4%bc%98%e5%85%88%e7%ba%a7%e7%9a%84%e5%ae%8c%e5%85%a8%e4%ba%8c%e5%8f%89%e6%a0%91-%e5%a0%86/

(0)
彭晨涛彭晨涛管理者
上一篇 2020年2月9日 21:03
下一篇 2020年2月10日

相关推荐

  • 弱平衡的二叉查找树-红黑树

    为什么会出现红黑树? 之前我们提过AVL树-自平衡的二叉搜索树,但是一旦树的节点发生变化,AVL树为了维持绝对平衡(虽然说AVL也容忍左右两个子树最多为1的高度差,但这里仍视为绝对…

    2020年2月9日
    01190
  • Java基础查缺补漏03(附赠哈夫曼树&哈夫曼编码)

    继续我的复习刷题 构造器显式调用父类构造方法的规则 题目: 以下程序的输出结果为 class Base{ public Base(String s){ System.out.pri…

    2020年5月27日
    0250
  • 跳表-披着链表外衣的伪搜索树

    本文参考资源: 【数据结构与算法】之跳表(Java实现)---第九篇Java震哥聊校招-CSDN博客 跳表的原理及实例 - Rogn - 博客园 跳表Java实现Java偏离的定弦…

    2020年3月28日
    0710
  • leetcode155-最小栈

    原题 设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。 push(x) – 将元素 x 推入栈中。 pop() – 删除栈顶的元素。 top() …

    算法 2019年12月11日
    0130
  • 多叉平衡查找树-B树与B+树

    本文参考链接: 从B树、B+树、B*树谈到R 树_磁盘,数据结构,存储_结构之法 算法之道-CSDN博客 【数据结构】B树、B+树详解 - Assassinの - 博客园 BTre…

    2020年2月8日
    0170
  • 海量数据去重-由BitMap引出的布隆过滤器

    本文参考资源: 那些惊艳的算法们(一)——布隆过滤器_C/C++_xinzhongtianxia的博客-CSDN博客 详解布隆过滤器的原理、使用场景和注意事项 - 简书 概述 昨天…

    2020年2月28日
    01.4K0
  • leetcode705-设计哈希集合

    原题 不使用任何内建的哈希表库设计一个哈希集合 具体地说,你的设计应该包含以下的功能 add(value):向哈希集合中插入一个值。 contains(value) :返回哈希集合…

    算法 2019年12月18日
    0140
  • AVL树-自平衡的二叉搜索树

    本文图片来源:手把手教,手写AVL树 - 不止是编程 - 博客园 AVL树的概念 自平衡 当二叉搜索树处于平衡状态的时候,其操作时间复杂度为O(logN),但当二叉搜索树是单支树的…

    2019年11月25日
    0470
  • leetcode622-设计循环队列

    设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。 循环队列的一个好处是…

    算法 2019年11月24日
    0130
  • leetcode385-迷你语法分析器

    原题 给定一个用字符串表示的整数的嵌套列表,实现一个解析它的语法分析器。 列表中的每个元素只可能是整数或整数嵌套列表 提示: 你可以假定这些字符串都是格式良好的: 字符串非空 字符…

    算法 2020年1月28日
    02260

发表回复

登录后才能评论