堆排序——排序算法笔记
1. 二叉堆
二叉堆是完全二叉树或者是近似完全二叉树
二叉树特性:
- 父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
- 每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
(1)大堆
父结点的键值总是大于或等于任何一个子节点的键值
(2) 小堆
父结点的键值总是小于或等于任何一个子节点的键值
2. 堆的存储
堆的逻辑结构是树,但是一般存储的方式是数组;
逻辑结构:
存储结构:
i 结点的父结点:(i - 1) / 2;
i 结点的左子节点:i * 2 + 1;
i 结点的右子节点:i * 2 + 2;
基本思路
- 将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
第一个非叶子节点 1(arr[i] = 6)的计算:
方法一:
最末子结点 = length - 1;
最末子结点的父结点 = (最末子节点 - 1)/ 2
方法二:
length / 2 - 1
-
找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换
-
将[图片上传中...(image.png-af7acd-1582374158730-0)]
堆顶元素9和末尾元素4进行交换。
-
重新调整结构,使其继续满足堆定义
-
再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.
实现这一步方式,直接将arr的length-1,长度递减
for(int i = lens - 1; i >= 0; i--){
swap(arr[0], arr[i]);
heapify(arr, i, 0);
}
问题:heapify只能完成一个节点的heapify
所以堆排中很重要的一点的是:
void buildHeap(int *arr, int lens)
{
//计算最底层的“第一个”父结点
int last_node = lens - 1;
int parent_node = (last_node - 1) / 2;
//从底层开始向上回溯
for(int i = parent_node; i >= 0; i--){
heapify(arr, lens, i);
}
}
参考链接:https://www.bilibili.com/video/av47196993?from=search&seid=5468598825988489537
递归方法的堆排序:
#include <iostream>
#include <string.h>
#include <vector>
using namespace std;
void heapify(vector<int>& nums, int lens, int index)
{
if (index > lens)
{
return;
}
int left = index * 2 + 1;
if (left < lens)
{
int maxIndex = left + 1 < lens && nums[left + 1] > nums[left]? left + 1 : left;
maxIndex = nums[maxIndex] > nums[index]? maxIndex : index;
if (maxIndex != index)
{
swap(nums[maxIndex], nums[index]);
heapify(nums, lens, maxIndex);
}
}
}
void heapSort(vector<int>& nums)
{
int lens = nums.size();
for (int i = lens / 2 - 1; i >= 0 ; i--)
{
heapify(nums, lens, i);
}
for (int i = lens - 1; i > 0 ; i--)
{
swap(nums[0], nums[i]);
heapify(nums, i, 0);
}
}
int main()
{
int arr[5] = {5,4,3,2,1};
vector<int> nums;
for (int i = 0; i < 5; i++)
{
nums.push_back(arr[i]);
}
heapSort(nums);
for (int i = 0; i < 5; i++)
{
printf("%d",nums[i]);
}
return 0;
}