概念铺垫:
- 二叉树 根 父 子 左孩子 右孩子 叶节点 树的深度(也叫层高)
- 二叉树:左孩子,右孩子,最多只有两个孩子的就是二叉树;
- m叉树;
- 所有的树都可以转成二叉树,所有的森林都可以转成树(如何转后面讲树会详细介绍).所以二叉树很重要;
- 大根堆:所有的父节点的值都大于子节点
- 小根堆:所有的父都小于子
节点下标推导:
- 已知父亲节点 i :左孩子:i*2+1 右孩子: i*2+2
- 已知孩子节点 i,求父亲节点:(i-1)/2
堆排序的算法思想:
- 首先从最大下标所在的子树开始排大根堆,让后从后往前依次处理子树成为大根堆,(第一次完全乱序的情况下处理大根堆是需要多次调整的,但是之后的调整在第一次大根堆基础上,更为便利)
-
- 当完成第一次大根堆排序后,使得最大值处于根节点的位置:arr[0]
- 将根节点最大值(arr[0])与当前排序序列的最后一个元素交换arr[len-1],下次排序则可以排除最后一个元素,向前递进,直至交换完成
最后节点下标为i,则开始需要处理的子树的根节点下标为:(i-1)/2
数组长度为 len,则开始需要处理的子树的根节点下标为:(len-1-1)/2
i=len-1;
堆排序的代码实现:
//这是配置好的模板文件
#include <iostream>
#include <string>
using namespace std;
void HeadAdjust(int* arr, int begin, int end)
{
int tmp = arr[begin];//子树的根值
for (int i = 2 * begin + 1; i <= end;i=i*2+1)
{
if (arr[i] < arr[i + 1] &&i<end)
{
i++;//使得该子树的孩子节点中最大值为i下标的值
}
if (arr[i] > tmp)
{
arr[begin]=arr[i];
begin = i;
}
else
break;
arr[begin]=tmp;
}
}
void Heapsort(int* arr, int len)
{
for (int i = (len - 2) / 2; i >= 0; i--)//第一次排序大根堆,i为开始的子树根下标
{
HeadAdjust(arr,i,len-1);//每次给予开始和结束下标,此函数处理交换排序
}
for (int i = 0; i < len - 1; i++)
//循环处理每次完成大根堆排序后的arr[0]交换arr[len-i],n个数完成n-1次交换
{
int tmp = 0;
tmp = arr[0];
arr[0] = arr[len - 1 - i];
arr[len - 1 - i] = tmp;
HeadAdjust(arr, 0, len - 1-i-1);
//这一次在大根堆的基础上,只需要完成一次大根堆排序即可,只有根节点的数值有可能不合适
//这一次将根节点的数值交换到合适位置即可,所以不用像第一次一样循环子树处理
}
}
int main()
{
int arr[] = { 1,3,12,5,4,23,7,2,8 ,7,11,22,31 };
int len = sizeof(arr) / sizeof(arr[0]);
Heapsort(arr, len);
for (int i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
堆排序的特性:
- 时间复杂度:O(nlogn)
第一次大根堆排序推导时间复杂度为O(n)
之后每次循环N次排序大根堆,每次调用函数O(logn),乘积为:O(n*logn)
合并后忽略了序数为:O(nlogn)
- 空间复杂度:O(1),仅交换
- 稳定性:不稳定