“堆的增删查改“以及“堆排序“详解

目录

堆的增删查改:

1.void HeapInit(HP* php); 初始化函数:

2.void HeapPush(HP* php, HPDataType x);  堆添加数据函数:

(2)void AdjustUp(HPDataType* a, size_t child) 向上调整函数: 

3.void HeapPop(HP* php) 堆顶删除函数:

(2) void AdjustDown(HPDataType* a, size_t size, int parent)  向下调整函数(向下调整法建小堆的前提是:务必保证左右子树都是小堆,如果建大堆就要保证左右子树都是大堆):

4.剩下的HeapSize  HeapTop HeapEmpty 都和顺序表的一个做法。

堆排序的两类做法:

1.void HeapSort(int a[],int size) 堆排序函数第一类写法,但是不推荐哦:

2.void HeapSort2(int a[],int size) 堆排序函数第二类写法,共2种写法:

(1)如何通过向下调整法把乱序的数组排成大堆并排升序:

(即 如何通过向下调整法建大堆 并排升序?)

(2) 如何通过向上调整法建大堆?(堆排序的排序步骤都一样,主要说建大堆步骤)

 下面是堆增删查改和堆排序的代码实现:

Heap.h

Heap.c

Test.c

运行结果: 


堆的增删查改:

堆的逻辑结构是完全二叉树,完全二叉树特点是最底层的节点连续,所以物理结构用数组构建。

给出结构体Heap(堆),包含一个数组a,节点个数size(就是数据个数),还有数组大小capacity。随后在Test.c文件中定义出这个结构体 HP hp;这里就按建小堆为例(小根堆:父亲小于等于孩子) 接下来我们来逐个分析如何构建堆相关的函数:

1.void HeapInit(HP* php); 初始化函数:

因为底层的物理结构就是数组,没什么好说的,把数组置空,size和capacity给成0就初始化完成。 

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

2.void HeapPush(HP* php, HPDataType x);  堆添加数据函数:

像这种添加数据的函数上来肯定要判断数组是否满:首先如果是空数组,就让 newcapacity 给成0,不满就给他php->capacity 的2倍,等会扩容完再把newcapacity 给capacity ,然后为了防止扩容失败 就先给a扩容完先赋给一个中间值tmp,判断如果tmp不是NULL扩容成功再给a。 

判空结束后就要把值放进数组,放完以后因为要实现小堆(建成下面图的样子),就要利用向上调整法来实现小堆,下面介绍 AdjustUp 函数

void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		size_t newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a,sizeof(HPDataType)* 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);
}

(2)void AdjustUp(HPDataType* a, size_t child) 向上调整函数: 

假如已有下面的小堆,当最后添加0这个数据时,如何让它回到堆顶?只需找到他的父亲,孩子和父亲比较,如果孩子更小就交换,再刷新child和parent 直到child为0结束(看下面动图解析),如果孩子比父亲大,因为要实现大堆,那就不用再调整了,直接break(堆尾插函数,用向上调整法在尾部插入数据,前提必须是 添加数据之前 这个堆就必须是大堆或者小堆 )

 动图过程:(单纯的交换和刷新child和parent)

void swap(HPDataType* pa, HPDataType* pb)
{
	HPDataType tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

void AdjustUp(HPDataType* a, size_t child)
{
	size_t parent = (child - 1) / 2;
	while (child > 0)
	{
		//if (a[child] > a[parent])
		if (a[child] < a[parent])		//小堆
		{
			swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

3.void HeapPop(HP* php) 堆顶删除函数:

先把堆顶和堆尾数据交换,把堆尾数据删除,再对新的堆进行向下调整法排成小堆,下面介绍 AdjustDown 函数

void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);			//忘写了!!!!!!!4
	swap(&php->a[php->size - 1], &php->a[0]);
	php->size--;
	AdjustDown(php->a,php->size,0);
}

(2) void AdjustDown(HPDataType* a, size_t size, int parent)  向下调整函数(向下调整法建小堆的前提是:务必保证左右子树都是小堆,如果建大堆就要保证左右子树都是大堆):

当交换了堆顶和堆尾数据后是这个样子:

 删除队尾数据,开始向下调整法:先从堆顶开始,通过 child=parent*2+1 找出他的左右孩子中较小的那个和自己比 (先默认左孩子是较小孩子,比较左右孩子,如果右孩子小,就把右孩子赋给child ),如果较小的孩子比父亲小,就交换父亲和孩子,再刷新父亲孩子所指向的对象:新的parent 就是原本的child( parent=child ),新的child就是 新的parent 的孩子( child=新parent*2+1 )。如果建小堆 父亲比较小的孩子还小,那就不用再调整了,直接break,再重复循环进行,直到 child<size 不成立结束。(这里还有一点需要注意,如果当循环到最后一个父亲时,如果这个父亲只有左孩子没有右孩子,那么选较小孩子时,比较左右孩子就会越界访问,因此在选较小孩子时的条件 要判断右孩子坐标是否小于总节点数,如果超出就没必要比较了,不超出才比较)   整个过程看下面动图!!!

void AdjustDown(HPDataType* a, size_t size, int parent)
{
	size_t child = parent * 2 + 1;				//size_t!!!!!!!没写对2

	while (child < size)
	{    //默认左孩子是较小孩子,比较左右孩子,如果右孩子小,就把右赋给child
		if (child + 1 < size && a[child + 1] < a[child]) //小堆//size_t!!!!!!!位置放错了,放while外面了
		{
			child++;
		}
		//if (a[child] > a[parent])
		if (a[child] < a[parent])		//小堆
		{
			swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

 向下调整动图:

(  AdjustDown_better 就是调整为大堆而已,变了个 > )

4.剩下的HeapSize  HeapTop HeapEmpty 都和顺序表的一个做法。

① size_t HeapSize(HP* php) 找堆大小函数:返回php->size 即可

size_t HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

② HPDataType HeapTop(HP* php) 找堆顶数据函数:保证堆不空的情况下返回php->a[0] 即可


HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);		//容易忘写!!!!!!!!!
	return php->a[0];
}

③ bool HeapEmpty(HP* php) 判断堆是否为空函数:判断php->size 是否为0,为0 空就返回true,不是0 非空就返回false 。

bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

堆排序的两类做法:

①堆排序是什么?

通过堆操作把数组排成有序数组。

②为什么要有堆排序?

因为普通遍历排序时间复杂度是O(N²),效率低,堆排序优于直接选择排序,才有价值,建堆的时间复杂度是O(N) (我的下一篇博客会讲解——详解堆排序的时间复杂度) 

先给出主函数:

void test2()
{
	int a[] = { 4, 2, 7, 8, 5, 1, 0, 6 };
	int size = sizeof(a) / sizeof(a[0]);
	HeapSort(a, size);
	int i = 0;
	for (i = 0; i < size; i++)
	{
		printf("%d ", a[i]);
	}
}
int main()
{
	test2();
	return 0;
}

1.void HeapSort(int a[],int size) 堆排序函数第一类写法,但是不推荐哦:

自己创建一个堆hp,通过 堆添加函数 把数组a中的数据一个一个按小堆添加到 堆hp的数组hp->a 中,添加完的堆是小堆,再把堆顶数据赋给 数组a 后,调用堆顶删除函数 删掉堆顶数据,删完后依旧是小堆排列,再重复操作,直到堆为空结束,此时数组a中的数据就是从小到大排列。

void HeapSort(int a[],int size)
{
	HP hp;
	HeapInit(&hp);
	int i = 0;
	for (i = 0; i < size; i++)
	{
		HeapPush(&hp, a[i]);
	}
	HeapPrint(&hp);
	int j = 0;
	while (!HeapEmpty(&hp))
	{
		a[j++] = HeapTop(&hp);
		HeapPop(&hp);
	}
}

这个写法思路虽然简单,但是你觉得好写吗?——不好写啊,因为前提你要自己创建一个堆,然后写出堆的增删查改,利用 HeapPush 堆添加函数和 HeapPop 堆顶删除函数才能实现这个堆排序,啰嗦的很,所以这种方法严重不推荐。能否跨过堆,直接用 AdjustUp/AdjustDown 两种调整法对数组进行操作呢?是可以的,下面就来介绍这两种堆排序方法……

2.void HeapSort2(int a[],int size) 堆排序函数第二类写法,共2种写法:

(1)如何通过向下调整法把乱序的数组排成大堆并排升序:

(即 如何通过向下调整法建大堆 并排升序?)

(AdjustDown_better是把大的数据放在堆顶的向下调整法)

从乱序的数组a的最后一个父亲(即 倒数第一个非叶子节点 )开始进行向下调整法把这个父亲下面的分支排成大堆( 排升序为什么不是建小堆我在下一篇会重点讲,这里一知半解说不清楚 ),因为倒数第一个非叶子节点下面就是叶子了,叶子本身满足左右子树为大堆的前提(叶子既可以看做大堆也可以看做小堆),所以一定可以调成大堆, 再让 父亲-- ,因为前面的操作已经使得下面的子树都排成大堆了,所以可以继续利用向下调整法把从这个父亲开始的整个树排成大堆,重复进行,直到父亲小于0结束。

void HeapSort2(int a[], int size)
{
	int i = 0;
	int end = size - 1;
	for (i = (end - 1) / 2; i >= 0; i--)    //1.向下调整法建大堆
	{
		AdjustDown_better(a, size, i);
	}
	while (end > 0)                        //2.排升序
	{
		swap(&a[0], &a[end]);
		AdjustDown_better(a, end--, 0);
	}
}

举个例子:下面乱序的堆,只要按序号依次进行向下调整,就能逐个建成大堆,直到堆顶最后一次向下调整结束,父亲 i 小于0,结束,就建成了大堆

比如先把①向下调整成大堆(因为本身就是大堆所以不用交换)  ②37>34 交换34和37,就把这部分建成大堆了 ③把49>28 ,交换49,28第三部分大堆就建成  ④18的左右子树已经皆是大堆,可以继续比较并交换,⑤部分同理(下面动态图是向下调整的全过程,顺序就是①->②->③->④->⑤,最后有结束标语)

 

排升序:最后再依次交换队头队尾数据,再排除队尾数据,整体进行向下调整成大堆,(这里end=size-1,所以第一次就不用 -- ,)把大的数据依次放在队尾,最后end到第一个数据就没必要交换了,结束就是排升序。

(2) 如何通过向上调整法建大堆?(堆排序的排序步骤都一样,主要说建大堆步骤)

从乱序数组a的 a[0]开始,对a[0]以上的数据进行向上调整,只不过只有a[0]一个,第一次不用动,所以说i=1,从a[1]开始,,对a[1]以上的数据进行向上调整,a[0]和a[1]比较并交换形成大堆;i=2时,对a[2]进行向上调整算法,因为a[0],a[1]已经是大堆,(满足向上调整法的前提:添加数据之前 这个堆就必须是大堆或者小堆 )所以可以继续把这三个数建成大堆;i=3同理……直到i=size-1最后一个数据进行向上调整算法后,i++,i<size不成立,就结束,此时大堆就已经建成了!

void HeapSort1(int a[], int size)
{
	int i = 0;
	for (i = 1; i<size ; i++)    //1.向上调整法建大堆
	{
		AdjustUp_better(a,  i);
	}
	int end = size - 1;           //2.排升序
	while (end > 0)
	{
		swap(&a[0], &a[end]);
		AdjustDown_better(a, end--, 0);
	}
}

 下面是完整的向上调整法建大堆并排升序:

void AdjustDown_better(HPDataType* a, size_t size, int parent)
{
	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[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapSort1(int a[], int size)
{
	int i = 0;
	for (i = 1; i<size ; i++)
	{
		AdjustUp_better(a,  i);
	}
	int end = size - 1;
	while (end > 0)
	{
		swap(&a[0], &a[end]);
		AdjustDown_better(a, end--, 0);
	}
}

void test2()
{
	int a[] = { 15, 18, 28, 34, 65, 19, 49, 25,37,27 };
	int size = sizeof(a) / sizeof(a[0]);
	HeapSort2 (a, size);
	int i = 0;
	for (i = 0; i < size; i++)
	{
		printf("%d ", a[i]);
	}
}

int main()
{
	test2();
	return 0;
}

 下面是堆增删查改和堆排序的代码实现:

Heap.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
#include<math.h>


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

void HeapInit(HP* php);
void HeapPush(HP* php, HPDataType x);
void HeapPop(HP* php);
void HeapPrint(HP* php);
bool HeapEmpty(HP* php);
HPDataType HeapTop(HP* php);
size_t HeapSize(HP* php);
void HeapDestroy(HP* php);
void swap(HPDataType* pa, HPDataType* pb);
void AdjustUp(HPDataType* a, size_t child);
void AdjustDown(HPDataType* a, size_t size, int parent);

Heap.c

#define _CRT_SECURE_NO_WARNINGS
#include"Heap.h"

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

void HeapPrint(HP* php)
{
	assert(php);
	int i = 0;
	int j = 0;
	int a = 1;
	for (i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
		if (i == j)
		{
			printf("\n");
			j +=pow(2,a++);			//!!!!!!!!!!看答案了1
		}
	}
	printf("\n\n");
}

void swap(HPDataType* pa, HPDataType* pb)
{
	HPDataType tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

void AdjustUp(HPDataType* a, size_t child)
{
	size_t parent = (child - 1) / 2;
	while (child > 0)
	{
		//if (a[child] > a[parent])
		if (a[child] < a[parent])		//小堆
		{
			swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		size_t newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a,sizeof(HPDataType)* 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);
}

void AdjustDown(HPDataType* a, size_t size, int parent)
{
	size_t child = parent * 2 + 1;				//size_t!!!!!!!没写对2

	while (child < size)
	{
		if (child + 1 < size && a[child + 1] < a[child]) //小堆//size_t!!!!!!!位置放错了,放while外面了3
		{
			child++;
		}
		//if (a[child] > a[parent])
		if (a[child] < a[parent])		//小堆
		{
			swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void AdjustDown_better(HPDataType* a, size_t size, int parent)
{
	size_t child = parent * 2 + 1;				//size_t!!!!!!!没写对2

	while (child < size)
	{
		if (child + 1 < size && a[child + 1] > a[child]) //size_t!!!!!!!位置放错了,放while外面了3
		{
			child++;
		}
		if (a[child] > a[parent])		//大堆
		{
			swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);			//忘写了!!!!!!!4
	swap(&php->a[php->size - 1], &php->a[0]);
	php->size--;
	AdjustDown(php->a,php->size,0);
}

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

size_t HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);		//忘写了!!!!!!!!!5
	return php->a[0];
}

bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

Test.c

#define _CRT_SECURE_NO_WARNINGS
#include"Heap.h"

void TestHeap()
{
	HP hp;
	HeapInit(&hp);
	HeapPush(&hp, 1);
	HeapPush(&hp, 5);
	HeapPush(&hp, 0);
	HeapPush(&hp, 8);
	HeapPush(&hp, 3);
	HeapPush(&hp, 9);
	HeapPrint(&hp);

	HeapPop(&hp);
	HeapPrint(&hp);

	if (HeapEmpty(&hp))
	{
		printf("空\n");
	}
	else
	{
		printf("不空\n");
	}
	printf("%d\n", HeapSize(&hp));
	printf("%d\n", HeapTop(&hp));
	HeapDestroy(&hp);
}

void HeapSort(int a[],int size)
{
	HP hp;
	HeapInit(&hp);
	int i = 0;
	for (i = 0; i < size; i++)
	{
		HeapPush(&hp, a[i]);
	}
	HeapPrint(&hp);
	int j = 0;
	while (!HeapEmpty(&hp))
	{
		a[j++] = HeapTop(&hp);
		HeapPop(&hp);
	}

	//while (hp.size > 0)
	//{
	//	swap(&hp.a[0], &hp.a[hp.size - 1]);
	//	hp.size--;
	//	AdjustDown(hp.a, hp.size, 0);
	//}
	//for (i = 0; i < size; i++)
	//{
	//	a[i] = hp.a[i];
	//}
}

void HeapSort2(int a[], int size)
{
	int i = 0;
	int end = size - 1;
	for (i = (end - 1) / 2; i >= 0; i--)
	{
		AdjustDown_better(a, size, i);
	}
	while (end > 0)
	{
		swap(&a[0], &a[end]);
		AdjustDown_better(a, end--, 0);
	}
}

void test2()
{
	int a[] = { 4, 2, 7, 8, 5, 1, 0, 6 };
	int size = sizeof(a) / sizeof(a[0]);
	HeapSort2(a, size);
	int i = 0;
	for (i = 0; i < size; i++)
	{
		printf("%d ", a[i]);
	}
}

int main()
{
	//TestHeap();
	test2();
	return 0;
}

运行结果: 

① TestHeap

  • 32
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 21
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值