什么是最小堆
对于任意一个节点的值,它的子节点的值都不小于它的值。也就是说,最小堆的根节点是整个堆中最小的元素。在最小堆中,每个节点的值都小于等于其子节点的值。
应用场景
最小堆的时间复杂度为 O(log n),其中 n 是堆中元素的个数。最小堆的应用场景非常广泛,比如优先队列、堆排序、最小生成树算法和最短路径算法等。
操作
插入
- 将新元素插入堆的最后位置;
- 如果新元素父节点的值大于新元素的值,则将它们交换,直到满足最小堆的性质。
graph TD
将新元素添加到堆的最后一个位置 --> 新元素的下标
新元素的下标 --> 如果新元素小于它的父节点
如果新元素小于它的父节点 --> 交换新元素和它的父节点
交换新元素和它的父节点 --> 将新元素的下标更新为其父节点的下标
对于索引为index的结点,其父结点的索引为(index - 1)/2。
/**
* 插入
* @param nums
* @param val 待插入的值
*/
public static void insert(int[] nums, int val) {
int n = nums.length - 1;
//将要插入的值作为数组的最后一个元素
nums[n] = val;
//当前节点索引
int curIndex = n;
//当前节点父节点索引
int parentIndex = (curIndex - 1) / 2;
//当前节点小于父节点
while (curIndex > 0 && nums[curIndex] < nums[parentIndex]) {
//交换当前节点和父节点
swap(nums, curIndex, parentIndex);
//更新当前索引为父节点索引
curIndex = parentIndex;
//更新父节点索引
parentIndex = (curIndex - 1) / 2;
}
}
删除
- 将根节点删除,并将堆的最后一个元素移动到根节点的位置;
- 如果新的根节点的值大于它的子节点的值,则将它与其中最小的子节点交换,直到满足最小堆的性质。
graph TD
堆大小 --> 堆内元素大于等于1
堆内元素大于等于1 --> 保存堆顶元素 --> 将堆的最后一个元素移动到根节点的位置
将堆的最后一个元素移动到根节点的位置 --> 默认左节点的值比较小为small
默认左节点的值比较小为small --> 如果右节点小于small --> 将右结点更新为small
默认左节点的值比较小为small --> 如果small大于当前节点 --> 将左节点与当前节点交换 --> 更新下标
/**
* 删除堆顶元素
* @param nums
*/
public static void delete(int[] nums) {
int n = nums.length - 1;
//将堆顶元素设置为数组末尾元素
nums[0] = nums[n];
int curIndex = 0;
int leftIndex = 1;
int rightIndex = 2;
while (curIndex < n) {
//smallIndex为当前节点的左右节点中较小的一个
int smallIndex = leftIndex;
if (rightIndex < n && nums[smallIndex] > nums[rightIndex]) {
smallIndex = rightIndex;
}
//如果当前节点大于较小的节点
if (nums[curIndex] > nums[smallIndex]) {
//交换当前节点和较小节点
swap(nums, curIndex, smallIndex);
//更新当前节点和左右节点的索引
curIndex = smallIndex;
leftIndex = curIndex * 2 + 1;
rightIndex = curIndex * 2 + 2;
} else {
break;
}
}
nums[n] = 0;
}
交换函数
private static void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
总结
最小堆的插入,主打一个上浮,先将新元素插入到数组的末尾,然后将当前元素与其父节点比较。上浮到合适的位置即可。 最小堆的删除,主打一个下沉,每次删除都是先删除堆顶的元素,然后将最后一个元素放到堆顶的位置,然后通过下沉到合适的位置即可。
全部代码
package algo;
import java.util.Arrays;
public class MinHeap {
public static void main(String[] args) {
int[] nums = new int[] {2, 3, 4, 5, 6, 0};
System.out.println(Arrays.toString(nums));
insert(nums, 1);
System.out.println(Arrays.toString(nums));
delete(nums);
System.out.println(Arrays.toString(nums));
}
/**
* 插入
* @param nums
* @param val 待插入的值
*/
public static void insert(int[] nums, int val) {
int n = nums.length - 1;
//将要插入的值作为数组的最后一个元素
nums[n] = val;
//当前节点索引
int curIndex = n;
//当前节点父节点索引
int parentIndex = (curIndex - 1) / 2;
//当前节点小于父节点
while (curIndex > 0 && nums[curIndex] < nums[parentIndex]) {
//交换当前节点和父节点
swap(nums, curIndex, parentIndex);
//更新当前索引为父节点索引
curIndex = parentIndex;
//更新父节点索引
parentIndex = (curIndex - 1) / 2;
}
}
/**
* 删除堆顶元素
* @param nums
*/
public static void delete(int[] nums) {
int n = nums.length - 1;
//将堆顶元素设置为数组末尾元素
nums[0] = nums[n];
int curIndex = 0;
int leftIndex = 1;
int rightIndex = 2;
while (curIndex < n) {
//smallIndex为当前节点的左右节点中较小的一个
int smallIndex = leftIndex;
if (rightIndex < n && nums[smallIndex] > nums[rightIndex]) {
smallIndex = rightIndex;
}
//如果当前节点大于较小的节点
if (nums[curIndex] > nums[smallIndex]) {
//交换当前节点和较小节点
swap(nums, curIndex, smallIndex);
//更新当前节点和左右节点的索引
curIndex = smallIndex;
leftIndex = curIndex * 2 + 1;
rightIndex = curIndex * 2 + 2;
} else {
break;
}
}
nums[n] = 0;
}
private static void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}