目录
1.什么是堆
1.1 堆的相关概念
1.2 完全二叉树
![](https://i-blog.csdnimg.cn/blog_migrate/beaa95043860dcbe8bf567cfed1bf17a.png)
如上图,第h层所有节点连续集中在最左边
我们这里来实现一个最大堆(值越大优先级越高),最小堆也可参考本文实现
我们说堆是可以被看成一棵树的数组对象,也就是说,它的底层实现是数组,我们这里把元素依次排列成完全二叉树的形状,根节点是最大值,这就是一个最大堆,例 : 上图就是一个最大堆
1.3 根据堆的性质可以得出如下结论
2. 最大堆的建立与算法实现
2.1 底层逻辑实现(新建堆)
//最大堆实现
public class MaxHeap<T extends Comparable<T>> { //继承Comparable接口
private T[] array; //底层数组
private int size; //元素个数
public MaxHeap() { //无参构造
this.size = 0;
this.array = (T[]) new Comparable[20];
}
public MaxHeap(T[] array) { //有参构造
this.array = Arrays.copyOf(array, array.length); //复制数组
this.size = array.length;
}
}
这里我们先给出基础的成员变量和构造方法
2.2 常用工具方法实现
//判断是否为空
public boolean isEmpty() {
return this.size == 0;
}
//获取堆中有多少元素
public int getSize() {
return this.size;
}
//获取父节点索引
private int getParentIndex(int index) {
if (index < 0) {
throw new IllegalArgumentException("索引值错误");
} else if (index == 0) {
return -1;
} else {
return (index - 1) / 2;
}
}
//获取左孩子索引
private int getLeftChildIndex(int index) {
if (index < 0) {
throw new IllegalArgumentException("索引值错误");
} else {
return 2 * index + 1;
}
}
//交换
private void swap(int curIndex, int changeIndex) { //当前索引,变换的索引
T temp = this.array[curIndex];
this.array[curIndex] = this.array[changeIndex];
this.array[changeIndex] = temp;
}
@Override
public String toString() { //重写toString()方法
StringBuilder sb = new StringBuilder("[");
int i = 0;
while (i < size) {
sb.append(this.array[i]);
if (i != size - 1) {
sb.append(",");
}
i++;
}
sb.append("]");
return sb.toString();
}
我们的底层数据结构是数组,所以都是对索引进行操作
右孩子索引为 : 左孩子索引 + 1 上面方法也就不去实现这一步了
这里也是给出了我们一会儿要用到的工具方法
2.3 添加元素
这里添加元素的时候,我们需要在添加完毕做一个浮动操作
如上图,我们添加了一个 52 的结点, 是在数组的最末尾 , 但根据最大堆的性质 , 52 在上图中应该是比 41 大 ,比 62 小的 这样一个位置, 所以我们在添加完成后需要 为52 找到一个合适的位置 , 我们把这一操作 叫做上浮
过程 : 1、添加元素52,添加到数组中索引为size的位置,然后更新size
2、从最后一个结点开始与父亲结节进行(优先级)比较,如果父亲结点的优先级低于当前结点,则进行交换
3、重复第二步操作,
4、直至根节点或父亲结点的优先级高于当前结点
直到满足最大堆的性质,操作结束 ,代码实现 :
//添加元素
public void add(T ele) {
this.array[size] = ele; //添加元素
this.size++; //更新size
floatUp(size - 1); //调用浮动方法
}
//浮动操作
private void floatUp(int i) {
int curIndex = i; //当前操作的节点
int parentIndex = getParentIndex(curIndex); //得到父节点
//循环条件 : 当前节点大于0 并且 当前节点优先级大于它的父亲节点
while (curIndex > 0 && this.array[curIndex].compareTo(this.array[parentIndex]) >0){
swap(curIndex, parentIndex); //交换值
curIndex = parentIndex; //让当前节点指向父节点
parentIndex = getParentIndex(curIndex); //父节点指向当前节点的父节点
}
}
2.4 移除优先级最高的元素
![](https://i-blog.csdnimg.cn/blog_migrate/43ff981dc186787803468a8542317eaf.png)
我们可以看出, 如果删除62 ,我们肯定会用52 当作优先级最高的元素,但我们能去直接删除62吗,我们的底层是数组 , 要删除元素的话最好的选择是先保存然后覆盖这个值,所以我们这里可以用末尾的元素(图中16)去替换他 ,然后再把16去放到底下一个合适的位置,这个操作我们叫做下沉
过程 :
2、从索引为0的位置开始进行下沉操作
![](https://i-blog.csdnimg.cn/blog_migrate/b9eea4923a57bcf999fc7c56335222a6.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d69fba335f96752fc31abd9bfbc2a403.png)
代码实现如下 :
//移除优先级最高的元素
public T removePriorityFirst() {
if (isEmpty()) { //为空
throw new IllegalArgumentException("堆为null");
}
T result = this.array[0]; //保存根元素
this.array[0] = this.array[this.size - 1]; //让数组末尾元素覆盖数组索引为0的元素
this.size--; //更新size
swim(); //根下沉
return result;
}
//下沉操作
private void swim() {
if (isEmpty()) {
return;
}
int curIndex = 0; //当前节点
int leftIndex = getLeftChildIndex(curIndex); //获取左孩子结点索引
int changeIndex = leftIndex; //先默认左边优先级最大
//循环的条件 : 有左右孩子
while (leftIndex < this.size) {
//如果右孩子结点优先级高,让changeIndex指向右孩子结点
if (leftIndex + 1 < this.size && this.array[leftIndex].compareTo(this.array[leftIndex + 1]) < 0) {
changeIndex = leftIndex + 1;
}
//找到了合适的位置,跳出循环
if (this.array[curIndex].compareTo(this.array[changeIndex]) > 0) {
break;
}
swap(curIndex, changeIndex); //交换
curIndex = changeIndex; //当前节点指向优先级高的结点
leftIndex = getLeftChildIndex(curIndex); //获取左孩子结点索引
changeIndex = leftIndex; //默认左孩子优先级高
}
}
至此,操作结束
2.5 replace操作(取出最大元素,放入一个新元素)
直接看代码实现,这里我们只需要调我们的swim方法即可
//替换操作:取出优先级最高的元素,将其替换为一个新元素
public void replace(T ele) {
this.array[0] = ele;
swim(); //调我们的下沉方法即可
}
2.6 时间复杂度分析
3. 关于堆的其他算法实现
3.1 heapify操作(将任意数组堆化)
![](https://i-blog.csdnimg.cn/blog_migrate/1a200b422969bebafe6ef319678272c0.png)
思路: 我们从最后一个节点的父亲节点往前依次调整,每次都同自己的孩子结点比较做一个下沉操作 ,直到调整到根节点结束,调整顺序如下
根据索引顺序依次向前找,每次都同自己的孩子结点做swim操作
//堆化操作:给定一个数组,将其整理成堆
public void heapify() { //时间复杂度为o(n)
if (this.array == null || this.array.length == 0) {
return;
}
int lastParentIndex = (this.array.length - 1 - 1) / 2; //获取最后一个结点的父亲结点的索引
//循环到根节点
for (; lastParentIndex >= 0; lastParentIndex--) {
heapifySwim(this.array, lastParentIndex, this.array.length); //每次做一个swim操作
}
}
//heapify中的swim操作
private void heapifySwim(T[] array, int lastParentIndex, int length) {
//array:原数组 lastParentIndex:最后一个结点的父亲结点的索引 length:原数组长度
if (isEmpty()) {
return;
}
int curIndex = lastParentIndex;
int leftIndex = getLeftChildIndex(curIndex);
int changeIndex = leftIndex;
while (leftIndex < length) {
if (leftIndex + 1 < length && array[leftIndex].compareTo(array[leftIndex + 1]) < 0) {
changeIndex = leftIndex + 1;
}
if (array[curIndex].compareTo(array[changeIndex]) > 0) {
break;
}
swap(curIndex, changeIndex);
curIndex = changeIndex;
leftIndex = getLeftChildIndex(curIndex);
changeIndex = leftIndex;
}
}
至此操作结束
3.2 堆排序
要求:先把数组进行heapify操作,整理成堆
代码实现 :
这里我们直接调用我们写好的方法就行
//堆排序
public void sort(T[] array) {
if (array == null || array.length == 0) {
return;
}
//Arrays.stream(array).forEach(this::add);
this.heapify(); //堆化
int index = 0;
while (!isEmpty()) {
array[index++] = this.removePriorityFirst(); //排序
}
}
至此操作完成
4 . 测试
我们用main方法来测试刚才的算法
4.1 测试添加和移除优先级最高的元素
添加:
Integer[] arr = new Integer[]{ 62, 30, 41, 28, 13, 22, 19, 15, 17, 16};
MaxHeap<Integer> heap = new MaxHeap<>(); //传入arr数组
for (Integer i : arr) {
heap.add(i); //添加
}
System.out.println(heap);
随便给一个数组,然后遍历依次添加元素,输出这个堆,得到结果如下 :
我们来画图看他是不是一个最大堆
可见,测试通过
移除优先级最高的元素 :
System.out.println(heap.removePriorityFirst());
System.out.println(heap);
结果 :
画图验证 :
测试通过
4.2 测试heapify和堆排序
Integer[] arr1 = new Integer[]{ 30, 62, 41, 28, 13, 22, 19, 15, 17, 16};
MaxHeap<Integer> heap1 = new MaxHeap<>(arr1);
heap1.heapify(); //堆化
System.out.print("堆化 : ");
System.out.println(heap1);
heap1.sort(arr1); //排序
System.out.print("堆排序 : ");
System.out.println(Arrays.toString(arr1));
结果 :
测试通过
5.总结
堆是一种很重要的数据结构,这里介绍了最大堆的一些常见的算法实现,另外后续会在介绍优先队列里使用堆作为我们的底层数据结构,后面再来介绍,感谢阅读