数据结构系列——堆

原创 2015年11月19日 13:13:20

堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。在队列中,调度程序反复提取队列中第一个作业并运行,因为实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。

堆的性质

堆的实现通过构造二叉堆(binary heap),实为二叉树的一种;由于其应用的普遍性,当不加限定时,均指该数据结构的这种实现。这种数据结构具有以下性质:

  • 任意节点小于(或大于)它的所有后裔,最小元(或最大元)在堆的根上(堆序性)。
  • 堆总是一棵完全树。即除了最底层,其他层的节点都被元素填满,且最底层尽可能地从左到右填入。
    将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。

堆的主要应用场景有:堆排序以及优先队列

堆的表现形式

对于理解堆以及实现堆很重要的一点就是明白堆的表现形式,堆是树的一种,所以很自然的想到使用链表来实现堆,其实不然,由于我们需要频繁的对堆进行增加删除,所以一般堆的底层都是通过数组来实现,那么就有一个问题:数组如何表现出堆的结构呢?这里就有一个规则,即数组的第一个元素(即下标为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
下面结合上面的代码分别对堆的四个操作进行简单解释,都比较简单。

构建堆

首先构建堆,对于给定一个数组,我们需要将其构建成一个堆的结构,这个过程是一个递归的过程,相当于盖楼,先从最底层开始构建,并逐层往上构建,最终构建成一个完整的堆。
所以这里从最后一个节点的父节点开始构建堆,即构建最底层的堆,看图。

构建堆的过程

移除元素

从一个堆的结构中移除元素,一般都是移除堆顶元素,即最大值或者最小值,当我们移除堆顶元素之后,需要用堆的最后一个元素来填补到堆顶元素,然后这个堆的结构已经被破坏,我们需要调整这个堆,使其满足堆的结构。调整堆将在下面说。

插入元素

堆中插入元素一般是将其插入到堆的最后一个元素,然后自下而上递归调整堆。

调整堆

调整堆的方式有两种:自上而下和自下而上,分别对应移除堆和插入元素。但是两种方式的逻辑是一致的,即递归调整堆,如自上而下则寻找其两个子节点,将三者最大的放到根节点(三者小堆的根,不是整个大堆),然后递归调整移动了的节点(如右节点最大,则将右节点与根节点发生交换,递归调整右节点),最终到达底层,或者中途未发生交换则结束。自下而上原理类似,只是将一个节点与其父节点进行比较。

以上是我个人对堆的了解,如有错误请不宁赐教~

如果想了解更多关于堆的使用案例,可参考:

参考文章

堆 (数据结构)
数据结构之堆


欢迎访问我的个人博客,寻找更多乐趣~

版权声明:本文为博主原创文章,转载请注明出处,谢谢。

堆和栈的区别(内存和数据结构)

在计算机领域,堆栈是一个不容忽视的概念,我们编写的C语言程序基本上都要用到。但对于很多的初学着来说,堆栈是一个很模糊的概念。堆栈:一种数据结构、一个在程序运行时用于存放的地方,这可能是很多初学者的认识...
  • wolenski
  • wolenski
  • 2012年09月06日 17:53
  • 19952

堆(数据结构)及堆排序

1堆 这里的堆是指一种数据结构(或数据结构属性),非指堆内存。堆属性用二叉树来体现,具堆属性的数据结构才可被叫做为堆。具堆属性的数据结构满足以下笔记的“顺序”和“形状”两个条件。 (1)顺序 ...

数据结构:堆

已移到:http://www.wypblog.com/archives/97堆常用来实现优先队列,在这种队列中,待删除的元素为优先级最高(最低)的那个。在任何时候,任意优先元素都是可以插入到队列中去的...

【数据结构】最大堆的插入与删除

堆是一种特殊的队列,从堆中取出元素的顺序不是按照元素进入队列的先后顺序,而是依据元素的优先权,或者说是大小,所以堆也叫做“优先队列”。 堆最常使用二叉树结构表示,可以看作是一种特殊的完全二叉树,对于...

数据结构—堆的学习:堆化数组、堆的插入、堆的删除

原网页:http://blog.csdn.net/morewindows/article/details/6709644 程序如下: //数据结构堆的创建(堆化数组)、堆的插入和删除 #inclu...

数据结构-堆(heap)

堆(heap)也被称为优先队列(priority queue)。队列中允许的操作是先进先出(FIFO),在队尾插入元素,在队头取出元素。而堆也是一样,在堆底插入元素,在堆顶取出元素,但是堆中元素的排列...

常见数据结构(一)-栈,队列,堆,哈希表

本文介绍几种常见的数据结构:栈、队列、堆、哈希表,等等。

数据结构之堆(Heap)的实现

堆数据结构是一种数组对象,它可以被视为一棵完全二叉树结构,所以堆也叫做二叉堆。二叉堆满足二个特性:  1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。  2.每个结点的左子树和右子树...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:数据结构系列——堆
举报原因:
原因补充:

(最多只允许输入30个字)