数据结构--堆及堆排序的代码实现

本文深入介绍了堆这一数据结构,包括最小堆和最大堆的概念,以及它们在完全二叉树中的特性。堆的插入、删除操作通过调整保持堆的性质,如AdjustUp和AdjustDown函数。此外,堆可用于实现优先队列和堆排序,具有O(logn)的时间复杂度。堆排序算法通过构建最小堆,不断取出堆顶元素达到排序目的。文章还提供了堆的C语言实现示例,帮助读者更好地理解和应用堆。

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

堆是一个按照完全二叉树顺序存储方式在一个二维数组中的数据结构,其性质为

  1. 堆中的某个结点总是不大于或不小于父节点的值

  2. 堆是一个完全二叉树

每个结点大于父节点的叫最小堆,每个结点小于父节点的叫最大堆


这就是一个最小堆,因为每个父节点都小于对应的子节点。

在这里插入图片描述

比如说1小于575小于69

堆的根节点总是放置最小或最大的结点,但是其他结点的排序不一定为如此,每个结点只能说是自己的子树上最小或最大的结点。

堆可以构建一个优先序列,也可以进行堆排序。

堆可以利用一个数组进行存储,减少了空间浪费。

比如说

int a[] = [1,5,7,6,9,8,10,8,9,12]

假设结点位置为i则其子节点分别就为child1 = (i * 2)+ 1,child2 = (i * 2)+ 2

下面这道题就用到了这个性质:

一颗完全二叉树上有1001个结点,其叶子结点的个数是( )
A.251 B.500 C.501 D.1001

要判断叶子结点的个数,由上面的公式我们知道每个父亲结点和其子结点的位置编号关系为child = 2 * parent + 1, child2 = 2 * parent + 2,所以从编号500开始的节点就没有孩子节点了, 都为叶子节点,故叶子节点位置[500, 1000],共501个。 所以答案为C。

堆的基本结构:

typedef int DataType;

typedef struct Heap {
	DataType* a;
	size_t size;
	size_t capacity;
}HP;

堆的基本操作:

void HeapInit(HP* php);//初始化堆
void HeapDestroy(HP* php);//销毁堆
void HeapPush(HP* php, DataType x);//向堆中插入元素
void HeapPop(HP* php);//删除堆中最大或最小的元素
void AdjustUp(DataType* a, size_t child);//向上调整堆
void AdjustDown(DataType* a, size_t size, size_t root);//向下调整堆
DataType HeapTop(HP* php);//堆的根元素
void HeapPrint(HP* php);//打印堆中元素
bool HeapEmpty(HP* php);//判断堆是否为空
void Swap(DataType* pa, DataType* pb);//交换元素
size_t HeapSize(HP* php);//堆的大小

堆的初始化:

void HeapInit(HP* php) {
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

堆的销毁:

void HeapDestroy(HP* php) {
	free(php->a);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

堆的插入:

我们通过一个插入例子来看看插入操作的细节。我们将数字 3 插入到这个堆中:

在这里插入图片描述

数组变为[1,5,7,9,10,3]

在这里插入图片描述

我们需要对数组进行改变使其满足最小堆。

在这里插入图片描述

插入一个数据然后与其父节点进行比较如果比父节点小就交换结点,直到满足最小堆。

代码如下:

void AdjustUp(DataType* a, size_t child) {

	size_t parent = (child - 1) / 2;
	while (child > 0) {
		if (a[child] < a[parent]) {
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else {
			break;
		}
	}

}

void HeapPush(HP* php, DataType x) {
	assert(php);
	
	if (php->size == php->capacity) {
		size_t newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;

		DataType* tmp = realloc(php->a, sizeof(DataType) * newCapacity);
		if (tmp == NULL) {
			printf("realloc fail\n");
			exit(-1);
		}

		php->a = tmp;
		php->capacity = newCapacity;
	}

	php->a[php->size] = x;
	++php->size;

	AdjustUp(php->a, php->size - 1);
}

删除根节点:

在这里插入图片描述

对这个小堆进行pop操作,首先我们将根节点和尾结点交换

在这里插入图片描述

此时为了保持最小堆我们需要对这个堆整体进行调整

在这里插入图片描述

最后得到一个最小堆

在这里插入图片描述

删除及向下调整代码如下:

void AdjustDown(DataType* a, size_t size, size_t root) {
	size_t parent = root;
	size_t child = parent * 2 + 1;
	while (child < size) {
		if (child + 1 < size && a[child + 1] < a[child]) {
			child++;
		}

		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapPop(HP* php) {
	assert(php);
	assert(php->size > 0);

	Swap(&php->a[0], &php->a[php->size - 1]);
	--php->size;

	AdjustDown(php->a, php->size, 0);
}

得到堆顶元素:

DataType HeapTop(HP* php) {
	assert(php);
	assert(php->size > 0);

	return php->a[0];//返回堆顶元素
}

判断堆是否为空:

bool HeapEmpty(HP* php) {
	assert(php);

	return php->size == 0;
}

堆的大小:

size_t HeapSize(HP* php) {
	assert(php);

	return php->size;
}

堆排序

由上面堆的性质我们可以知道堆每个结点一定是他的子树中最小或最大的结点,我们可以运用这个性质进行一个排序操作。

首先我们进行插入操作,这样可以保证这个数组会是一个最小堆,当堆不为空时一直取栈顶元素,这样就可以产生一个由小到大的数列。

算法复杂度:

push操作的算法复杂度为O(logn), 堆排序中push操作的算法复杂度就是O(nlogn), pop操作同理,所以堆排序的算法复杂度就为O(nlogn)。

void HeapSort(int* a, int size) {
	HP hp;
	HeapInit(&hp);

	for (int i = 0; i < size; i++) {
		HeapPush(&hp,a[i]);
	}

	int j = 0;

	while (!HeapEmpty(&hp)) {
		a[j] = HeapTop(&hp);
		j++;
		HeapPop(&hp);
	}

	HeapDestroy(&hp);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值