堆排序是一种基于比较的排序算法,它使用二叉堆(特殊的完全二叉树)的数据结构来实现。二叉堆可以是最大堆或最小堆,其中最大堆的父节点总是大于或等于子节点,而最小堆的父节点总是小于或等于子节点。
基本思想:
- 构建堆:首先将无序的输入序列构造成一个最大堆或最小堆。
- 排序:通过交换堆顶元素(最大或最小)与堆的最后一个元素,然后从堆中移除该元素(即原堆的最后一个元素),并重新调整堆的结构,使其继续保持最大堆或最小堆的性质。这个过程重复进行,直到堆中只剩下一个元素。
操作步骤:
-
建堆(Heapify):
- 从最后一个非叶子节点开始,向上遍历至根节点。
- 对于每个节点,确保它满足堆的性质(即在最大堆中,父节点的值大于子节点的值;在最小堆中,父节点的值小于子节点的值)。
- 如果节点不满足堆的性质,通过与其子节点中的较大(或较小)者交换来调整堆。
- 重复这个过程,直到建堆完成。
-
排序:
- 将堆顶元素(最大或最小值)与堆的最后一个元素交换。
- 移除堆中原来的最后一个元素(已经与堆顶元素交换)。
- 对新的堆顶元素进行下沉操作(在最大堆中向下调整,在最小堆中向上调整),以恢复堆的性质。
- 重复上述步骤,直到堆中只剩下一个元素。
堆排序的时间复杂度为O(n log n),其中n是序列的长度。建堆的过程是O(n)的,而每次的下沉操作是O(log n)的,由于排序过程中需要进行n次下沉操作,因此总的时间复杂度为O(n log n)。
堆排序是一种原地排序算法,不需要额外的存储空间,但它不是稳定的排序算法,因为相同的元素可能会在堆调整过程中改变顺序。堆排序适用于大数据集的排序,特别是当内存使用成为一个考虑因素时。
输入格式
- 第一行:包含两个整数
n
和m(1 <= n,m <= 100000)
。n
表示整数数列的长度。m
表示需要输出的前m
小的数。
第二行
- 包含
n
个整数,表示整数数列中的每个数。
输出格式
- 输出一行,包含
m
个整数。 - 这些整数是整数数列中的前
m
小的数,按照从小到大的顺序排列。
输入样例:
5 3
4 5 1 3 2
输出样例:
1 2 3
#include<iostream>
using namespace std;
const int N = 100010; // 定义常量N,表示堆的最大容量
int h[N], n, m; // h数组用于存储堆的元素,n表示堆中元素的数量,m表示操作的次数
// heap函数用于维护堆的性质,x是当前节点的索引
void heap(int x)
{
int u = 2 * x; // u是x的左子节点的索引
if(u + 1 <= n && h[u + 1] < h[u]) u = u + 1; // 如果有右子节点,并且右子节点的值小于左子节点的值,则选择较小的子节点
if(u <= n && h[u] < h[x]) // 如果子节点的值小于父节点的值
{
int t = h[u]; // 交换父节点和子节点的值
h[u] = h[x];
h[x] = t;
heap(u); // 递归调用heap函数,继续向下调整
}
}
int main()
{
cin >> n >> m; // 输入n和m
for(int i = 1; i <= n; i ++) cin >> h[i]; // 输入堆中的元素
// 从最后一个非叶子节点开始向上建立最小堆
for(int i = n / 2; i >= 1; i --) heap(i);
while(m --) // 处理m次操作
{
cout << h[1] << " "; // 输出堆顶元素(最小元素)
h[1] = h[n --]; // 将堆顶元素替换为堆的最后一个元素,并减少堆的大小
heap(1); // 重新调整堆以维护其性质
}
return 0; // 程序结束
}
总结
堆排序是一种高效的比较排序算法,它利用二叉堆(最大堆或最小堆)的性质来对序列进行排序。算法首先将给定的序列构造成一个堆,然后将堆顶元素(最大或最小值)与最后一个元素交换并从堆中移除,接着重新调整堆以维持其性质。这个过程重复进行,直到堆中只剩下一个元素,从而实现从小到大的排序。堆排序的时间复杂度为O(n log n),是原地排序算法,不需要额外的存储空间,但在排序过程中相同元素的相对顺序可能会改变。