【C语言】建堆算法与向上/下调整

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

带领大家简单了解一下堆,二叉树的部分有简单了解就可以看的懂


提示:以下是本篇文章正文内容,下面案例可供参考

一、堆

堆这种数据结构其实就是“更有规矩”一点的完全二叉树
在这里插入图片描述
如果在一个堆中,任意一个根节点都大于等于它的子节点,则称作这个堆为大堆。
如果在一个堆中,任意一个根节点都小于等于它的子节点,则称作这个堆为小堆。
在这里插入图片描述
一般做题的时候为了方便在本地调试,我们可能会一个一个的开辟出节点,然后手动把它们连接在一起,但实际上我们可以写出一个函数,让它根据数组创建堆

虽然堆中的数据是以“二叉树”的逻辑顺序存储的,但是实际上堆是以数组这样的物理顺序依次存储的

这样其实可以发现,设父节点的下标为parent,左子节点的下标为child,那么child=parent*2+1,这在后面是非常重要的一个点
下面是我自己写的堆中头文件的内容:

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int HpDataType;
typedef struct Heap
{
	HpDataType* a;
	int size;
	int capacity;
}Heap;
// 对数组进行堆排序
void HeapSort(int* a, int n);
//输入一个拥有n个元素的数组,实现堆的构建
void HeapCreate(Heap* hp, HpDataType* a, int n);
//堆的初始化
HpDataType* HeapInit(Heap* hp);
//堆的销毁
void HeapDestory(Heap* hp);
//打印堆中元素
void HeapPrintf(Heap* hp);
//插入新元素
void HeapPush(Heap* hp,HpDataType x);
//push过程中维持小堆形态
void AdjustUp(Heap* hp,int child);
//删除堆顶元素
void HeapPop(Heap* hp);
//删除堆顶元素过程中维持大堆
void AdjustDown(Heap* hp, int n,int parent);
//返回堆顶元素
HpDataType HeapTop(Heap* hp);
//返回堆大小
int HeapSize(Heap* hp);
//堆的判空
int HeapEmpty(Heap* hp);

下面建堆算法中,我以大堆来举例,对应的是函数“AdjustDown”

1、向上/下调整

先来看一些无关紧要的函数,对堆先有一点小了解

//堆的初始化
HpDataType* HeapInit(Heap* hp)
{
	assert(hp);
	hp->a = NULL;
	hp->size = 0;
	hp->capacity = 0;
}
//堆的销毁
void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->a);
	hp->a = NULL;
	hp->capacity = hp->size = 0;
}
void HeapPrintf(Heap* hp)//方便调试
{
	assert(hp);
	int i = 0;
	for (i = 0; i < hp->size; i++)
	{
		printf("%d ", hp->a[i]);
	}
	printf("\n");
}

然后是一些小例子,这是一个大堆:
在这里插入图片描述
直接删除堆顶数据,再按原来的顺序构建二叉树,你会发现它已经不再是堆

在这里插入图片描述
按照正常规则,删除堆顶元素的以后的二叉树长这样:
在这里插入图片描述
所以在堆的增删过程中我们需要用函数来对整个堆进行调整

堆的删除,指的是堆顶元素的删除,也就是将数组中的数据用建堆算法排好序以后,删除堆这个二叉树“祖先节点”-也就是下标为0的数据,然后,我们需要将堆底的数据置于堆顶,(实现起来的话先”交换“后“删除”是一样的)最后需要用函数AdjustDown对堆进行调整

//删除堆顶元素,并将堆底元素置于堆顶
void HeapPop(Heap* hp)
{
	assert(hp);
	int tmp = hp->a[0];
	hp->a[0] = hp->a[hp->size - 1];
	hp->size--;
	AdjustDown(hp, hp->size, 0);
}

向下调整指的是把刚刚被挪到堆顶的数据向下调整

由于是从堆顶往堆底调整,所以首先要注意child不要超过数组的容量n;
parent这个参数主要是要在建(大)堆算法中复用,所以特意设置的

//删除堆顶元素过程中维持大堆
void AdjustDown(Heap* hp, int n, int parent)//参数parent是为了在建堆算法中复用
{
	int child = 2 * parent + 1;
	while (child < n)
	{
		 保证child指向大的那个孩子
		if ((child+1 < n) && (hp->a[child] < hp->a[child + 1]))
		{
			child++;
		}
		// 1、孩子大于父亲,交换,继续向下调整
		// 2、孩子小于父亲,则调整结束
		if (hp->a[parent] < hp->a[child])
		{
			int tmp = hp->a[parent];
			hp->a[parent] = hp->a[child];
			hp->a[child] = tmp;
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}

插入数据后要进行调整,也是相似的道理
在插入新元素的时候我们选择直接在数组的后面直接插入这个数据,但如果我们想把它安放在逻辑结构的堆中的正确顺序,我们还需要把这个数组重新用函数AdjustUp调整

//插入新元素
void HeapPush(Heap* hp, HpDataType x)
{
	assert(hp);
	if (hp->capacity == hp->size)
	{
		hp->a = (HpDataType*)realloc(hp->a, 2 * (hp->capacity) * sizeof(HpDataType));
	}
	hp->a[hp->size] = x;
	hp->size++;
	AdjustUp(hp->a, hp->size, hp->size - 1);
}

向上调整指的是把新插入的元素向上调整到合适的位置

主要思想是从新插入的节点开始,如果它比父节点大,那么就交换两个节点的值,因为这个过程需要把节点和父节点“从上往下,从堆顶到堆底”进行比较,所以边界条件是child大于0

//push过程中维持小堆形态
void AdjustUp(Heap* hp, int child)
{
	assert(hp);
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (hp->a[child] > hp->a[parent])
		{
			int tmp = hp->a[child];
			hp->a[child] = hp->a[parent];
			hp->a[parent] = tmp;
			child = parent;//确保循环持续运行
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

2、建堆算法

以建造大堆为例
建堆算法的规则就是:一个一个的把所有parent节点用adjustdown按照从堆底向堆顶的方向挨个跑一次

堆的构建(复杂度高)
//void HeapCreate(Heap* hp, HpDataType* arr, int n)
//{
//	assert(hp);
//	HeapInit(hp);
//	int i = 0;
//	Heap* tmp = (HpDataType*)malloc(n * sizeof(HpDataType));
//	if (!tmp)
//	{
//		perror("HeapInit:malloc\n");
//		exit(-1);
//	}
//	hp->a = tmp;
//	hp->capacity = n;
//	for (i = 0; i < n; i++)
//	{
//		HeapPush(hp, arr[i]);
//		AdjustUp(hp, (hp->size) - 1);
//	}
//}
//堆的构建
void HeapCreate(Heap* hp, HpDataType* arr, int n)
{
	assert(hp);
	HeapInit(hp);
	int i = 0;
	Heap* tmp = (HpDataType*)malloc(n * sizeof(HpDataType));
	if (!tmp)
	{
		perror("HeapInit:malloc\n");
		exit(-1);
	}
	hp->a = tmp;
	hp->capacity = hp->size = n;//注意n就是size,所以后面求父节点时要减2才是下标
	memcpy(hp->a, arr, n * sizeof(HpDataType));
	for (int i = (n - 2) / 2; i >=0; i--)
	{
		AdjustDown(hp, n, i);
	}


3.Topk问题

举个具体的例子就是“从N个数里面寻找最大的K个”这样的问题,我们既可以建大堆,取一次堆顶数据然后pop一次,
但真正高效的做法是:
建立K个数小堆,然后遍历这N个数的数组,如果遍历到的数据比堆顶的数据大,那么就把这个数据push进去,然后向下调整,遍历结束以后,留在这个小堆中的数据就是前k大的数据

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: #include <stdio.h> void max_heapify(int arr[], int start, int end) { // 建立父节点指标和子节点指标 int dad = start; int son = dad * 2 + 1; while (son <= end) { // 子节点指标在范围内才做比较 if (son + 1 <= end && arr[son] < arr[son + 1]) // 先比较两个子节点大小,选择最大的 son++; if (arr[dad] > arr[son]) // 如果父节点大於子节点代表调整完毕,直接跳出函数 return; else { // 否则交换父节点和子节点的值,并继续子节点和孙节点比较 int temp = arr[dad]; arr[dad] = arr[son]; arr[son] = temp; dad = son; son = dad * 2 + 1; } } } void heap_sort(int arr[], int len) { int i; // 初始化,i从最後一个父节点开始调整 for (i = len / 2 - 1; i >= 0; i--) max_heapify(arr, i, len - 1); // 先將第一個元素和已排好元素前一位做交换,再重新調整,直到排序完畢 for (i = len - 1; i > 0; i--) { int temp = arr[0]; arr[0] = arr[i]; arr[i] = temp; max_heapify(arr, 0, i - 1); } } int main() { int arr[10] = {2, 3, 1, 7, 8, 4, 10, 16, 12, 9}; int len = (int) sizeof(arr) / sizeof(*arr); heap_sort(arr, len); int i; for (i = 0; i < len; i++) printf("%d ", arr[i]); printf("\n"); return 0; } ### 回答2: C语言中的堆排序算法如下: #include <stdio.h> // 堆排序函数 void heapSort(int arr[], int n) { // 创建一个最大堆 buildMaxHeap(arr, n); // 从最后一个非叶子节点开始调整堆 for (int i = n - 1; i > 0; i--) { // 将堆顶元素与末尾元素交换 swap(&arr[0], &arr[i]); // 调整堆 heapify(arr, i, 0); } } // 创建最大堆 void buildMaxHeap(int arr[], int n) { // 从最后一个非叶子节点开始向上调整 for (int i = (n / 2) - 1; i >= 0; i--) { heapify(arr, n, i); } } // 调整堆 void heapify(int arr[], int n, int i) { int largest = i; // 假设当前节点为最大节点 int left = 2 * i + 1; // 左子节点 int right = 2 * i + 2; // 右子节点 // 若左子节点大于最大节点,则更新最大节点 if (left < n && arr[left] > arr[largest]) { largest = left; } // 若右子节点大于最大节点,则更新最大节点 if (right < n && arr[right] > arr[largest]) { largest = right; } // 若最大节点不是当前节点,则交换并继续调整 if (largest != i) { swap(&arr[i], &arr[largest]); heapify(arr, n, largest); } } // 交换两个元素的函数 void swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } int main() { int arr[] = { 5, 2, 8, 3, 1 }; int n = sizeof(arr) / sizeof(int); heapSort(arr, n); printf("排序结果:"); for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } return 0; } ### 回答3: 堆排序是一种利用最大堆或最小堆数据结构进行排序的算法。下面是C语言实现堆排序的代码: ```c #include <stdio.h> #include <stdlib.h> // 交换两个元素 void swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } // 调整堆 void heapify(int arr[], int n, int i) { int largest = i; // 初始化根节点为最大值 int left = 2 * i + 1; // 左子节点 int right = 2 * i + 2; // 右子节点 // 如果左子节点大于根节点,则更新最大值索引 if (left < n && arr[left] > arr[largest]) largest = left; // 如果右子节点大于最大值,则更新最大值索引 if (right < n && arr[right] > arr[largest]) largest = right; // 如果最大值变化,则交换最大值与根节点,并继续调整堆 if (largest != i) { swap(&arr[i], &arr[largest]); heapify(arr, n, largest); } } // 堆排序 void heapSort(int arr[], int n) { // 构建最大堆 for (int i = n / 2 - 1; i >= 0; i--) heapify(arr, n, i); // 逐个将堆顶元素移动到末尾,并调整堆 for (int i = n - 1; i > 0; i--) { swap(&arr[0], &arr[i]); heapify(arr, i, 0); } } // 打印数组 void printArray(int arr[], int n) { for (int i = 0; i < n; ++i) printf("%d ", arr[i]); printf("\n"); } int main() { int arr[] = { 12, 11, 13, 5, 6, 7 }; int n = sizeof(arr) / sizeof(arr[0]); printf("初始数组:"); printArray(arr, n); heapSort(arr, n); printf("排序后的数组:"); printArray(arr, n); return 0; } ``` 以上代码中,`swap`函数用于交换两个元素的值,`heapify`函数用于调整堆,`heapSort`函数用于构建堆并进行堆排序。`main`函数中给定了一个示例数组,通过调用`heapSort`函数对数组进行排序,最后通过`printArray`函数打印出排序后的结果。输出结果为: ``` 初始数组:12 11 13 5 6 7 排序后的数组:5 6 7 11 12 13 ``` 这段代码实现了堆排序算法的基本逻辑,通过堆数据结构实现了一个稳定且高效的排序算法

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Alexanderite

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

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

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

打赏作者

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

抵扣说明:

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

余额充值