Java手写堆
小根堆介绍
这里以小根堆为例
最大堆和最小堆是二叉堆的两种形式。
小根堆:根结点的键值是所有堆结点键值中最小者,且每个结点的值都比其孩子的值小。
大根堆:根结点的键值是所有堆结点键值中最大者,且每个结点的值都比其孩子的值大。
堆的父元素与子元素对应数组的索引有如下关系
设根节点索引从1开始
- 当父节点索引为n时,左孩子节点2n , 右孩子索引2n+1。
- 当前节点为 n, 那么它的父节点为n/2。
代码实现
自定义小根堆类
public class MyHead {
private int[] headArr; // 存放数据的数组 (0下标不存值)
private int headSize=0; // 堆的大小
private int maxSize; // 数组最大值
// 构造函数
public MyHead(int maxSize) {
this.maxSize = maxSize;
this.headArr = new int[maxSize];
}
}
数组元素交换位置方法(便于交换)
// 两个数组元素进行交换
void swap(int[] arr, int index1, int index2) {
int temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
}
添加元素
// 添加元素
void put(int num) {
if (headSize == maxSize-1) {
System.out.println("堆已满!");
return;
}
/**
* 1、堆的大小+1 ++headSize
* 2、将插入的数放入堆尾 headArr[headSize] = num
*/
headArr[++headSize] = num;
// 操作数刚开始的下标是堆尾元素下标 curIndex = headSize
int curIndex = headSize;
// 重新调整为小根堆 (shiftUp)
shiftUp(curIndex);
}
// 插入堆时 上移(若当前状态不满足小根堆)
private void shiftUp(int curIndex) {
while (curIndex > 1) { // 判断不是根节点
// 父节点为 当前节点/2 curIndex>>1 等同于 curIndex/2
int fatherIndex = curIndex>>1;
if (headArr[fatherIndex] > headArr[curIndex]) { // 如果当前节点比父节点还小 交换
swap(headArr, curIndex, fatherIndex);
// 交换之后 当前节点的下标也换了
curIndex = fatherIndex;
}else break;
}
}
获取根节点元素,并删除
// 获取最小值元素 并移除
int pop() {
if (headSize == 0) {
System.out.println("堆已空!");
return -1;
}
// 获取最小值元素(根节点既为最小值)
int minNum = headArr[1];
/**
* 1、将堆尾部元素覆盖给arr[1]位置 headArr[1] = headArr[headSize]
* 2、将堆的大小减1 headSize--
*/
headArr[1] = headArr[headSize--];
// 因为队尾元素值已经覆盖到根节点位置 接下来的操作数便是这个值
int curIndex = 1;
// 重新调整为小根堆 (shiftDown)
shiftDown(1);
return minNum;
}
// 堆删除根节点元素时 下移(若当前状态不满足小根堆)
private void shiftDown(int curIndex) {
while (curIndex*2 <= headSize) { // 存在左孩子(存在子节点)
int leftSonIndex = 2*curIndex;
// 最小子节点下标默认值: 左孩子下标
int minSonIndex = leftSonIndex;
int rightSonIndex = leftSonIndex+1;
/**
* 1、这个判断的是存在右孩子 rightSonIndex<=headSize
* 2、判断如果右孩子比左孩子小 headArr[leftSonIndex]>headArr[rightSonIndex]
* 3、如果右孩子比左孩子还小,最小孩子就是右孩子 minSonIndex = rightSonIndex
*/
if (rightSonIndex<=headSize && headArr[leftSonIndex]>headArr[rightSonIndex]) minSonIndex = rightSonIndex;
// 如果当前节点比最小孩子节点大(不满足小根堆) 则节点交换位置
if (headArr[curIndex] > headArr[minSonIndex]) { // 如果最小孩子比当前节点小
swap(headArr, curIndex, minSonIndex);
// 交换之后 当前节点的下标也换了
curIndex = minSonIndex;
}else break;
}
}
主方法测试
public static void main(String[] args) {
MyHead myHead = new MyHead(20);
for (int i=0; i<10; i++) {
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
myHead.put(num);
for (int j=1; j<=myHead.headSize; j++) {
System.out.print(myHead.headArr[j]+" ");
}
System.out.println();
}
for (int i=0; i<10; i++) {
int minNum = myHead.pop();
System.out.println("最小值为:"+minNum);
for (int j=1; j<=myHead.headSize; j++) {
System.out.print(myHead.headArr[j]+" ");
}
System.out.println();
}
}
Java中提供的优先队列
Java中的堆是用优先队列PriorityQueue实现的。默认创建一个小根堆,可以接受一个Comparator比较器,来创建最大堆。由于Comparator是一个函数接口,这里我们可以直接传入一个lambda表达式就能够自动创建Comparator对象。
Queue<Integer> minheap =new PriorityQueue<Integer>();//默认为最小堆
// 大根堆 (这种内部类的方式 比 lamda表达式的效率要高)
PriorityQueue<Integer> maxHead = new PriorityQueue<Integer>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});