堆结构
堆结构就是用数组实现的特殊的完全二叉树结构:
- 有右子节点时,必有左子节点
- 每个父节点都大于等于子节点(或者每个父节点都小于等于子节点)
堆的类型有两种:
堆中的方法:
堆结构优点(以下解释以大根堆为例):
下面用代码示例来演示堆结构:
package com.leftGod;
/**
* 基础堆结构
*
* 基础:
* 求父节点下标:(i-1)/2
* 求左孩子节点下标:i*2+1
* 求右孩子节点下标:i*2+2
*
* @author zhao.hualuo
* Create at 2022/4/2
*/
public class BaseHeap {
/** 堆当前使用量 */
private static int heapSize = 0;
/** 堆数组,用来存放当前元素 */
private static int[] heapArr = new int[4];
/**
* 添加新元素
*
* @param newValue 新元素
*/
public static void add(int newValue) {
//将这个新元素放到堆的最后一个位置
heapArr[heapSize] = newValue;
//将现在的数组重新转化成堆
heapInsert(heapSize);
//堆长度+1
heapSize++;
//判断堆是否需要扩容
expansion();
}
/**
* 从数组中根据下标移除元素
*
* @param index 要移除的元素下标
* @return 移除的元素值
*/
public static int remove(int index) {
int returnValue = heapArr[index];
//将最后一个位置的元素交换到要移除的这个元素位置上,堆长度减1
swap(index, --heapSize);
//1、看看这个元素是不是比父元素要大。若是,需要上移
heapInsert(index);
//2、看看这个元素是不是比子元素要小。若是,需要下沉
heapify(index);
return returnValue;
}
/**
* heapInsert
* 元素向上移动,最终形成堆
*
* @param index 下标
*/
private static void heapInsert(int index) {
while (index>=0 && heapArr[index] > heapArr[(index-1)/2]) {
swap(index, (index-1)/2);
index = (index-1)/2;
}
}
/**
* heapify
* 元素向下移动,重新形成堆
*
* @param index 要移除的元素下标
* @return 移除的元素值
*/
private static void heapify(int index) {
int left = index*2+1;
while (left < heapSize) {
//看看两个子元素那个大,下一步要和大的进行比较替换
int maxSonIndex = left+1<heapSize && heapArr[left+1] >heapArr[left] ? left+1 : left;
//当前元素和较大的那个儿子作比较,若父亲比两个儿子都大,匹配成功,循环结束
if (heapArr[index] >= heapArr[maxSonIndex]) {
break;
}
//走到这一步,说明儿子比老子大,老子和儿子要交换位置
swap(index, maxSonIndex);
//准备下一次循环
index = maxSonIndex;
left = left*2+1;
}
}
/**
* 堆自动扩容机制
*/
private static void expansion() {
//判断是否需要扩容
if (heapSize < heapArr.length) {
return;
}
//创建新数组,并复制
int[] newHeapArr = new int[heapArr.length*2];
System.arraycopy(heapArr, 0, newHeapArr, 0, heapArr.length);
heapArr = newHeapArr;
System.out.println("触发扩容,新堆长度:" + heapArr.length);
}
/**
* 将两个元素交换位置
*
* @param originIndex 原位置
* @param targetIndex 目标位置
*/
private static void swap(int originIndex, int targetIndex) {
if (originIndex == targetIndex) {
return;
}
heapArr[originIndex] = heapArr[originIndex] ^ heapArr[targetIndex];
heapArr[targetIndex] = heapArr[originIndex] ^ heapArr[targetIndex];
heapArr[originIndex] = heapArr[originIndex] ^ heapArr[targetIndex];
}
public static void main(String[] args) {
//测试heapInsert
add(15);
add(40);
add(9);
add(8);
add(32);
add(39);
add(35);
System.out.println("\n当前堆大小:" + heapArr.length + ",已占用堆空间:" + heapSize);
System.out.print("heapInsert结果:");
for (int i = 0; i < heapSize; i++) {
System.out.print(heapArr[i] + ", ");
}
System.out.println();
//[40, 32, 39, 8, 15, 9, 35, 0]
//测试heapify
//应该将8删除,用最后一个元素35来替换,35会和父亲32做一次heapInsert
remove(3);
System.out.println("\n当前堆大小:" + heapArr.length + ",已占用堆空间:" + heapSize);
System.out.print("heapify结果:");
for (int i = 0; i < heapSize; i++) {
System.out.print(heapArr[i] + ", ");
}
//[40, 35, 39, 32, 15, 9, 8, 0]
System.out.println();
}
}
上面示例的运行结果如下:
"C:\Program Files\Java\jdk1.8.0_212\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2.2\lib\idea_rt.jar=9226:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_212\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\rt.jar;D:\CodeWorkspace\PersonCode\demo\target\classes;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter\2.4.3\spring-boot-starter-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot\2.4.3\spring-boot-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-context\5.3.4\spring-context-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-aop\5.3.4\spring-aop-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-beans\5.3.4\spring-beans-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-expression\5.3.4\spring-expression-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.4.3\spring-boot-autoconfigure-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter-logging\2.4.3\spring-boot-starter-logging-2.4.3.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;C:\Users\Administrator\.m2\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;C:\Users\Administrator\.m2\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-core\5.3.4\spring-core-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-jcl\5.3.4\spring-jcl-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\yaml\snakeyaml\1.27\snakeyaml-1.27.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-http\4.1.0.5\gexin-rp-sdk-http-4.1.0.5.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-template\4.0.0.24\gexin-rp-sdk-template-4.0.0.24.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-base\4.0.0.30\gexin-rp-sdk-base-4.0.0.30.jar;C:\Users\Administrator\.m2\repository\com\google\protobuf\protobuf-java\2.5.0\protobuf-java-2.5.0.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-fastjson\1.0.0.3\gexin-rp-fastjson-1.0.0.3.jar;C:\Users\Administrator\.m2\repository\com\getui\push\restful-sdk\1.0.0.1\restful-sdk-1.0.0.1.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;C:\Users\Administrator\.m2\repository\org\apache\httpcomponents\httpclient\4.5.13\httpclient-4.5.13.jar;C:\Users\Administrator\.m2\repository\org\apache\httpcomponents\httpcore\4.4.14\httpcore-4.4.14.jar;C:\Users\Administrator\.m2\repository\commons-codec\commons-codec\1.15\commons-codec-1.15.jar;C:\Users\Administrator\.m2\repository\com\google\code\gson\gson\2.8.6\gson-2.8.6.jar" com.leftGod.BaseHeap
触发扩容,新堆长度:8
当前堆大小:8,已占用堆空间:7
heapInsert结果:40, 32, 39, 8, 15, 9, 35,
当前堆大小:8,已占用堆空间:6
heapify结果:40, 35, 39, 32, 15, 9,
Process finished with exit code 0
上面介绍的是堆的概念,并且自己动手实现了一个堆的示例。
但是大多数情况下并不需要自己实现堆结构,因为Jdk1.5之后已经帮我们实现了堆,就是PriorityQueue
平时我们可以直接使用Java中实现好的堆,下面使用代码来演示一下:
package com.leftGod;
import java.util.Comparator;
import java.util.PriorityQueue;
/**
* 测试Java中的堆
*
* PriorityQueue支持通过poll移除第一个元素,也支持根据元素值移除元素,
* 但是不支持根据下标移除(PriorityQueue有removeAt方法,但是是private修饰的)
* 或者如果我们需要根据下标更新元素值时,PriorityQueue就不支持,就需要我们使用我们自定义的堆结构
*
* @author zhao.hualuo
* Create at 2022/4/3
*/
public class JavaHeapTest {
public static void main(String[] args) {
//PriorityQueue默认是小根堆
PriorityQueue<Integer> minPriorityQueue = new PriorityQueue<Integer>();
minPriorityQueue.add(15);
minPriorityQueue.add(40);
minPriorityQueue.add(9);
minPriorityQueue.add(8);
minPriorityQueue.add(32);
minPriorityQueue.add(39);
minPriorityQueue.add(35);
System.out.println("小根堆:" + minPriorityQueue);
//若想生成大根堆,需要手动实现比较器
MyCustomComparator comparator = new MyCustomComparator();
PriorityQueue<Integer> maxPriorityQueue = new PriorityQueue<Integer>(comparator);
maxPriorityQueue.add(15);
maxPriorityQueue.add(40);
maxPriorityQueue.add(9);
maxPriorityQueue.add(8);
maxPriorityQueue.add(32);
maxPriorityQueue.add(39);
maxPriorityQueue.add(35);
System.out.println("大根堆:" + maxPriorityQueue);
//移除元素
maxPriorityQueue.remove(8);
System.out.println("移除元素8后的大根堆:" + maxPriorityQueue);
//poll默认移除第一个元素
Integer poll = maxPriorityQueue.poll();
System.out.println("poll移除的元素是:" + poll);
System.out.println("移除元素后的大根堆:" + maxPriorityQueue);
}
//我的自定义类加载器
public static class MyCustomComparator implements Comparator {
/**
* 比较规则
*
* @param o1 第一个参数
* @param o2 第二个参数
* @return 若返回负数,o1在前面; 若返回正数,o2在前面
*/
@Override
public int compare(Object o1, Object o2) {
//若o2-o1<0 说明02小,o1应该在前面
return (int)o2 - (int)o1;
}
}
}
上面代码的运行结果如下:
"C:\Program Files\Java\jdk1.8.0_212\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2.2\lib\idea_rt.jar=11054:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_212\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\rt.jar;D:\CodeWorkspace\PersonCode\demo\target\classes;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter\2.4.3\spring-boot-starter-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot\2.4.3\spring-boot-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-context\5.3.4\spring-context-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-aop\5.3.4\spring-aop-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-beans\5.3.4\spring-beans-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-expression\5.3.4\spring-expression-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.4.3\spring-boot-autoconfigure-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter-logging\2.4.3\spring-boot-starter-logging-2.4.3.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;C:\Users\Administrator\.m2\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;C:\Users\Administrator\.m2\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-core\5.3.4\spring-core-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-jcl\5.3.4\spring-jcl-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\yaml\snakeyaml\1.27\snakeyaml-1.27.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-http\4.1.0.5\gexin-rp-sdk-http-4.1.0.5.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-template\4.0.0.24\gexin-rp-sdk-template-4.0.0.24.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-base\4.0.0.30\gexin-rp-sdk-base-4.0.0.30.jar;C:\Users\Administrator\.m2\repository\com\google\protobuf\protobuf-java\2.5.0\protobuf-java-2.5.0.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-fastjson\1.0.0.3\gexin-rp-fastjson-1.0.0.3.jar;C:\Users\Administrator\.m2\repository\com\getui\push\restful-sdk\1.0.0.1\restful-sdk-1.0.0.1.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;C:\Users\Administrator\.m2\repository\org\apache\httpcomponents\httpclient\4.5.13\httpclient-4.5.13.jar;C:\Users\Administrator\.m2\repository\org\apache\httpcomponents\httpcore\4.4.14\httpcore-4.4.14.jar;C:\Users\Administrator\.m2\repository\commons-codec\commons-codec\1.15\commons-codec-1.15.jar;C:\Users\Administrator\.m2\repository\com\google\code\gson\gson\2.8.6\gson-2.8.6.jar" com.leftGod.JavaHeapTest
小根堆:[8, 9, 15, 40, 32, 39, 35]
大根堆:[40, 32, 39, 8, 15, 9, 35]
移除元素8后的大根堆:[40, 35, 39, 32, 15, 9]
poll移除的元素是:40
移除元素后的大根堆:[39, 35, 9, 32, 15]
Process finished with exit code 0