堆
堆是一种特别的二叉树,满足以下条件的二叉树,可以称之为堆:
- 完全二叉树;
- 每一个节点的值都必须 大于等于或者小于等于 其孩子节点的值。
堆具有以下的特点:
- 可以在 O(logN) 的时间复杂度内向堆中插入元素;
- 可以在 O(logN) 的时间复杂度内向堆中删除元素;
- 可以在 O(1)的时间复杂度内获取堆中的最大值或最小值。
堆的分类
堆有两种类型:最大堆和最小堆。
- 最大堆:堆中每一个节点的值都大于等于其孩子节点的值。所以最大堆的特性是 堆顶元素(根节点)是堆中的最大值。
- 最小堆:堆中每一个节点的值都小于等于其孩子节点的值。所以最小堆的特性是 堆顶元素(根节点)是堆中的最小值。
PriorityQueue
在Java中的堆可以使用 java.util
包下的优先队列 PriorityQueue
类来表示。
//创建一个空的最小堆
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
//创建一个空的最大堆
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
// 创建带初始值的「堆」, 或者称为「堆化」操作
PriorityQueue<Integer> heapWithValues= new PriorityQueue<>(Arrays.asList(3,1,2));
//插入元素
minHeap.add(1);
maxHeap.add(1);
//获取堆顶元素
minHeap.peek();//最小值
maxHeap.peek();//最大值
//删除堆顶元素
minHeap.poll();
maxHeap.poll();
//获取堆的长度
minHeap.size();
maxHeap.size();
//判断堆是否存在元素,若存在返回false,不存在返回true
minHeap.isEmpty();
maxHeap.isEmpty();
基本用法
public class Demo_01_PriorityQueue {
public static void main(String[] args) {
//堆化[5,7,8]
PriorityQueue<Integer> minHeap = new PriorityQueue<>((a, b) -> b - a);
minHeap.add(8);
minHeap.add(7);
minHeap.add(1);
minHeap.add(3);
minHeap.add(9);
// 添加元素,add方法是通过调用offer方法添加元素的
System.out.println(minHeap.offer(2));
System.out.println("size:" + minHeap.size());
System.out.println("toString:" + minHeap.toString());
// 判断堆是否为空
System.out.println("isEmpty:" + minHeap.size());
// 获取堆顶元素
System.out.println("peek:" + minHeap.peek());
// 删除元素
System.out.println("poll:" + minHeap.poll());
// 获取堆的比较器对象
System.out.println(minHeap.comparator());
// 查找堆中是否存在元素 6
System.out.println("contains:" + minHeap.contains(6));
// 将堆转换成一个对象数组
Object[] array = minHeap.toArray();
System.out.println(Arrays.toString(array));
}
}
时间复杂度和空间复杂度分析
堆的用法 | 时间复杂度 | 空间复杂度 |
---|---|---|
创建堆 | O(N) | O(N) |
插入元素 | O(logN) | O(1) |
获取堆顶元素 | O(1) | O(1) |
删除堆顶元素 | O(logN) | O(1) |
获取堆的长度 | O(1) | O(1) |
自定义堆
初始化堆
// 数组创建完全二叉树的结构
private int[] maxHeap;
// 数组总大小
private int size;
// 记录堆的元素个数
private int count;
// 默认堆大小
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 默认初始化堆大小
public MaxHeap() {
this(DEFAULT_INITIAL_CAPACITY);
}
// 初始化
public MaxHeap(int size) {
this.size = size;
this.maxHeap = new int[size + 1];
// 第1个元素不使用,随便赋值
maxHeap[0] = -1;
}
/**
* 交换值
*
* @param arr 堆
* @param a a元素的索引
* @param b b元素的索引
*/
private void swap(int[] arr, int a, int b) {
int t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
插入元素
/**
* 插入元素
*
* @param e 元素
* @return 成功返回true,失败返回false
*/
public boolean add(int e) {
return offer(e);
}
public boolean offer(int e) {
// 堆元素加1
count++;
// 堆中元素的个数大于初始化数组的个数
if (count > size) {
count--;
return false;
}
// 添加元素
maxHeap[count] = e;
// 新增元素的索引位置
int index = count;
/**
* 注意,若用数组表示完全二叉树,并且根结点存储在数组的【索引1】的位置的时
* 任何一个节点的父节点索引位置为【该节点的索引位置/2】,任何一个节点的左孩
* 子节点的索引位置为【该节点的索引位置*2】,任何一个节点的右孩子节点的索引
* 位置为【该节点的索引位置*2+1】;
*/
// 新增元素的父节点的索引位置
int parent = index >>> 1;
// 当添加的元素大于父节点时,将父节点的值和新增元素的值交换
while (index > 1 && maxHeap[index] > maxHeap[parent]) {
// 交换
swap(maxHeap, index, parent);
// 元素上浮
index = parent;
parent = index >>> 1;
}
return true;
}
获取堆顶元素
/**
* 获取堆顶元素
*
* @return 返回堆顶元素
*/
public int peek() {
return maxHeap[1];
}
删除堆顶元素
/**
* 删除堆顶元素
*
* @return 返回被删除的元素
*/
public int poll() {
// 返回结果
int result = 0x80000000;
// 堆个数为0
if (count <= 0) {
return result;
} else { // 堆个数至少为1
result = maxHeap[1];
// 将堆中最后一个元素替换到堆顶
maxHeap[1] = maxHeap[count];
// 堆中元素减1
count--;
// 被删除元素索引
int index = 1;
// 若删除的节点是根节点
while (index < count && index <= (count >>> 1)) {
// 被删除节点的左节点和右节点
int left = 2 * index, right = 2 * index + 1;
// 当删除节点的元素小于,左节点或右节点,表示该元素值小,此时需要将该元素与左、右孩子节点中最大的进行交换
if (maxHeap[index] < maxHeap[left] || maxHeap[index] < maxHeap[right]) {
// 与左、右节点中最大的进行交换
if (maxHeap[left] > maxHeap[right]) {
swap(maxHeap, index, left);
// 左元素下沉,记录下沉后索引的位置
index = left;
} else {
swap(maxHeap, index, right);
// 右元素下沉,同上
index = right;
}
} else {
break;
}
}
}
return result;
}
堆的大小
/**
* 获取堆的大小
*
* @return 返回堆中元素个数
*/
public int size() {
return count;
}
/**
* 判断堆是否为空
*
* @return 空返回true,非空返回false
*/
public boolean isEmpty() {
return count == 0;
}
最大堆完整代码
/**
* Author : AiTao
* Create : 2021/4/1 22:20
* Description : 最大堆类
*/
public class MaxHeap {
// 数组创建完全二叉树的结构
private int[] maxHeap;
// 数组总大小
private int size;
// 记录堆的元素个数
private int count;
// 默认堆大小
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 默认初始化堆大小
public MaxHeap() {
this(DEFAULT_INITIAL_CAPACITY);
}
// 初始化
public MaxHeap(int size) {
this.size = size;
this.maxHeap = new int[size + 1];
// 第1个元素不使用,随便赋值
maxHeap[0] = 0x80000000;
}
/**
* 插入元素
*
* @param e 元素
* @return 成功返回true,失败返回false
*/
public boolean add(int e) {
return offer(e);
}
public boolean offer(int e) {
// 堆元素加1
count++;
// 堆中元素的个数大于初始化数组的个数
if (count > size) {
count--;
return false;
}
// 添加元素
maxHeap[count] = e;
// 新增元素的索引位置
int index = count;
/**
* 注意,如果用数组表示完全二叉树,并且根结点存储在数组的[索引1]的位置的时候,
* 任何一个节点的父节点索引位置为「该节点的索引位置/2」,任何一个节点的左孩
* 子节点的索引位置为「该节点的索引位置*2」,任何一个节点的右孩子节点的索引
* 位置为「该节点的索引位置*2+1」
*/
// 新增元素的父节点的索引位置
int parent = index >>> 1;
// 当添加的元素大于父节点时,将父节点的值和新增元素的值交换
while (index > 1 && maxHeap[index] > maxHeap[parent]) {
// 交换
swap(maxHeap, index, parent);
// 元素上浮
index = parent;
parent = index >>> 1;
}
return true;
}
/**
* 获取堆顶元素
*
* @return 返回堆顶元素
*/
public int peek() {
return maxHeap[1];
}
/**
* 删除堆顶元素
*
* @return 返回被删除的元素
*/
public int poll() {
// 返回结果
int result = 0x80000000;
// 堆个数为0
if (count <= 0) {
return result;
} else { // 堆个数至少为1
result = maxHeap[1];
// 将堆中最后一个元素替换到堆顶
maxHeap[1] = maxHeap[count];
// 堆中元素减1
count--;
// 被删除元素索引
int index = 1;
// 若删除的节点是根节点
while (index < count && index <= (count >>> 1)) {
// 被删除节点的左节点和右节点
int left = 2 * index, right = 2 * index + 1;
// 当删除节点的元素小于,左节点或右节点,表示该元素值小,此时需要将该元素与左、右孩子节点中最大的进行交换
if (maxHeap[index] < maxHeap[left] || maxHeap[index] < maxHeap[right]) {
// 与左、右节点中最大的进行交换
if (maxHeap[left] > maxHeap[right]) {
swap(maxHeap, index, left);
// 左元素下沉,记录下沉后索引的位置
index = left;
} else {
swap(maxHeap, index, right);
// 右元素下沉,同上
index = right;
}
} else {
break;
}
}
}
return result;
}
/**
* 获取堆的大小
*
* @return 返回堆中元素个数
*/
public int size() {
return count;
}
/**
* 判断堆是否为空
*
* @return 空返回true,非空返回false
*/
public boolean isEmpty() {
return count == 0;
}
/**
* 重写toString
*/
public String toString() {
if (count == 0) {
return "堆为空!";
} else {
// 打印 [5,4,3,2,1]
StringBuilder sb = new StringBuilder();
sb.append('[');
for (int i = 1; i <= count; i++) {
sb.append(maxHeap[i]);
if (i != count) {
sb.append(',');
}
}
sb.append(']');
return sb.toString();
}
}
/**
* 交换值
*
* @param arr 堆
* @param a a元素的索引
* @param b b元素的索引
*/
private void swap(int[] arr, int a, int b) {
int t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
}
最小堆完整代码
/**
* Author : AiTao
* Create : 2021/4/1 22:21
* Description : 最小堆类
*/
public class MinHeap {
// 数组创建完全二叉树的结构
private int[] minHeap;
// 数组总大小
private int size;
// 记录堆的元素个数
private int count;
// 默认堆大小
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 默认初始化堆大小
public MinHeap() {
this(DEFAULT_INITIAL_CAPACITY);
}
// 初始化
public MinHeap(int size) {
this.size = size;
this.minHeap = new int[size + 1];
// 第1个元素不使用,随便赋值
minHeap[0] = 0x7fffffff;
}
/**
* 插入元素
*
* @param e 元素
* @return 成功返回true,失败返回false
*/
public boolean add(int e) {
return offer(e);
}
public boolean offer(int e) {
// 堆元素加1
count++;
// 堆中元素的个数大于初始化数组的个数
if (count > size) {
count--;
return false;
}
// 添加元素
minHeap[count] = e;
// 新增元素的索引位置
int index = count;
/**
* 注意,如果用数组表示完全二叉树,并且根结点存储在数组的[索引1]的位置的时候,
* 任何一个节点的父节点索引位置为「该节点的索引位置/2」,任何一个节点的左孩
* 子节点的索引位置为「该节点的索引位置*2」,任何一个节点的右孩子节点的索引
* 位置为「该节点的索引位置*2+1」
*/
// 新增元素的父节点的索引位置
int parent = index >>> 1;
// 当添加的元素大于父节点时,将父节点的值和新增元素的值交换
while (index > 1 && minHeap[index] < minHeap[parent]) {
// 交换
swap(minHeap, index, parent);
// 元素上浮
index = parent;
parent = index >>> 1;
}
return true;
}
/**
* 获取堆顶元素
*
* @return 返回堆顶元素
*/
public int peek() {
return minHeap[1];
}
/**
* 删除堆顶元素
*
* @return 返回被删除的元素
*/
public int poll() {
// 返回结果
int result = 0x7fffffff;
// 堆个数为0
if (count <= 0) {
return result;
} else { // 堆个数至少为1
result = minHeap[1];
// 将堆中最后一个元素替换到堆顶
minHeap[1] = minHeap[count];
// 堆中元素减1
count--;
// 被删除元素索引
int index = 1;
// 若删除的节点是根节点
while (index < count && index <= (count >>> 1)) {
// 被删除节点的左节点和右节点
int left = 2 * index, right = 2 * index + 1;
// 当删除节点的元素小于,左节点或右节点,表示该元素值大,此时需要将该元素与左、右孩子节点中最小的进行交换
if (minHeap[index] > minHeap[left] || minHeap[index] > minHeap[right]) {
// 与左、右节点中最大的进行交换
if (minHeap[left] > minHeap[right]) {
swap(minHeap, index, left);
// 左元素下沉,记录下沉后索引的位置
index = left;
} else {
swap(minHeap, index, right);
// 右元素下沉,同上
index = right;
}
} else {
break;
}
}
}
return result;
}
/**
* 获取堆的大小
*
* @return 返回堆中元素个数
*/
public int size() {
return count;
}
/**
* 判断堆是否为空
*
* @return 空返回true,非空返回false
*/
public boolean isEmpty() {
return count == 0;
}
/**
* 重写toString
*/
public String toString() {
if (count == 0) {
return "堆为空!";
} else {
// 打印 [5,4,3,2,1]
StringBuilder sb = new StringBuilder();
sb.append('[');
for (int i = 1; i <= count; i++) {
sb.append(minHeap[i]);
if (i != count) {
sb.append(',');
}
}
sb.append(']');
return sb.toString();
}
}
/**
* 交换值
*
* @param arr 堆
* @param a a元素的索引
* @param b b元素的索引
*/
private void swap(int[] arr, int a, int b) {
int t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
}