堆及堆排序的实现

目录

一. 堆的概念

1. 结构

 2. 通过下标计算堆的父子关系 公式

3.堆的两个特性

二. 堆的实现

1. 节点的定义

2. 初始化

3. 向上调整算法

4. 向下调整算法

5. 入堆

6. 出堆

7. 返回堆顶数据

8. 判断堆是否为空

9. 销毁堆

三. 堆排序

 堆排序实现方法


一. 堆的概念

1. 结构

堆的逻辑结构是一颗完全二叉树

堆的物理结构是一个数组

 2. 通过下标计算堆的父子关系 公式

左孩子==父亲节点*2+1

右孩子==父亲节点*2+2

父亲节点==(左孩子或右孩子-1)除以2

3.堆的两个特性

1.结构性:用数组表示的完全二叉树

2. 有序性:任意节点的值是其子树所有节点的最大值或最小值(看是大堆还是小堆)

最大堆(MaxHeap)也称大顶堆:最大值

最小堆(MinHeap)也称小顶堆:最小值

大堆要求:树中所有父亲都大于等于孩子

小堆要求:树中所有父亲都小于等于孩子

二. 堆的实现

1. 节点的定义
typedef int HeapType;

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

void Swap(HeapType* a, HeapType* b)//需要交换函数
{
	HeapType tmp = *a;
	*a = *b;
	*b = tmp;
}
2. 初始化
void HeapInit(HP* php)
{
	php->a = NULL;
	php->capacity = php->size = 0;
}
3. 向上调整算法

可用来建堆时间复杂度为O(N*logN)

孩子与父亲比较,父亲比孩子大就交换,再看父亲的父亲是否大于父亲以此类推直到遇见孩子大于父亲,如下图所示

1. 假设传入下标指向的值是16 与其父亲18比较

2. 父亲18大于16 交换得到

3. 此时16 变为18的父亲,16此时的父亲是15,与15比较

16并不大于15所以不交换,向上调整完毕退出循环(上图即为结果图)

void AdjustUp(HeapType* a, int child)//向上调整,数组排序从1开始到数组结束
{
	int parent = (child - 1) / 2;

	while (child > 0)
	{
		if (a[parent] > a[child])//
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
4. 向下调整算法

可用来建堆时间复杂度为O(N)

从根节点开始选出左右孩子中小的一个,跟父亲比较,比父亲小就交换,不断往下比较直到遇见孩子大于父亲或者调到叶子结点终止,步骤如下图所示

1.  19和15比较谁更小,小的与27比较

2. 显然15更小,并且15比27小,两数交换

比较18与28谁更小,选出18 与27比较

3. 18比27更小交换得到

void AdjustDown(HeapType* a, int n, int root)//n为数组的元素个数,root为根
{
	int parent = root;
	int child = root * 2 + 1;//孩子默认为左孩子
	while (child < n)
	{
		//比较左右孩子大小
		if (a[child + 1] > a[child] && child + 1 < n)//> 是找大堆  <是找小堆
		{
			child++;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;//如果左右子树不是小堆或大堆,检查不了后面是否有更大或更小的
		}
	}
	//while (child < n)
	//{
	//	//比较左右孩子大小
	//	if (a[child + 1] < a[child] && child + 1 < n)//> 是找大堆  <是找小堆
	//	{
	//		child++;
	//	}
	//	if (a[child] < a[parent])
	//	{
	//		Swap(&a[child], &a[parent]);
	//		parent = child;
	//		child = parent * 2 + 1;
	//	}
	//	else
	//	{
	//		break;//如果左右子树不是小堆或大堆,检查不了后面是否有更大或更小的
	//	}
	//}
}
5. 入堆
void HeapPush(HP* php,HeapType x)
{
	assert(php);
	if(php->capacity==php->size)
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HeapType* tmp = realloc(php->a, sizeof(HeapType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a, php->size - 1);
}
6. 出堆

出堆是出堆顶数据

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);
}
7. 返回堆顶数据
HeapType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}
8. 判断堆是否为空
bool HeapEmpty(HP* php)
{
	assert(php);

	if (php->size == 0)
		return true;
	else
		return false;
}
9. 销毁堆
void HeapDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->capacity = php->size = 0;
}

三. 堆排序

堆排序时间复杂度为O(N*logN)

特别注意,数组排序排成升序要建大堆,降序要建小堆

为什么呢?

如果是建小堆的话,最小的数在堆顶已经选出来了。那么在剩下的数中再选数,但是剩下的树结构已经乱了,需要重新建堆才能选出下一个数(因为向下调整算法和向上调整算法都需要是大堆或小堆才可以实现),建堆的时间复杂度为O(N),这样不是不可以,但堆排序就没有效率优势了

 堆排序实现方法

若是排升序

我们可以将第一个数M和最后一个数N交换,并且不再将M其视作堆里的一员(大顶堆)

N进行向下调整,选出次大的数在跟倒数第二个位置的数交换,以此类推

实现代码为

void AdjustDown(HeapType* a, int n, int root)//n为数组的元素个数,root为根
{
	int parent = root;
	int child = root * 2 + 1;//孩子默认为左孩子
	while (child < n)
	{
		//比较左右孩子大小
		if (a[child + 1] > a[child] && child + 1 < n)//> 是找大堆  <是找小堆
		{
			child++;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;//如果左右子树不是小堆或大堆,检查不了后面是否有更大或更小的
		}
	}
	//while (child < n)
	//{
	//	//比较左右孩子大小
	//	if (a[child + 1] < a[child] && child + 1 < n)//> 是找大堆  <是找小堆
	//	{
	//		child++;
	//	}
	//	if (a[child] < a[parent])
	//	{
	//		Swap(&a[child], &a[parent]);
	//		parent = child;
	//		child = parent * 2 + 1;
	//	}
	//	else
	//	{
	//		break;//如果左右子树不是小堆或大堆,检查不了后面是否有更大或更小的
	//	}
	//}
}
void HeapSort(int* a,int n)
{
	//建堆时间复杂度O(N)
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)//建堆大堆(也可建小堆),从最后一个叶子的父亲开始,往上(为了都是小堆\大堆)
	{
		AdjustDown(a, n, i);
	}
	//for (int i = 1; i < n; i++)
	//{
	//	AdjustUp(a, i);
	//}
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}
void test2()
{
	int a[] = { 4,6,2,3,77,54,12};//35,123,456,258 
	HeapSort(a, sizeof(a) / sizeof(int));
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		printf("%d  ", a[i]);
	}
}

值得一提的是用向下排序算法建堆要从最后一个节点的父亲节点开始往上遍历(n为元素个数)

	for (int i = (n - 1 - 1) / 2; i >= 0; i--)//建堆大堆(也可建小堆),从最后一个叶子的父亲开始,往上(为了都是小堆\大堆)
	{
		AdjustDown(a, n, i);
	}

用向上排序算法建堆要从下标为1出开始到数组结束

	for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}

这篇文章就到这里了,希望可以帮到你~

(๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤

  • 24
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值