本文将首先介绍什么是堆,然后介绍了堆的插入和删除操作,最后给出了堆的代码实现,并进行了测试。
什么是堆
堆是一颗完全二叉树,堆中某个节点的值总是不大于或不小于其父节点的值。根节点最大的堆叫做大根堆,根节点最小的堆叫做小根堆。
首先解释下什么是完全二叉树,设一颗二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。如下图所示,左侧的二叉树满足完全二叉树的定义,而右侧的不满足。
上图左侧便是一个小根堆,满足任意一个节点的值总是不小于其父节点的值。
堆的表示
一般的二叉树表示时需要首先定义节点结构,节点中包含指向父节点的指针,如下所示:
class Node<E>{
E e;//节点储存的值
Node left,right;//左右子节点
public Node(E e){
this.e = e;
this.left = this.right = null;
}
}
但是堆并不是像树一样存储,其中没有使用父指针或者子指针,而是用数组来实现。怎么用数组来实现呢?先看一张图,如下:
我们从0开始对节点进行编号,寻找其中父子节点之间索引的对应关系。
首先,通过子节点的索引来找父节点的索引,设子节点的索引为i,则其父节点的索引为
int parentIndex = (i - 1) / 2;
然后,通过父节点的索引来找子节点的索引,设父节点的索引为p,则其孩子节点的索引为
int leftChildIndex = 2 * p + 1;//左子节点
int rightChildIndex = 2 * p + 2;//右子节点
这样,通过子节点与父节点之间的索引关系,便相当于建立了父节点和子节点之间的指针,实现了用数组来存储堆这种数据结构。
堆的插入
对于堆来说,只有插入和删除两种操作,先谈一下堆的插入操作,此处以小根堆为例。
如上图1所示,在小根堆中插入元素0,首先将元素放置在二叉树最后一行的末尾,此时依然是完全二叉树;然后将该元素与父节点的值比较,若改节点的值小于父节点,则进行交换,如图3所示;之后再次与父节点进行对比交换,直至该节点的值大于等于父节点的值或者已经是根节点为止,如图4 所示。此时堆依然满足定义。
堆的删除
对于堆来说,删除元素是指移除根节点。以小根堆为例,是指移除根中最小值的节点,也就是根节点。移除很简单,之后我们要通过操作来使得堆依然满足定义。
首先删除堆中索引为0,也就是根节点,对于小根堆来说也就是最小值。然后将堆中最后一个元素填充至根节点的位置,如图3所示;之后比较该节点与左右子节点,若该节点大于左右子节点中较小的节点的值,则与该节点进行交换(小根堆中父节点永远与左右子节点中较小的那个子节点交换),如图4、5所示;直至满足该节点的值小于其左右子节点的值或者该节点左右子节点均为空。此时堆依然满足定义。
堆的实现
下面给出堆的代码实现,如下所示,实现了堆的插入和删除操作,并进行了测试。
package datastructures;
public class Heap {
private int[] data;//存储堆的数组
private int size;//堆中元素的数量
public Heap(int capacity){
data = new int[capacity];//初始化数组
size = 0;//初始化数量
}
/**
* 插入元素
*/
public void insert(int value) throws Exception{
if(size == data.length)
throw new Exception("堆已满");
else{
data[size] = value;//将新插入的元素放在堆的末尾
int i = size;
size ++;
while(i > 0){//对堆进行调整,直至满足条件
int p = (i - 1) / 2;
if(data[i] < data[p]){
int temp = data[i];
data[i] = data[p];
data[p] = temp;
i = p;
}
else
break;
}
}
}
/**
* 删除堆中的元素
* @return
* @throws Exception
*/
public int delMin() throws Exception{
int res;
if(size == 0)
throw new Exception("为空");
else{
res = data[0];//返回索引为0的元素
size -- ;
data[0] = data[size];//将堆中最后一个元素填充至索引为0的位置
int i = 0;
while(2 * i + 1 < size){//对堆进行调整
int left = 2 * i + 1;
int right = 2 * i + 2;
if(right < size && data[right] < data[left] && data[right] < data[i]){
int temp = data[i];
data[i] = data[right];
data[right] = temp;
i = right;
}
else if(data[left] < data[i] && (right >= size || data[right] >= data[left])){
int temp = data[i];
data[i] = data[left];
data[left] = temp;
i = left;
}
else
break;
}
}
return res;
}
//测试
public static void main(String[] args) throws Exception {
Heap heap = new Heap(10);
heap.insert(1);
heap.insert(5);
heap.insert(4);
heap.insert(3);
heap.insert(6);
heap.insert(2);
System.out.println(heap.delMin());
System.out.println(heap.delMin());
System.out.println(heap.delMin());
System.out.println(heap.delMin());
System.out.println(heap.delMin());
System.out.println(heap.delMin());
}
}
推荐阅读
为什么有红黑树?什么是红黑树?看完这篇你就明白了
《深入浅出话数据结构》系列之什么是B树、B+树?为什么二叉查找树不行?
都2020年了,听说你还不会归并排序?手把手教你手写归并排序算法
为什么会有多线程?什么是线程安全?如何保证线程安全?
觉得文章有用的话,点赞+关注呗,好让更多的人看到这篇文章,也激励博主写出更多的好文章。
更多关于算法、数据结构和计算机基础知识的内容,欢迎扫码关注我的原创公众号「超悦编程」。