堆的定义及其实现
前言
堆是计算机科学中一类特殊的数据结构的统称,堆通常可以被看做是一棵完全二叉树的数组对象。本文讲解堆增删的基本思路和具体代码实现。
一、堆的特性
1.它是完全二叉树,除了树的最后一层结点不需要是满的,其它的每一层从左到右都是满的,如果最后一层结点不是满的,那么要求左满右不满。
2.它通常用数组来实现。
具体方法就是将二叉树的结点按照层级顺序放入数组中,根结点在位置1,它的子结点在位置2和3,而子结点的子结点则分别在位置4,5,6和7,以此类推。
3. 堆中树的某个结点的父结点与左右子结点对应数组的索引关系
如果一个结点的位置为k,则它的父结点的位置为k/2,而它的两个子结点的位置则分别为2k和2k+1。这样,在不使用指针的情况下,我们也可以通过计算数组的索引在树中上下移动:从a[k]向上一层,就令k等于k/2,向下一层就令k等于2k或2k+1。
4.每个结点都大于等于它的两个子结点。这里要注意堆中仅仅规定了每个结点大于等于它的两个子结点,但这两个子结点的顺序并没有做规定,跟我们之前学习的二叉查找树是有区别的。
二、堆的实现
0、堆的设计
类名:
Heap<T extends Comparable>
构造函数:
public Heap(int capacity)
成员变量:
private T[] item; //存储堆中的元素
private int N; //记录堆中元素的个数
成员方法:
成员方法 | 方法说明 |
---|---|
private boolean less(int i,int j) | 判断堆中索引i处的元素是否小于索引j处的元素 |
private void exch(int i,int j) | 交换堆中i索引和j索引处的值 |
public void insert(T t) | 往堆中插入一个元素 |
private void swim(int k) | 使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置 |
public T delMax() | 删除堆中最大的元素,并返回这个最大元素 |
private void sink(int k) | 使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置 |
1、堆的插入思想与实现
堆是用数组完成数据元素的存储的,由于数组的底层是一串连续的内存地址,所以我们要往堆中插入数据,我们只能往数组中从索引0处开始,依次往后存放数据,但是堆中对元素的顺序是有要求的,每一个结点的数据要大于等于它的两个子结点的数据,所以每次插入一个元素,都会使得堆中的数据顺序变乱,这个时候我们就需要通过上浮算法让刚才插入的这个数据放入到合适的位置。
- 利用上浮算法进行数据插入示例图
上浮算法就是将待上浮的结点与其父结点比较,如果待上浮的结点大于父结点,则交换位置,循环往复,直至待上浮的结点没有父结点。
因此,将待插入的结点放到数组的最后一个元素,然后将插入的结点作为待上浮的结点,通过上浮算法将其放到合适的位置。
- 堆插入实现
//往堆中插入一个元素
public void insert(T t)
{
item[++N]=t;
swim(N);
}
//使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
private void swim(int k)
{
//通过循环,不断将索引为k的当前结点与其索引为k/2的父结点进行比较,如果当前结点大于父结点,则交换位置
while (k>1)
{
if (less(k/2,k))
exch(k/2,k);
k=k/2;
}
}
2、堆删除最大结点的思想与实现
由堆的特性我们可以知道,索引1处的元素,也就是根结点就是最大的元素,当我们把根结点的元素删除后,需要有一个新的根结点出现,这时我们可以暂时把堆中最后一个元素放到索引1处,充当根结点,但是它有可能不满足堆的有序性需求,这个时候我们就需要下沉算法,让这个新的根结点放入到合适的位置。
- 堆删除最大元素的示例图
下沉算法就是通过将待下沉的结点k,与其左子结点2k和右子结点2k+1中较大的结点进行比较,如果待下沉的结点小于左子结点和右子结点中较大的结点,则交换位置,循环往复,直至待下沉的结点没有左右子结点即叶子节点时完成下沉。
删除最大结点,即删除索引1处的结点,首先将最大索引处结点与索引1处的结点交换,此时会破坏堆的有序行,然后对索引处的结点执行下沉算法,使堆重新有序。
- 堆删除最大结点的代码实现
//删除堆中最大的元素,并返回这个最大元素
public T delMax()
{
//1. 获取堆中最大元素
T max=item[1];
//2. 交互待删除的最大元素与堆中索引最大的最小元素,使堆中索引最大出的元素为临时结点
exch(1,N);
//3. 删除堆中索引最大处的元素
item[N]=null;
//4. 元素个数-1
N--;
//5. 通过下沉算法调整堆,让堆重新有序
sink(1);
return max;
}
//使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置
private void sink(int k)
{
//通过循环不断比较父结点与其子结点(左子结点为2k,右子结点为2k+1)中相对较大的一个结点中元素的大小,如果父结点小于较大的子结点,则交换;如果该结点没有左子结点,则结束
while (2*k<=N)
{
int maxIndex;//左右子结点较大结点的索引
//判断是否有右子结点
if (2*k+1<=N) {
if (less(2 * k, 2 * k + 1))
maxIndex = 2 * k + 1;
else
maxIndex = 2 * k;
}
else
maxIndex=2*k;
//比较k处结点与其最大子结点的大小,若k处结点元素小,则交换
if (!less(k,maxIndex))
{
break;
}
exch(k,maxIndex);
k=maxIndex;
}
}
三、堆实现完整代码
/** 堆 数据结构 **/
//该实现可以称为最大堆,相对应也可以按照同样的思想构建最小堆;
//最大堆的2大特性:1.最大的元素放在数组的索引1处;2.每个结点的数据总是大于等于它的两个子结点的数据。
//最小堆的2大特性:1.最小的元素放在数组的索引1处;2.每个结点的数据总是小于等于它的两个子结点的数据。
//而最大堆就是最大优先队列的实现思想,最小堆就是最小优先队列的实现思想。
//最大优先队列,即每次从数据中拿出最大的数据,或者优先执行等级高的命令等;因为最大堆可以很方便的实现删除并获取最大结点,故可以最大堆实现最大优先队列;
//最小优先队列,即每次从数据中拿出最小的数据,或者优先执行等级低的命令等;因为最小堆可以很方便的实现删除并获取最大结点,故可以最小堆实现最小优先队列;
public class Heap<T extends Comparable<T>> {
//存储堆中的元素
private T[] item;
//记录堆中元素的个数
private int N;
public Heap(int capacity)
{
this.item=(T[])new Comparable[capacity+1];
this.N=0;
}
//判断堆中索引i处的元素是否小于索引j处的元素
private boolean less(int i,int j)
{
return item[i].compareTo(item[j])<0;
}
//交换堆中i索引和j索引处的值
private void exch(int i,int j)
{
T temp=item[i];
item[i]=item[j];
item[j]=temp;
}
//往堆中插入一个元素
public void insert(T t)
{
item[++N]=t;
swim(N);
}
//使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
private void swim(int k)
{
//通过循环,不断将索引为k的当前结点与其索引为k/2的父结点进行比较,如果当前结点大于父结点,则交换位置
while (k>1)
{
if (less(k/2,k))
exch(k/2,k);
k=k/2;
}
}
//删除堆中最大的元素,并返回这个最大元素
public T delMax()
{
//1. 获取堆中最大元素
T max=item[1];
//2. 交互待删除的最大元素与堆中索引最大的最小元素,使堆中索引最大出的元素为临时结点
exch(1,N);
//3. 删除堆中索引最大处的元素
item[N]=null;
//4. 元素个数-1
N--;
//5. 通过下沉算法调整堆,让堆重新有序
sink(1);
return max;
}
//使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置
private void sink(int k)
{
//通过循环不断比较父结点与其子结点(左子结点为2k,右子结点为2k+1)中相对较大的一个结点中元素的大小,如果父结点小于较大的子结点,则交换;如果该结点没有左子结点,则结束
while (2*k<=N)
{
int maxIndex;//左右子结点较大结点的索引
//判断是否有右子结点
if (2*k+1<=N) {
if (less(2 * k, 2 * k + 1))
maxIndex = 2 * k + 1;
else
maxIndex = 2 * k;
}
else
maxIndex=2*k;
//比较k处结点与其最大子结点的大小,若k处结点元素小,则交换
if (!less(k,maxIndex))
{
break;
}
exch(k,maxIndex);
k=maxIndex;
}
}
}