含义
大顶堆:完全二叉树,每个根节点值都大于等于其左右孩子的节点值,这样根节点就是最大值
小顶堆:每个根节点值都小于等于左右孩子节点值,根节点为最小值
前置知识
我们对堆中的结点按层进行编号从左到右,对应的数组就是一个大顶堆数组。
由于是完全二叉树,所以有下面结论
- 父结点索引:(i-1)/2
- 左孩子索引:2*i+1
- 右孩子索引:2*i+2
思路
堆排序的过程可以具体分为三步,
- 创建堆:创建完全二叉树,首先将待排序的数组构造成一个大根堆,此时整个数组的最大值就是堆结构的顶端。遍历数组,每次将节点从最后一位放入,最后一位是完全二叉树的最后一位(也就是从底向上,从右向左)。我们对比当前节点和父节点。如果比父节点大,就交换。
- 调整堆:这时我们找到最大值了,也就是堆顶元素。放到数组最后。也就是排序好的,然后继续遍历前面没有排序好的。
- 重复:已排序的就不需要再参与排序了,将最堆顶元素和最后一个元素交换。将剩余n-1个元素继续上面两步,找到最大值,并放到排序好的后面,反复排序。
复杂度
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
空间复杂度:在原数组进行操作,所以辅助空间为O(1),不需要额外空间
代码
//1. 新建大顶堆函数,该函数执行完之后,会让数组中的元素按照大顶堆的顺序排序
void heapify(vector<int> &nums) {
//循环遍历,每次插入一个节点
for(int i = 1; i < nums.size(); i++) {
//这里有个概念,因为我们每次插入的数据要构成完全二叉树,那么这个数组中,插入之后,他的父节点索引值为(i-1) / 2
int parent = (i-1) / 2; // 找到当前节点的父节点的索引肯定是(i-1)/2
int cur = i; // 当前遍历节点为子节点
//如果当前节点值大于父节点,则交换父子节点值,然后将当前节点上移动继续和父节点对比
while(nums[parent] < nums[cur]) {
swap(nums[parent], nums[cur]);//交换值
//自底向上移动节点
cur = parent;//上移当前节点游标
parent = (parent-1)/2;//上移父节点索引
}
}
}
//2. 调整堆,使用递归,因为当前根节点小,目的是将该根节点放到他指定的位置。从根节点向下遍历,如果根节点小于他最大的子节点,则交换,并继续递归该子树
//传入当前节点和最后一个节点的索引
void rebuildHeap(vector<int> & nums, int parent, int last) {
//因为是完全二叉树,对应数组中左孩子索引:2*i+1 右孩子索引:2*i+2
//拿到当前节点的左右子节点中较大的,放到根节点,这时根节点就是最大值
int left = 2*parent+1; // 左子节点
int right = 2*parent+2; // 右子节点
//看是否有左子节点
if(last < left) return; //终止条件:如果当前节点没有左子节点,也就是当前左子节点索引大于最后一个index,也就是,也就是当前节点是最底层节点,则终止
//看是否有右子节点,如果不存在右节点则最大索引就是左子节点了
int maxIndex = left;
//如果存在右节点,且右子节点值大于左节点则右节点值大
if(right <= last && nums[right] > nums[left]) maxIndex = right;
//如果当前根节点值小于他的较大子节点值,则交换,因为要把最大的数放到最上面根节点
if (nums[parent] < nums[maxIndex]) {
// 和最大子节点比较
swap(nums[parent], nums[maxIndex]); // 互换到最大子节点
//进行递归,继续向下调整子树,一直递归到他是子节点,或者他下面的值都比他小则结束
rebuildHeap(nums, maxIndex, last);
}
}
void HeapSort(vector<int>& nums) {
//新建一个大顶堆
heapify(nums);
//3. 从后往前依次排序 ,这时,堆顶的值最大,也就是数组第一个元素最大,我们把最大值放到最后作为排好序的值,然后我们调整未排序的大顶堆中的值
for (int i = nums.size() - 1; i >= 1; i--) {
swap(nums[i], nums[0]); // 最大值和最后一个数交换
rebuildHeap(nums, 0,i-1); // 去掉已排序的数,对未排序的数重建大顶堆
}
}
int main()
{
//随机产生N个数
int N = 200000;
srand(time(NULL)); //生成随机数种子
//放到vector中
vector<int> nums(N);
for (int i = 0; i < N; ++i) {
nums[i] = 1 + (rand() % N);
//cout << nums[i] << ",";
}
cout << endl;
//开始计时
double start, finish; /* 定义开始的时间和结束的时间 */
start = (double)clock();
//20000
//BubbleSort(nums); //2288ms
//SelectionSort(nums); //915ms
//InsertionSort(nums); //528ms
//200000
//mergeSort(nums); //93ms
//quickSort(nums, 0, nums.size()-1);//41ms
HeapSort(nums);//78ms
finish = (double)clock();
//打印
// for (auto item : nums)
// {
// cout << item << ",";
// }
cout << (finish) - start << " ms" << endl;
}