1、堆的定义
堆是一棵完全二叉树,并且堆中每一个节点的值都必须大于等于(或者小于等于)其子树的每个节点的值。
2、堆的分类
大顶堆:堆中任一节点的值都大于等于子树中的节点值。
小顶堆:堆中任一节点的值都小于等于子树中的节点值。
3、堆的存储结构
因为堆是一棵完全二叉树,因此使用顺序结构(数组)存储更好!
回忆前面二叉树内容 二叉树两种存储方式:
如下图为大顶堆的存储:
4、堆的基本操作
4.1、向堆中插入元素【自底向上堆化】
参考链接:堆的原理与实现
如下图的大顶堆,当数组尾部插入元素15后,破坏了大顶堆的性质,因此必须重新调整(和AVL树失衡后调整一样的道理)
发现15>父节点9,交换二者。
又发现交换后的15>父节点14,再次交换二者。
最后15<父节点18,满足大顶堆。
4.2、删除堆顶元素【自顶向下堆化】
(1)、一种错误思路:删除堆顶元素,即用第二大元素(第二层左右子树中最大元素)替代,依次迭代下去。错误原因在于:最后形成的不是完全二叉树。
(2)、正确操作:将数组最后一个元素替代堆顶元素;再判断此时是否为大顶堆(堆顶与左右子树比较并交换,依次迭代)
如下图:删除大顶堆的堆顶元素18,先让最后元素9替代它。
此时形成的不是大顶堆,要调整。
分析后可知,堆的插入、删除时间复杂度为O(log n),取决于完全二叉树的高度。
public class Heap {
// 存放堆内元素的数组【从下标1开始存储元素,方便计算】
private int data[];
// 堆内存储元素最多个数
private int MAXSIZE;
// 堆内已经存储元素的个数
private int count;
public Heap() {
this.data = new int[11];
this.count = 0;
this.MAXSIZE = 10;
this.data[0] = Integer.MAX_VALUE;// 避免插入元素时,data[0]=0参与比较
}
public Heap(int capacity) {
this.data = new int[capacity + 1];// 从下标1开始存储堆内元素
this.count = 0;
this.MAXSIZE = capacity;
this.data[0] = Integer.MAX_VALUE;
}
public int[] getData() {
return data;
}
public void setData(int[] data) {
this.data = data;
}
public int getMAXSIZE() {
return MAXSIZE;
}
public void setMAXSIZE(int mAXSIZE) {
MAXSIZE = mAXSIZE;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
/* 向堆Heap中插入元素value,使其仍然为一个大顶堆,这个过程称为堆化。
* 第一步:将元素插入数组尾部;
* 第二步:如果插入节点<父节点,则交换二者;一直重复此操作直到形成大/小顶堆。
*/
public static boolean insertHeap(Heap heap, int value) {
if (heap.count == 0) {// 当前堆为空
System.out.println("当前堆为空,新插入元素为:" + value);
heap.data[1] = value;
heap.count = 1;
return true;
}
int length = heap.data.length;
int count = heap.count;// 堆内元素个数
if (length - count == 1) {
System.out.println("堆已满,要扩容");
return false;
}
// 正常插入时:heap.data[++count] = value,但是可能会破坏大顶堆
heap.data[++heap.count] = value;// 插入元素,数组长度+1
// 开始判断插入元素后是否还满足大顶堆性质
int j = heap.count;
while (j > 0 && (heap.data[j] > heap.data[j / 2])) {
int temp = heap.data[j];
heap.data[j] = heap.data[j / 2];
heap.data[j / 2] = temp;
j = j / 2;
}
return true;
}
/*
* 删除堆顶元素:
* 第一步:让数组最后一个元素替代堆顶元素;
* 第二步:此时堆顶元素与左右子树判断,是否符合大顶堆。
*/
public static boolean deleteHeap(Heap heap) {
if (heap.count == 0) {
System.out.println("堆内没有元素了,不能珊瑚");
return false;
}
heap.data[1] = heap.data[heap.count];
heap.data[heap.count] = 0;
heap.count--;// 替换并删除
// 开始判断替换后的堆顶元素是否还满足大顶堆性质
int j = 1;
while (j < heap.count && (heap.data[j] < heap.data[j * 2] || heap.data[j] < heap.data[j * 2 + 1])) {
int leftValue = heap.data[j * 2];// 左子树值
int rightValue = heap.data[j * 2 + 1];// 右子树值
if (leftValue > rightValue) {
int temp = heap.data[j];
heap.data[j] = heap.data[j * 2];
heap.data[j * 2] = temp;
j = j * 2;
} else {
int temp = heap.data[j];
heap.data[j] = heap.data[j * 2 + 1];
heap.data[j * 2 + 1] = temp;
j = j * 2 + 1;
}
}
return true;
}
// 打印堆内元素
public static void printHeap(Heap heap) {
for (int i = 1; i <= heap.count; i++) {
System.out.print(heap.data[i] + " ");
}
System.out.println("\n--------------打印结束---------------");
}
public static void main(String[] args) {
Heap heap = new Heap();
// insertHeap(heap, 20);
// insertHeap(heap, 10);
// insertHeap(heap, 15);
// insertHeap(heap, 18);// 调整
// insertHeap(heap, 16);
// insertHeap(heap, 12);
// insertHeap(heap, 8);
// insertHeap(heap, 30);// 调整
// printHeap(heap);
//
// deleteHeap(heap);// 删除堆顶元素30
// printHeap(heap);
// insertHeap(heap, 20);
// insertHeap(heap, 10);
// insertHeap(heap, 16);
// insertHeap(heap, 5);
// insertHeap(heap, 15);
// printHeap(heap);
// deleteHeap(heap);// 删除堆顶元素20
// printHeap(heap);
insertHeap(heap, 10);
insertHeap(heap, 5);
insertHeap(heap, 8);
insertHeap(heap, 20);
insertHeap(heap, 15);
insertHeap(heap, 16);
insertHeap(heap, 30);
printHeap(heap);
}
}
5、堆的应用之堆排序
参考链接:图解堆排序算法
(1)、堆排序的基本思想是:将待排序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
(2)、堆排序步骤:
第一步、构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
第二步:将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
/*
* 堆排序:
* 第一步:构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
* 第二步:将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
*/
public class HeapSort {
/*
* copy的Heap类代码,这里就不改为插入数组了
*/
public static boolean insertHeap(Heap heap, int value) {
if (heap.getCount() == 0) {// 当前堆为空
System.out.println("当前堆为空,新插入元素为:" + value);
heap.setContent(heap, 1, value);
heap.setCount(1);
return true;
}
int length = heap.getData().length;
int count = heap.getCount();// 堆内元素个数
if (length - count == 1) {
System.out.println("堆已满,要扩容");
return false;
}
heap.setCount(heap.getCount() + 1);
heap.setContent(heap, heap.getCount(), value);// 插入元素,数组长度+1
// 开始判断插入元素后是否还满足大顶堆性质
int j = heap.getCount();
while (j >= 1 && (heap.getData()[j] > heap.getData()[j / 2])) {
int temp = heap.getData()[j];
heap.getData()[j] = heap.getData()[j / 2];
heap.getData()[j / 2] = temp;
j = j / 2;
}
return true;
}
/*
* 给大顶堆Heap排序:将堆顶元素与末尾元素进行交换,使末尾元素最大
*/
public static int[] sort(Heap heap) {
int srotedHeap[] = new int[heap.getCount()];// 返回排序后的数组
for (int i = heap.getCount(); i >= 1; i--) {
srotedHeap[i - 1] = heap.getData()[1];// 当前最大元素
heap.setContent(heap, 1, heap.getData()[i]);
heap.setContent(heap, i, 0);
// 开始判断替换后的堆顶元素是否还满足大顶堆性质
int j = 1;
while (j < (i - 1)
&& (heap.getData()[j] < heap.getData()[j * 2] || heap.getData()[j] < heap.getData()[j * 2 + 1])) {
int leftValue = heap.getData()[j * 2];// 左子树值
int rightValue = heap.getData()[j * 2 + 1];// 右子树值
if (leftValue > rightValue) {
int tempValue = heap.getData()[j];
heap.setContent(heap, j, heap.getData()[j * 2]);
heap.setContent(heap, j * 2, tempValue);
j = j * 2;
} else {
int tempValue = heap.getData()[j];
heap.setContent(heap, j, heap.getData()[j * 2 + 1]);
heap.setContent(heap, j * 2 + 1, tempValue);
j = j * 2 + 1;
}
}
}
return srotedHeap;
}
public static void main(String[] args) {
// int data[] = new int[] { 10, 8, 6, 7, 5 };
int data[] = new int[] { 20, 10, 15, 8, 30, 12, 5, 4, 3, 2, 50, 25, 100 };
Heap heap = new Heap(15);
for (int i = 0; i < data.length; i++) {// 完成第一步:构建初始堆
insertHeap(heap, data[i]);
}
heap.printHeap(heap);
int[] sortedHeap = sort(heap);
for (int i = 0; i < sortedHeap.length; i++) {
System.out.print(sortedHeap[i] + " ");
}
}
}