文章目录
- 一、堆(Heap)的基本概念
- 1.1 引入二叉树的顺序存储
- 二、建立大根堆
- 三、基于大根堆进行排序
选择排序:在每一趟待排元素中选取关键字最小(或最大)的元素加入有序子序列
选择排序分为:
- 简单选择排序
- 堆排序
一、堆(Heap)的基本概念
若n个关键字序列L[1…n]满足下面某一条性质,则称为堆(Heap):
- 若满足:L(i)≥L(2i)且L(i)≥L(2i+1)(1≤i≤n/2)–大根堆
- 若满足:L(i)≤L(2i)且L(i)≤L(2i+1)(1≤i≤n/2) --小根堆
1.1 引入二叉树的顺序存储
有此可见,堆逻辑上可理解为一个顺序存储的完全二叉树
- 大根堆–根>左,右的完全二叉树
- 小根堆–根<左,右的完全二叉树
二、建立大根堆
基本思路:把所有分支结点检查一遍,判断是否满足大根堆的要求,如果不满足,则进行调整。
根据完全二叉树的性质:
- 2i<n
- 2i+1<n
- 叶子结点:i>n/2
故只需检查 i ≤ n / 2 i\leq n/2 i≤n/2结点(分支结点)即可。二叉树从下往上调整(数组从后往前遍历)。如果不满足大根堆的条件,从左右孩子结点中挑较大的一个与根结点交换。
举例帮助理解:
i=8;只需检查
i
≤
4
i\leq 4
i≤4的结点即可,即为上图圈出来的结点。二叉树从下往上调整,故从09开始,数组对应从后往前,i=4向前遍历。并令arr[0]=09暂存
代码实现
建立大根堆:
void buildMaxHead(int arr[], int length) {
for (int i = length / 2; i >= 1; i--) {//从后往前遍历数组
HeadAdjust(arr, i, length);
}
}
将以k为根的子树调整为大根堆:
void HeadAdjust(int arr[], int k, int len) {
arr[0] = arr[k];//key
for (int i = 2 * k; i <= len; i*=2) {//延key不断向下检查子节点
if (arr[i] < arr[i + 1] && i + 1 <= len) { //将i指向较大子孩子
i++;
}
if (arr[0] >= arr[i])//当满足大根堆,退出
break;
else {
arr[k] = arr[i];
k = i;//修改k值,继续向下检查
}
}
arr[k] = arr[0];//将key放入最后检查的位置
}
三、基于大根堆进行排序
堆排序:每一趟将堆顶元素加入有序子序列(与待排序序列中的最后一个元素交换)
并将待排序元素序列再次调整为大根堆(小元素不断“下坠”)
举例帮助理解
此时已经完成一次堆排序,堆顶元素为最大元素
将堆顶元素队列末尾元素互换。接着不断进行HeadAdjust(arr,1,len-1)
完整代码实现
#include<iostream>
using namespace std;
int N;
int arr[100050];
void HeadAdjust(int arr[], int k, int len) {
arr[0] = arr[k];
for (int i = 2 * k; i <= len; i *= 2) {
if (arr[i] < arr[i + 1] && i + 1 <= len) { //将i指向较大子孩子
i++;
}
if (arr[0] > arr[i])
break;
else {
arr[k] = arr[i];
k = i;
}
}
arr[k] = arr[0];
}
void buildMaxHead(int arr[], int length) {
for (int i = length / 2; i >= 1; i--) {
HeadAdjust(arr, i, length);
}
}
int main() {
cin >> N;
for (int i = 1; i <= N; i++) {
cin >> arr[i];
}
buildMaxHead(arr, N);//建立大根堆
//基于大根堆的堆排序
for (int i = N; i >= 1;) {
int t = arr[i];
arr[i] = arr[1];
arr[1] = t;
HeadAdjust(arr,1,--i);
}
for (int i = 1; i <= N; i++) {
if (i != 1)
cout << " ";
cout << arr[i];
}
}