堆
定义
堆必须是完全二叉树,符合完全二叉树的性质才可以构成堆,如果不符合则进行堆调整(上虑和下虑)
完全二叉树
- 只有叶子不满
- 从左到右
- 不可有空缺(间隔)
堆序性,即堆的类型
这也是堆进行调整的依据
大根堆:如果父节点小于子节点,则交换(递归)
- 大根堆:每个父节点的元素大于其子元素,同时完全二叉树不是二叉搜索树,没有左根右的严格顺序

root(大)
/ \
left right
- 小根堆:每个父节点都小于(或者说不大于?)其子节点
root(小)
/ \
left right
存储
- 层序遍历(bfs --> 宽度优先的方式,从上至下)
- 这种映射关系使得堆可以用一维数组进行描述和存储

因为是完全二叉树,每个树的下标和数是一一对应的?
数组下标
left = 2i+1
right = 2i+2
堆的基本操作
下虑
向下调整堆,因为其只有根破坏了堆序性
以大根堆为例
1. 当根节点不满足大根堆性质,即比子节点小,需调整堆
2. 将堆的根节点与其最大的子节点进行交换
3. 最大子节点上移成根,其满足大于旧根节点以及两一个子节点
4. 旧根继续判断是否满足堆性质,如不满足则继续进行
5. 当完全二叉树性质满足或旧根调整到叶子节点即结束
6. 时间复杂度O(logN)
上虑
- 向上调整堆,因只有叶节点破坏了堆序性
- 主要用来插入元素,新增一个元素,放到队尾的叶子接节点
- 直接和父节点进行交换即可
- 时间复杂度O(logN)
建堆
不同的建堆方式对应不同的堆操作
而且其插入的效率不同
自顶向下的建堆
- 对应上滤,调整的是最后一个元素
1. 将新元素放到堆的最后一位,即放在尾部
2. 根堆堆性质进行堆调整,即上滤操作
3. 时间复杂度O(NlogN)
4. 自顶向下体现在哪? -> 是从上到下开始构建的,先保证插入元素之前的元素都是有序的,即上部分有序
自下而上的建堆
- 对应的是下滤
1. 找到最后一个父节点
2. 进行堆序行调整
3. 调整后堆完全有序
4. 依次向上调整,每次只处理两个元素
5. 时间复杂度为O(N)

堆的使用
优先队列 --> 默认小根堆,实现大根堆要使用比较器
- 弹出元素:弹出根,将最后一个元素放在根,最后一个元素肯定小于根的左右节点,必然发生调整
堆排序
使用优先队列完成排序
- 将元素放入优先队列,o(logN)
- 依次弹出元素,即完成排序,O(N)
- 所以整体的时间复杂度为0(NlogN)
- 并且有单独的空间申请
小根堆排序
大根堆排序
1. 使用大根堆进行存储元素
2. 弹出最大元素,放入队尾,堆的size-1
3. 队尾元素放在根,此时需要下滤调整堆
4. 调整后,将堆的根节点放在size-3的位置,递归完成
5. 此时数组在不申请新的空间的情况下完成了排序

大根堆及其堆排序实现
大根堆(Max Heap)是一种特殊的完全二叉树,其中每个父节点的值都大于或等于其子节点的值。堆排序利用大根堆的性质对数组进行排序。具体步骤如下:
- 构建大根堆:将数组调整成大根堆。
- 堆排序:将堆顶元素(最大值)与堆的最后一个元素交换,然后重新调整剩余部分为大根堆,重复此过程直到整个数组有序。
以下是大根堆及其堆排序的 Java 实现:
public class HeapSort {
public static void heapSort(int[] nums) {
int n = nums.length;
// 构建大根堆
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(nums, n, i);
}
// 进行堆排序
for (int i = n - 1; i > 0; i--) {
// 将堆顶元素(最大值)与堆的最后一个元素交换
int temp = nums[0];
nums[0] = nums[i];
nums[i] = temp;
// 重新调整剩余部分为大根堆
heapify(nums, i, 0);
}
}
// 调整大根堆
private static void heapify(int[] nums, int n, int i) {
int largest = i; // 初始化最大值为当前节点
int left = 2 * i + 1; // 左子节点
int right = 2 * i + 2; // 右子节点
// 如果左子节点大于当前节点
if (left < n && nums[left] > nums[largest]) {
largest = left;
}
// 如果右子节点大于当前最大值
if (right < n && nums[right] > nums[largest]) {
largest = right;
}
// 如果最大值不是当前节点,交换并继续调整
if (largest != i) {
int swap = nums[i];
nums[i] = nums[largest];
nums[largest] = swap;
// 递归调整受影响的子树
heapify(nums, n, largest);
}
}
public static void main(String[] args) {
int[] nums = {12, 11, 13, 5, 6, 7};
heapSort(nums);
// 打印排序后的数组
for (int num : nums) {
System.out.print(num + " ");
}
}
}
代码解释
-
构建大根堆:
for (int i = n / 2 - 1; i >= 0; i--) { heapify(nums, n, i); }从最后一个非叶子节点开始,逐个调整节点,使其满足大根堆的性质。最后一个非叶子节点的索引为
n / 2 - 1。 -
堆排序:
for (int i = n - 1; i > 0; i--) { int temp = nums[0]; nums[0] = nums[i]; nums[i] = temp; heapify(nums, i, 0); }将堆顶元素(最大值)与堆的最后一个元素交换,然后重新调整剩余部分为大根堆,重复此过程直到整个数组有序。
-
调整大根堆:
private static void heapify(int[] nums, int n, int i) { int largest = i; int left = 2 * i + 1; int right = 2 * i + 2; if (left < n && nums[left] > nums[largest]) { largest = left; } if (right < n && nums[right] > nums[largest]) { largest = right; } if (largest != i) { int swap = nums[i]; nums[i] = nums[largest]; nums[largest] = swap; heapify(nums, n, largest); } }largest初始化为当前节点i。left和right分别表示左子节点和右子节点的索引。- 比较当前节点与其子节点,找到最大值的索引。
- 如果最大值不是当前节点,交换节点值,并递归调整受影响的子树。
通过上述步骤,可以实现大根堆的构建和堆排序。

被折叠的 条评论
为什么被折叠?



