数据结构系列——堆
来源:互联网 发布:rf流体软件 编辑:程序博客网 时间:2024/05/22 01:33
堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。在队列中,调度程序反复提取队列中第一个作业并运行,因为实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。
堆的性质
堆的实现通过构造二叉堆(binary heap),实为二叉树的一种;由于其应用的普遍性,当不加限定时,均指该数据结构的这种实现。这种数据结构具有以下性质:
- 任意节点小于(或大于)它的所有后裔,最小元(或最大元)在堆的根上(堆序性)。
- 堆总是一棵完全树。即除了最底层,其他层的节点都被元素填满,且最底层尽可能地从左到右填入。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。
堆的主要应用场景有:堆排序以及优先队列
- 堆排序可具体参考排序算法系列——堆排序
- 优先队列可参考JDK并发工具类源码学习系列——PriorityBlockingQueue
堆的表现形式
对于理解堆以及实现堆很重要的一点就是明白堆的表现形式,堆是树的一种,所以很自然的想到使用链表来实现堆,其实不然,由于我们需要频繁的对堆进行增加删除,所以一般堆的底层都是通过数组来实现,那么就有一个问题:数组如何表现出堆的结构呢?这里就有一个规则,即数组的第一个元素(即下标为0的元素)为堆的根节点,其他节点按照堆结构自上而下,自左而右依次表示为数组中的元素,这是一种既非前序也非后序,更非中序的遍历树的方式。具体见下图。
明白了堆如何使用数组来表述,那么我们就很容易理解下面堆的结构特征了。
- parent(t)=(t - 1) >> 1,即t的父节点的下标=(t-1)/2,注意此处是整除,例如:t = 6,parent(t) = 2
- left(t)=t << 1 + 1,即t的左孩子的节点下标=t * 2 + 1,例如:t = 2,left(t) = 5
- right(t)=t << 1 + 2,即t的右孩子的节点下标=t * 2 + 2,例如:t = 2, right(t) = 6
- 说明:此处的下标是从0开始
堆的操作
针对堆主要的操作有:
- 构建堆:将一个数组构建成堆的结构
- 移除堆的根节点:从堆中移除最大值(大顶堆)或者最小值(小顶堆)
- 往堆中插入一个节点:插入节点
- 调整堆:在对堆的结构进行修改之后需要进行的操作,以保证堆结构
堆的具体实现
下面看看一个Java版的堆的实现。
package com.vicky.datastructure.tree.heap;import java.util.Arrays;/** * * <p> * 数据结构——树——堆 * </p> * * @author Vicky * @email vicky01200059@163.com * @2015年11月18日 * */public abstract class Heap { protected int[] data; protected int length = 0; public Heap(int[] data) { this.data = data; this.length = data.length; } /** * 构建堆 */ public abstract Heap buildHeap(); /** * 删除一个节点,只能删除根节点 * * @return */ public abstract Heap remove(); /** * 插入一个节点,只能插入到最后 * * @param value * @return */ public abstract Heap insert(int value); /** * 从node开始自上而下调整堆 * * @param node */ public abstract void adjustDownHeap(int node); /** * 从node开始自下而上调整堆 * * @param node */ public abstract void adjustUpHeap(int node); /** * 交换元素 * * @param n1 * @param n2 */ public void swap(int n1, int n2) { int temp = data[n1]; data[n1] = data[n2]; data[n2] = temp; } /** * 获取node的父节点的索引 * * @param node * @return */ protected int getParentIndex(int node) { return (node - 1) >> 1; } /** * 获取node的右孩子索引 * * @param node * @return */ protected int getRightChildIndex(int node) { return (node << 1) + 1; } /** * 获取node的左孩子索引 * * @param node * @return */ protected int getLeftChildIndex(int node) { return (node << 1) + 2; } /** * 打印堆 */ protected void print() { System.out.println(Arrays.toString(data)); }}package com.vicky.datastructure.tree.heap;import java.util.Arrays;/** * <p> * 最大堆 * </p> * * @author Vicky * @email vicky01200059@163.com * @2015年11月18日 * */public class MaxHeap extends Heap { public MaxHeap(int[] data) { super(data); } /** * {@inheritDoc} */ public Heap buildHeap() { // 从最后一个节点的父节点开始构建堆 int start = getParentIndex(length - 1); for (; start >= 0; start--) { adjustDownHeap(start); } return this; } /** * {@inheritDoc} */ public Heap remove() { // 将最后一个节点与头结点交换 swap(0, length - 1); // 重新复制一个数组 int[] newData = new int[length - 1]; System.arraycopy(data, 0, newData, 0, length - 1); this.data = newData; this.length = length - 1; // 从头开始调整堆 adjustDownHeap(0); return this; } /** * {@inheritDoc} */ public Heap insert(int value) { // 插入到数组最后 int[] newData = new int[length + 1]; System.arraycopy(data, 0, newData, 0, length); newData[length] = value; this.data = newData; this.length = length + 1; // 从最后一个节点开始自下而上调整堆 adjustUpHeap(this.length - 1); return this; } /** * {@inheritDoc} */ public void adjustDownHeap(int node) { int right = getRightChildIndex(node);// 右孩子 int left = getLeftChildIndex(node);// 左孩子 int max = node;// 三者最大的节点的索引 if (right < length && data[right] > data[max]) { max = right; } if (left < length && data[left] > data[max]) { max = left; } if (max != node) {// 需要调整 swap(node, max); adjustDownHeap(max);// 递归调整与根节点进行交换的节点,保证下层也是堆 } } /** * {@inheritDoc} */ public void adjustUpHeap(int node) { int parent = getParentIndex(node);// 父节点 if (parent >= 0 && data[parent] < data[node]) { swap(node, parent); adjustUpHeap(parent);// 递归调整与根节点进行交换的节点,保证上层也是堆 } } public static void main(String[] args) { int[] data = new int[10]; for (int i = 0; i < data.length; i++) { data[i] = (int) (Math.random() * 100); } System.out.println(Arrays.toString(data)); Heap heap = new MaxHeap(data); heap.buildHeap().print(); heap.remove().print(); heap.insert((int) (Math.random() * 100)).print(); }}
如上代码使用了继承,首先定义了一个堆的抽象类(Heap),封装了一些堆的公用方法,如:swap/getParentIndex/getRightChildIndex/getLeftChildIndex/print,其中具体的实现跟堆相关的方法通过抽象类由子类来实现,如:buildHeap/remove/insert/adjustHeap。大顶堆(MaxHeap)继承自Heap,实现具体的大顶堆的操作。小顶堆(MinHeap)的代码就不贴了,跟大顶堆相差不大,完整代码可见:GitHub。
下面结合上面的代码分别对堆的四个操作进行简单解释,都比较简单。
构建堆
首先构建堆,对于给定一个数组,我们需要将其构建成一个堆的结构,这个过程是一个递归的过程,相当于盖楼,先从最底层开始构建,并逐层往上构建,最终构建成一个完整的堆。
所以这里从最后一个节点的父节点开始构建堆,即构建最底层的堆,看图。
移除元素
从一个堆的结构中移除元素,一般都是移除堆顶元素,即最大值或者最小值,当我们移除堆顶元素之后,需要用堆的最后一个元素来填补到堆顶元素,然后这个堆的结构已经被破坏,我们需要调整这个堆,使其满足堆的结构。调整堆将在下面说。
插入元素
堆中插入元素一般是将其插入到堆的最后一个元素,然后自下而上递归调整堆。
调整堆
调整堆的方式有两种:自上而下和自下而上,分别对应移除堆和插入元素。但是两种方式的逻辑是一致的,即递归调整堆,如自上而下则寻找其两个子节点,将三者最大的放到根节点(三者小堆的根,不是整个大堆),然后递归调整移动了的节点(如右节点最大,则将右节点与根节点发生交换,递归调整右节点),最终到达底层,或者中途未发生交换则结束。自下而上原理类似,只是将一个节点与其父节点进行比较。
以上是我个人对堆的了解,如有错误请不宁赐教~
如果想了解更多关于堆的使用案例,可参考:
- 堆排序可具体参考排序算法系列——堆排序
- 优先队列可参考JDK并发工具类源码学习系列——PriorityBlockingQueue
参考文章
堆 (数据结构)
数据结构之堆
欢迎访问我的个人博客,寻找更多乐趣~
- 数据结构系列——堆
- 数据结构C语言实现系列[6]——堆
- 重学数据结构系列之——堆及堆排序
- 数据结构——堆
- 数据结构——堆
- 数据结构——堆
- 数据结构——堆
- 数据结构——堆
- 数据结构——堆
- 数据结构——堆
- 数据结构——堆
- 堆—数据结构
- 数据结构 — 堆排序
- 数据结构—堆
- 数据结构—堆
- 【数据结构系列】堆与堆排序
- 数据结构——堆排序
- 数据结构——堆排序
- Ⅰ.2.8 如何做自动压力测试(Automatic Stress Testing)
- XML的增删改查
- IOS-简单UI组件的一些常见问题
- 深度学习word2vec笔记之算法篇
- 如何让linux 复制拷贝命令 不询问覆盖、自动覆盖 问题
- 数据结构系列——堆
- 创业者们还有成为下一个BAT级存在的机会
- 文章标题
- jquery 中post 、get的同步问题
- 高斯函数一阶求导二阶求导
- 操作系统Unix、Windows、Mac OS、Linux的故事
- 关于des文件的加密,解密。
- 深入理解java语言的class文件格式(二)
- java变参