堆
- 堆(Java) --优先级队列的代理者
- 最大堆
堆(Java) --优先级队列的代理者
聊堆不能不聊优先级队列,优先级队列就是决定哪个任务优先执行的队列,通常会有一个优先级的数据,通过数据的大小来判断优先级,实现优先级队列其实有三种方式:
- 第一种:无序数组队列,这种在入队时的时间复杂度为O(1),但是出队时的时间复杂度是O(n);
- 第二种:有序数组队列,这种在入队时的时间复杂度为O(n),但是出队时的时间复杂度是O(1);
- 第三种:堆,最大堆或最小堆,入队出队都是O(logn),虽然相较于第一种第二种时会在入队或出队时较慢,但是当频繁进行操作,数据量大时,动态进行时,堆所需要的平均时间远远少于第一种和第二种
通过上面三种优先级队列的比较就体现出了堆的优势,所以称堆为优先级队列的代理者,另外需要意识到的是,堆有两种基本形态最大堆和最小堆,而不管是哪种形态其本质都是一棵树,而且是一棵完全二叉树,最大堆的性质是父节点的值一定大于字节点,最小堆则是父节点的值一定小于子节点,兄弟节点大小无所谓~~
以下演示的是一个最大堆的实现,对于最大堆的存储用的还是一个数组,只是这个数组中的元素有些讲究;元素的讲究主要体现在对这个堆数组插入(insert方法)元素和弹出根节点元素(popTheTop)的实现上:
最大堆
// ELE继承Comparable,方便比较
public class MaxHeap<ELE extends Comparable> implements Heap {
private ELE[] data;
private int size;
private int cap; // 容量
// 基本构造函数,默认最大堆中数据为空
public MaxHeap(int capacity) {
data = (ELE[])new Comparable[capacity+1];
size = 0;
cap = capacity;
}
// 将数组最大堆化的构造函数
public MaxHeap(ELE[] arr) {
int n = arr.length;
data = (ELE[])new Comparable[n+1];
size = n;
cap = n;
for(int i=0; i<n; i++) {
data[i+1] = arr[i];
}
// 最大堆化
for(int i=n/2; i>=1; i--)
spinDown(i);
}
// 插入元素
public void insertELE(ELE ele) {
// 元素个数已满则直接返回
if(size >= cap)
return;
data[++size] = ele;
spinUp(size);
}
// 弹出最大元素
public ELE popMax() {
ELE BigBrother = data[1];
// 最后的小弟与老大交换,老大已弹出,当容量减一,这个数据就被忽略了,所以无需清理掉这个数据
// 减一直接用后--进行
UtilsSet.swap(data,1,size--);
// 自上而下恢复最大堆
spinDown(1);
// 再见大兄弟
return BigBrother;
}
// 内部堆化工具 -- 插入数据后用,自底向上找到合适位置,传入的upward即为底
private void spinUp(int upward) {
while(upward/2 > 0 && data[upward].compareTo(data[upward/2]) > 0) {
UtilsSet.swap(data, upward, upward / 2);
upward /= 2;
}
}
// 内部堆化工具 -- 自顶向下找到合适位置,可用于弹出数据后,从底调上数据后用,也可用于对随机数组堆化,传入的superStar位置不同
private void spinDown(int superStar) {
// 记录临时小哥,这个临时小哥为目前自顶向下的临时老大
ELE tempBoss=data[superStar];
// 临时小哥相当老大必须比自己的左右小弟节点大,因可能没有左右小弟,所以作为循环介绍与否的判断
while(superStar*2 <= size) {
int bigGuy = whoIsTheBigGuy(superStar*2, superStar*2+1);
// 如果临时小哥是凭实力当的老大,别搞事情了,结束战斗
if(tempBoss.compareTo(data[bigGuy]) >= 0)
break;
// 如果临时小哥没两个小弟中更大的牛,则需要让位
// 临时小哥的位置可以更新,数据已经记在tempBoss中了,继续进行循环,直到临时小哥找到属于自己的小弟,然后再将tempBoss中的值赋值
data[superStar] = data[bigGuy];
superStar = bigGuy;
}
data[superStar] = tempBoss;
}
// 找到同老大的左右两个小弟节点中较大的序号,并返回
private int whoIsTheBigGuy(int left, int right) {
if(data[right] == null || data[left].compareTo(data[right]) >= 0)
return left;
else
return right;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
// 测试用
public static void main(String[] args) {
MaxHeap<Integer> maxHeap = new MaxHeap<Integer>(100);
int N = 100; // 堆中元素个数
int M = 100; // 堆中元素取值范围[0, M)
for( int i = 0 ; i < N ; i ++ )
maxHeap.insertELE( Integer.valueOf((int)(Math.random() * M)) );
Integer[] arr = new Integer[N];
// 将maxheap中的数据逐个取出来
// 取出来的顺序应该是按照从大到小的顺序取出来的
for( int i = 0 ; i < N ; i ++ ){
arr[i] = maxHeap.popMax();
System.out.print(arr[i] + " ");
}
System.out.println();
// 确保arr数组是从大到小排列的
for( int i = 1 ; i < N ; i ++ )
assert arr[i-1] >= arr[i];
}
}
最小堆的实现大同小异,需要注意的是这里的最大堆实现为了方便是从数组下标1开始,而不是0开始