堆排序算法
1 堆
堆是一棵二叉树,有以下几个约束条件:
(1)用数组表示
(2)是一棵完全二叉树
所谓完全二叉树就是用数组表示的时候,孩子节点的下标和父节点的下标满足如下的关系:
left = 2 * parent + 1,
right = 2 * parent + 2,
下标从 0 开始。
下图中,左边的二叉树是完全二叉树,右边的二叉树不是完全二叉树。
(3)分大顶堆和小顶堆
如果子节点的数值都不大于父节点,那么堆是大顶堆。如果子节点的数值都不小于父节点,那么堆是小顶堆。下图中,左边是大顶堆,右边是小顶堆。
大顶堆和小顶堆与二叉搜索树的约束条件是不一样的:对于堆来说只要求子节点和父节点之间的大小关系,至于左子节点和右子节点的大小关系,没有要求;对于二叉排序树来说,要求 left < root <= right。
2 堆排序
使用堆这种数据结构可以进行排序,属于选择排序的思想。大顶堆用于从小到大排序,小顶堆用于从大到小排序。
以大顶堆为例,为什么从小到大排序使用大顶堆呢 ?如下图所示,堆排序是属于选择排序,每一次选择的时候都是选择的 root 节点(root 节点确定是堆中的最大值),然后将选择的节点从后向前排。比如第一次选择,选择的数值是 100,要和最后一个位置进行交换。交换之后的数组,[0, 7] 位置的元素需要进行调整,把数据再调整为一个堆。如果从小到大排序使用小顶堆,那么第一次选择的就是最小值,这个值不需要动位置,那么 [1, 8] 位置的数据就需要进行调整,调整到满足堆的要求。这个时候 [1, 8] 中的数据,子节点和父节点下标不满足 left = 2 * parent + 1, right = 2 * parent + 2,无法操作。所以从小到大排序使用大顶堆,从大到小排序使用小顶堆。
使用堆排序一般分为两步:
(1)创建初始堆
(2)选择排序
选择排序的每一步又分两步,选择和调整。每选择一个数放到指定位置之后都要对堆进行调整,使数据满足堆的要求。
leetcode 中有一个数组排序题,可以使用堆排序。
class Solution {
public:
// 昨天写的,今天再写,还是会出错
vector<int> sortArray(vector<int>& nums) {
heapSort(nums);
return nums;
}
// 堆排序算法
void heapSort(vector<int> &nums) {
// 创建初始堆
createHeap(nums);
int size = nums.size();
// 选择排序过程
for (int i = 0; i < size; i++) {
// 选择和交换
int tmp = nums[0];
nums[0] = nums[size - 1 - i];
nums[size - 1 - i] = tmp;
// 调整堆
// 随着排序的进行,确定好位置的数据越来越多
// 堆排序的时候需要关心的数据个数也在递减
adjustHeap(nums, 0, size - 1 - i - 1);
}
}
// 创建初始堆
void createHeap(vector<int>& nums) {
int size = nums.size();
// 堆是一棵二叉树
// 创建堆的时候,从二叉树的倒数第二层往上遍历
// 每个节点都要进行判断,如果需要调整,则进行调整
int start = size / 2;
for (int i = start; i >= 0; i--) {
adjustHeap(nums, i, size - 1);
}
}
// 调整堆
// 创建初始堆和调整堆,都会使用这个函数
// 选择过程调整堆的时候,因为就改变了 root 这一个位置
// 除了 root 之外,其它节点都满足堆的要求
// 所以从 root 节点开始,调整一次堆就可以了,调整之后就满足堆的要求了
void adjustHeap(vector<int>& nums, int start_index, int end_index) {
if (start_index >= end_index) {
return;
}
// 在 parent,left child, right child 中选择一个较大的值
int bigger_index = start_index;
int left_child_index = 2 * start_index + 1;
int right_child_index = left_child_index + 1;
if (left_child_index <= end_index && nums[left_child_index] > nums[bigger_index]) {
bigger_index = left_child_index;
}
if (right_child_index <= end_index && nums[right_child_index] > nums[bigger_index]) {
bigger_index = right_child_index;
}
// 如果 left child 和 right child 的值都不大于 parent
// 那么不需要交换,这一层不需要交换
// 那么下边的也是满足要求的,所以调整结束,可以直接返回了
// 如果这次需要交换,那么就进行交换,然后递归向下遍历
if (bigger_index != start_index) {
int tmp = nums[start_index];
nums[start_index] = nums[bigger_index];
nums[bigger_index] = tmp;
adjustHeap(nums, bigger_index, end_index);
}
}
};
原文地址:https://blog.csdn.net/weixin_38184628/article/details/138002513
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!