本文欢迎转载,转载前请联系作者。若未经允许转载,转载时请注明出处,谢谢! http://blog.csdn.net/colton_null 作者:喝酒不骑马 Colton_Null from CSDN
二叉堆(binary heap)是一种通常用于实现优先队列的数据结构。要想知道二叉堆是什么东西,得从两方面介绍它:结构性和堆序性。
二叉堆是一颗除底层外被完全填满的二叉树,对于底层上的元素满足从左到右填入的特性。如下图所示。
所以,鉴于二叉堆的这个结构特性,我们可以很容易得出——一颗高为h的二叉堆有 2 h 2^h 2h到 2 h + 1 − 1 2^{h+1} - 1 2h+1−1 个节点。
而且,基于二叉堆的这个特性,我们可以用一个数组在表示这种数据结构,不需要使用链。如下图所示。
数组上任意位置i上的元素,它的左节点在2i上,右节点在2i + 1上,它的父节点在└i/2┘。不用链表的好处在于,这么做对于计算机来说,访问某一元素的效率很高,不需要遍历即可访问。
这里我们用最小堆(min-heap)来说明。
在堆中,每个节点n,都小于或等于它的两个子节点。如下图所示。
当然,我们可以类比处最大堆(max-heap)即每个节点都大于或等于它的子节点。
根据二叉堆的这个性质,最小元素或者最大元素即为根节点。我们直接就可以访问到这个最小的元素,即array[1](数组结构参考结构性)。
在使用二叉堆的时候,我们对二叉堆插入和获取最小值的操作,会破坏二叉堆的特性。那么我们需要在每次操作之后,对二叉堆进行维护。
我们在执行插入(insert)操作的时候,可以采用上滤(percolate up)策略对二叉树进行维护。
首先在插入元素t时,在底层创建一个空穴,以保证完全二叉树的性质。如果元素t可以直接放在该空穴中不影响堆的堆序性,则插入完成;如果元素t不能直接放置在空穴中(即对于min-heap来说,t小于它的根节点或对于max-heap来说,t大于它的根节点),则把空穴的根节点放置在空穴中,原根节点被视为空穴。直到t被放入在合适的空穴中。
在我们获取最小元素(即获取根节点并删除它时),我们需要用下滤(percolate down)策略来维护堆序性。
当删除最小元素时,根节点变为空穴,那么为了保证二叉堆的结构性,所以要把最后一个元素x移动到该堆的某个地方。如果x可以直接放入到空穴中,则下滤结束;否则,空穴子节点中最小或最大的值(这要看是min-heap还是max-heap,min-heap为最小值,max-heap为最大值)放入到空穴中,然后空穴下移一位,直到x可以被放置在空穴中。
过程如下图所示。
BinaryHeap.java
public class BinaryHeap> {
public BinaryHeap() {
this(DEFAULT_CAPACITY);
}
public BinaryHeap(int capacity) {
currentSize = 0;
arr = (T[]) new Comparable[capacity + 1];
}
public BinaryHeap(T[] items) {
currentSize = items.length;
arr = (T[]) new Comparable[(currentSize + 2) * 11 / 10];
int i = 1;
for (T item : items) {
arr[i++] = item;
}
buildHeap();
}
/**
* 二叉堆的插入方法。使用上滤法。
*
* @param t 被插入的元素
*/
public void insert(T t) {
if (currentSize == arr.length - 1) {
// 如果当前元素个数为数组长度-1,则扩容
enlargeArray(arr.length * 2 + 1);
}
int hole = ++currentSize;
// arr[0] = t初始化,最后如果循环到顶点,t.compartTo(arr[hole / 2])即arr[0]为0,循环结束
for (arr[0] = t; t.compareTo(arr[hole / 2]) < 0; hole /= 2) {
// 根节点的值赋值到子节点
arr[hole] = arr[hole / 2];
}
// 根节点(或树叶节点)赋值为t
arr[hole] = t;
}
/**
* 寻找堆内最小值。索引1处的元素最小。
*
* @return
*/
public T findMin() {
if (isEmpty()) {
// 这里如果堆为空,可以抛出异常。
// throw new UnderflowException( );
}
// 第1位的元素最小
return arr[1];
}
public T deleteMin() {
if (isEmpty()) {
// 这里如果堆为空,可以抛出异常。
// throw new UnderflowException( );
}
T minItem = findMin();
// 将最后一个节点赋值到根节点
arr[1] = arr[currentSize--];
// 从根节点执行下滤
percolateDown(1);
return minItem;
}
/**
* 判断堆是否为空
*
* @return 为空返回true;否则返回false
*/
public boolean isEmpty() {
return currentSize == 0;
}
/**
* 堆置空
*/
public void makeEmpty() {
currentSize = 0;
}
/**
* 打印堆
*/
public void print(){
System.out.print("堆为:");
for (int i = 1;arr[i] != null;i++){
System.out.print(arr[i] + " ");
}
System.out.println();
}
private static final int DEFAULT_CAPACITY = 10;
private int currentSize;
private T[] arr;
/**
* 下滤
*
* @param hole 下滤其实的节点的索引
*/
private void percolateDown(int hole) {
int child;
T tmp = arr[hole];
for (; hole * 2 <= currentSize; hole = child) {
child = hole * 2;
if (child != currentSize && arr[child + 1].compareTo(arr[child]) < 0) {
// 做子节点不为左后一个节点(说明有右节点)且右节点比做节点小,索引改为右节点节点
child++;
}
if (arr[child].compareTo(tmp) < 0) {
// 如果遍历到的这个节点比最后一个元素小
arr[hole] = arr[child];
} else {
break;
}
}
// 将最后一个元素补到前面的空位
arr[hole] = tmp;
}
private void buildHeap() {
for (int i = currentSize / 2; i > 0; i--) {
percolateDown(i);
}
}
/**
* 扩容
* @param newSize 新数组的大小
*/
private void enlargeArray(int newSize) {
T[] old = arr;
arr = (T[]) new Comparable[newSize];
for (int i = 0; i < old.length; i++) {
arr[i] = old[i];
}
}
}
BinaryHeapTest.java 测试类
public class BinaryHeapTest {
public static void main(String[] args) {
BinaryHeap binaryHeap = new BinaryHeap();
int[] nums = new int[]{23, 98, 34, 63, 3, 0, 87, 45};
for (Integer num : nums) {
binaryHeap.insert(num);
}
binaryHeap.print();
System.out.println("堆是否为空:" + binaryHeap.isEmpty());
System.out.println("获取最小值:" + binaryHeap.deleteMin());
binaryHeap.print();
}
}
输出:
堆为:0 23 3 45 63 34 87 98
堆是否为空:false
获取最小值:0
堆为:3 23 34 45 63 98 87 98
最小二叉堆实现可用。
以上就是有关二叉堆(binary heap)介绍及其Java实现。
有关[数据结构与算法]的学习内容已经上传到github,喜欢的朋友可以支持一下。
data-structures-and-algorithm-study-notes-java
站在前人的肩膀上前行,感谢以下博客及文献的支持。
《数据结构与算法分析(第3 版) 工业出版社》