题目信息
用堆排序算法按关键字递减的顺序排序。
输入
待排序记录数(整数)和待排序记录(整数序列)
输出
建堆结果和建堆后第一、第二次筛选结果。(注:待排序记录数大于等于3)
测试样例
测试样例1
6
11
12
16
14
15
10
16 15 11 14 12 10
15 14 11 10 12
14 12 11 10
测试样例2
9
9
8
7
6
5
4
3
2
1
9 8 7 6 5 4 3 2 1
8 6 7 2 5 4 3 1
7 6 4 2 5 1 3
解答
#include<iostream>
using namespace std;
int a[1010];
int n;
void Adjust(int x, int k)
{
int i = x;//i是这个根节点
int j = 2 * i;//j是下面的子节点
int up = a[i];//那么此时i的子节点其实就是2i和2i+1
while (true)
{
if (j < k && a[j] < a[j + 1])
{//如果还没到最后一个节点且后面的节点比前一个节点大,那么直接看后面的节点
j++;//因为我们要的是当前子节点的最大值
}
if (a[j] > up)
{//如果儿子大于父亲
a[i] = a[j];//将父亲的位置让给儿子,同时将父亲与孙子比对
i = j;//
j = 2 * i;
}
else
{//如果两个if都没走进去的话,相当于两个子节点都不符合要求
break;
}
}
a[i] = up;
}
void heapsort()
{
for (int i = n / 2; i >= 1; i--)
{//从一半的位置开始调整可由二叉树的性质推得
Adjust(i, n);
}
}
int main()
{
//freopen("/Users/zhj/Downloads/test.txt", "r", stdin);
cin >> n;
for (int i = 1; i <= n; i++)
{//第一个数存储位置在1!!!
cin >> a[i];
}
//数组是按照大根堆的方式来构建的
for (int i = 0; i < 3; i++)
{//一共展示三次
heapsort();//每一次都进行下堆调整
for (int k = 1; k <= n; k++)
{
cout << a[k] << " ";
}
cout << endl;
a[1] = a[n];//舍弃最大值,将原最大值的地方赋上最后的那个值
n--;//然后堆对剩下n-1个元素调整
}
return 0;
}
想法
本题考点很明确,就是堆。
堆介绍
堆的结构可以分为大根堆和小根堆,是一个完全二叉树,而堆排序是根据堆的这种数据结构设计的一种排序,下面先来看看什么是大根堆和小根堆。
左边是大根堆,右边是小根堆。我们对图中的每个数做一个标记,就会形成相对应的数组。
同时我们根据二叉树自己本身的性质会发现三个规律:
1.父结点索引:从0
到(int)i/2
2.左孩子索引:2*i
3.右孩子索引:2*i+1
堆初始化
根据输入的输入逐个先存储到数组中,假设我们现在有一组数据:
20 30 90 40 70 110 60 10 100 50 80
现在我们进行转化大根堆的操作:
每一次我们都将父节点与两个子节点中较的那个数进行比较
如果父节点A小于子节点中的较大值B,那么将这两个值进行调换,同时继续累积向下判断这个值A,直至这个A值放置在合适的位置。
如果父节点大于两个子节点表示此时已经完备了。
我们从最后一个父节点开始向第一个父节点遍历,逐个调整我们的堆。
调整的过程中一定要将父节点放置在最低最合适的地方
那么此时我们完成了对堆的初始化操作,使得堆顶是一个最大值,而此时我们发现实际上除了第一个值是最大值外,其它值的大小并没有办法做出一个确切的排序。
再构造·排序数组
在将数组转换成最大堆之后,接着要进行交换数据,从而使数组成为一个真正的有序数组。根据如下两步操作即可:
- 首先将数组构造成一个大根堆。
- 固定一个最大值,将剩余的数重新构造成一个大根堆,重复这样的过程。
首先将根节点,也就是最大值放置在数组的最后一个位置上。
对剩余的数组重新构建一次大根堆,也就是将新根节点放置在合适的位置,调整后的大根堆再一次将最大值浮现出来。
不断重复上述操作,最终数组后端变排序出数组,浮现出结果。
算法分析
时间复杂度
堆排序的时间复杂度是O(N*lgN)
。
假设被排序的数列中有N个数。遍历一趟的时间复杂度是O(N)
,需要遍历多少次?
堆排序是采用的二叉堆进行排序的,二叉堆就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的定义,它的深度至少是lg(N+1)
。由于二叉堆是完全二叉树,因此,它的深度最多也不会超过lg(2N)。因此,遍历一趟的时间复杂度是O(N)
,而遍历次数介于lg(N+1)
和lg(2N)
之间;因此得出它的时间复杂度是O(N*lgN)
。
稳定性
堆排序是不稳定的算法,它不满足稳定算法的定义。它在交换数据的时候,是比较父结点和子节点之间的数据,所以,即便是存在两个数值相等的兄弟节点,它们的相对顺序在排序也可能发生变化。
算法稳定性:假设在数列中存在a[i]=a[j]
,若在排序之前,a[i]
在a[j]
前面;并且排序之后,a[i]
仍然在a[j]
前面。则这个排序算法是稳定的!