堆,堆排序

堆排序是一种基于比较的排序算法,利用完全二叉树的特性,通过建立大根堆或小根堆进行排序。文章详细介绍了堆的概念,包括大根堆和小根堆,以及堆排序的原理和步骤。堆的插入和删除操作的时间复杂度为O(logn),而堆排序的整体时间复杂度为O(nlogn)。文章提供了堆类的C++实现,并讨论了两种建堆方法的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

堆,堆排序

​ 堆是一棵特殊的完全二叉树,每个节点总是大于等于或小于等于其孩子节点,是最高效的优先级队列。

大跟堆每个节点均大于等于其孩子节点的堆,根节点为最大值,小根堆同理。

​ 堆排序的原理则是先将数据建成堆,每次取出堆顶元素按顺序放置,即可完成排序。

堆的实现

堆类的声明:
typedef int HPDatatype;

class heap {                     //以大根堆为例

private:
	HPDatatype* hpData;
	int _size;
	int capacity;
	void expand();				 //扩容,默认扩容至当前2倍

public:
	heap(int k = 0);             //构造函数
	~heap();                     //析构函数
	HPDatatype top();			 //获取堆顶
	void push(HPDatatype x);	 //推入数据
	void pop();					 //取出堆顶
	bool empty();				 //判断堆是否为空
	int size();					 //获取堆的大小
	static void AdjustUp(int * _data, int x);		   //向上调整
	static void AdjustDown(int* _data, int x, int n);  //向下调整
};

向上向下调整定义为静态函数,堆排序时可直接调用

构造函数:
heap::heap(int k) {    //k值仅为预先开辟堆空间,堆中数据量为0
	if (!k) k = 1;
	_size = 0;
	capacity = k;
	hpData = (HPDatatype*)malloc(k * sizeof HPDatatype);
	if (hpData == nullptr) {
		perror("malloc fail");
		return;
	}
}
析构函数:
heap::~heap() {
	assert(hpData);

	free(hpData);
}

养成好习惯,避免内存泄露!

插入函数

​ 堆的插入操作为每次现将要插入数据添加到数组末尾,再从该位置向上调整保证堆的性质,最多调整次数为树的高度即 l o g n logn logn,所以插入操作的时间复杂度为 O ( l o g n ) O(logn) O(logn)

void heap::push(HPDatatype x) {
	assert(hpData);

	if (_size == capacity)
		expand();
	hpData[_size] = x;
	AdjustUp(hpData, _size++);
}
删除函数

堆的删除操作可以直接将堆顶元素放到数组最后一个元素交换,再从堆顶开始向下调整,与插入操作类似,时间复杂度为 O ( l o g n ) O(logn) O(logn)

void heap::pop() {
	assert(hpData);
	assert(!empty());

	_size--;
	swap(hpData[0], hpData[_size]);
	AdjustDown(hpData, 0, _size);
}
上下调整函数
void heap::AdjustUp(int * _data, int x) {
	assert(_data);

	while (x > 0 && _data[(x - 1) >> 1] < _data[x]) {
		swap(_data[(x - 1) >> 1], _data[x]);
		x = (x - 1) >> 1;
	}
}

void heap::AdjustDown(int* _data, int x, int n) {
	assert(_data);

	int child = (x << 1) + 1;
	while (child < n) {
		if (child + 1 < n && _data[child] < _data[child + 1])
			child++;
		if (_data[x] < _data[child])
			swap(_data[x], _data[child]);
		else
			break;
		x = child;
		child = (x << 1) + 1;
	}
}
其他函数
HPDatatype heap::top() {
	assert(hpData);
	if (empty())
		return -1;

	return hpData[0];
}

int heap::size() {
	assert(hpData);

	return _size;
}

bool heap::empty() {
	assert(hpData);

	return !_size;
}

void heap::expand() {     //仅内部扩容时使用
	assert(hpData);

	HPDatatype* rlc = (HPDatatype*)realloc(hpData, sizeof HPDatatype * capacity * 2);
	if (rlc == nullptr) {
		perror("realloc fail");
		return;
	}
	hpData = rlc;
	capacity *= 2;
}

堆排序

以排升序为例,先直接在原数据空间中向上调整建堆,有两种建堆方法,

1. 从前往后依次插入元素(push操作),时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

2. 从后往前依次向下调整,时间复杂度 O ( n ) O(n) O(n)

建堆完成后依次取出堆顶元素并从堆顶向下调整,所以总体时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

void HeapSort(int* st, int* ed) {
	int n = ed - st;
	for (int i = (n - 2) >> 1; i >= 0; i--) {
		heap::AdjustDown(st, i, n);
	}

	for (int i = n - 1; i > 0; i--) {
		swap(st[0], st[i]);
		heap::AdjustDown(st, 0, i);
	}
}

总结

综上所述,堆的各种操作的时间复杂度都不超过O (nlogn),所以堆是一种高效的数据结构。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BowTen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值