C语言--数据结构:堆(完全二叉树)

1、性质:

堆是一种完全二叉树,其特点在于它分为大堆和小堆。

堆具有以下几个重要的性质:

堆序性质:在堆中,根节点的值总是大于或小于其子节点的值,这个性质使得堆能够快速找到最大或最小值。

堆的高度:对于具有n个节点的堆,其高度为log2(n),这使得堆的插入和删除操作具有较好的时间复杂度。

堆的表示:通常使用数组来表示堆,其中根节点位于索引0,而子节点i的父节点位于索引floor((i-1)/2)。

常见操作

堆支持许多常见的操作,包括但不限于:

  • 最大堆(Max Heap):父节点的值大于或等于其子节点的值。
  • 最小堆(Min Heap):父节点的值小于或等于其子节点的值。
  • 堆序性质:在堆中,根节点的值总是大于或小于其子节点的值。
  • 完全二叉树:通常用于实现堆的底层数据结构,具有所有层次都填满,最后一层从左到右依次填充的特点。
  • 插入:将新元素插入到堆中的适当位置,并保持堆的性质不变。
  • 删除:删除堆中的根节点,并保持堆的性质不变。
  • 堆化:将一个无序数组转换为堆的过程,通常包括建立最大堆或最小堆。
  • 堆排序:使用堆进行排序的一种有效方法。

 实际应用

  • 优先队列:堆通常用于实现优先队列,其中具有最高优先级的元素总是最先出队。
  • 调度算法:在操作系统中,堆可以用于调度进程或任务,以便根据优先级动态调整执行顺序。
  • 图算法:在图算法中,堆可以用于实现Dijkstra算法等最短路径算法。
  • 内存管理:在某些内存管理算法中,堆可以用于分配和释放内存块。 

二、原理

我讲大堆,小堆只需要改函数中的几个比较运算符即可。

大堆:

我们还是用顺序表来表示的堆。

堆的难度主要在于增加和删除。

创建:

我们用数据表来表示堆,父亲和孩子就用二叉树的表示:

typedef int HeapDataType;
typedef struct Heap
{
	HeapDataType* _a;
	int _size;
	int _capacity;
}Heap;

的图示如上。

当我们插入一个元素时,我们要在最后插入,然后与父节点比较,如果大于父节点就要交换位置,并再次与新的父节点比较;直到小于父节点,或者到根时停止。

由于每次都是向上的比较,所以这种方式叫向上调整

删除,是删除堆顶的值,因为删除堆底的值意义不大。

删除的时候我们要先把堆顶的数据与顺序表最后的一个数据调换位置,并删除,然后向下调整。我们不能直接删除数据,因为那样删除后我们不仅要将后面的数据往前挪动一位,而且挪动后的数据还大概率不是堆。

向上调整

void AdjustUp(HeapDataType* a, int n, int child)
{
	assert(a);

	while (child > 0)
	{
		int parent = (child - 1) / 2;
		if (a[child] < a[parent])
		{
			int tmp = a[parent];
			a[parent] = a[child];
			a[child] = tmp;
			child = parent;
		}
		else
		{
			return;
		}
	}
}

n是堆的数据个数,child是传入的需要向上调整的第几个数据。

我们需要让传入的孩子与父亲比较,当孩子小于父亲或者孩子到达根的位置就退出循环。

向下调整

void AdjustDown(HeapDataType* a, int n, int parent)
{
	assert(a);
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child] > a[child + 1])
		{
			child++;
		}
		if (a[parent] > a[child])
		{
			int tmp = a[child];
			a[child] = a[parent];
			a[parent] = tmp;
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			return;
		}
	}
}

n是堆的数据个数,parent是传入的需要向下调整的第几个数据。

当parent小于俩孩子的时候,让较大的孩子与parent交换,直到parent都大于俩孩子,或者parent到达叶子的时候停止。

if (child + 1 < n && a[child] > a[child + 1])

这一句话的意思是让较大的孩子与父亲比较,但是为什么加上child + 1 < n?我们定义孩子的时候默认的创的左孩子,但是有左孩子不一定有有孩子,所以我们要加上这个限制,防止访问越界。

以下是一个堆的代码:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int HeapDataType;

typedef struct Heap
{
	HeapDataType* _a;
	int _size;
	int _capacity;
}Heap;

//向上调整
void AdjustUp(HeapDataType* a, int n, int child);
//向下调整
void AdjustDown(HeapDataType* a, int n, int parent);
//初始化堆
void HeapCreat(Heap* HP, HeapDataType* a, int n);
//插入
void HeapPush(Heap* HP, HeapDataType x);
//删除
void HeapPop(Heap* HP);
//取堆顶元素
HeapDataType HeapTop(Heap* HP);
//取堆大小
int HeapSize(Heap* HP);
//判断堆空
int HeapEmpty(Heap* HP);
//销毁堆
void HeapDestory(Heap* HP);

void AdjustUp(HeapDataType* a, int n, int child)
{
	assert(a);

	while (child > 0)
	{
		int parent = (child - 1) / 2;
		if (a[child] < a[parent])
		{
			int tmp = a[parent];
			a[parent] = a[child];
			a[child] = tmp;
			child = parent;
		}
		else
		{
			return;
		}
	}
}

void AdjustDown(HeapDataType* a, int n, int parent)
{
	assert(a);
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child] > a[child + 1])
		{
			child++;
		}
		if (a[parent] > a[child])
		{
			int tmp = a[child];
			a[child] = a[parent];
			a[parent] = tmp;
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			return;
		}
	}
}



void HeapCreat(Heap* HP, HeapDataType* a, int n)
{
	assert(HP && a);
	HP->_a = (HeapDataType*)malloc(sizeof(HeapDataType) * n);
	for (int i = 0; i < n; i++)
	{
		HP->_a[i] = a[i];
	}
	HP->_size = HP->_capacity = n;

	for (int i = (n - 2) / 2; i >= 0; i--)
	{
		AdjustDown(HP->_a, HP->_size, i);
	}

}

void HeapPush(Heap* HP, HeapDataType x)
{
	assert(HP);
	if (HP->_size >= HP->_capacity)
	{
		int newcapacity = HP->_capacity == 0 ? 4 : HP->_capacity * 2;
		HeapDataType* tmp = (HeapDataType*)realloc(HP->_a, sizeof(HeapDataType) * newcapacity);
		HP->_capacity = newcapacity;
	}
	HP->_a[HP->_size] = x;
	HP->_size++;
	AdjustUp(HP->_a, HP->_size, HP->_size - 1);
}

void HeapPop(Heap* HP)
{
	assert(HP);
	if (HeapEmpty(HP))
	{
		perror("HeapPop Empty");
	}
	if (HP->_size == 1)
	{
		HP->_size--;
	}
	else
	{
		HP->_a[0] = HP->_a[HP->_size - 1];
		HP->_size--;
		AdjustDown(HP->_a, HP->_size, 0);
	}
}

HeapDataType HeapTop(Heap* HP)
{
	assert(HP);
	return HP->_a[0];
}

int HeapSize(Heap* HP)
{
	assert(HP);
	return HP->_size;
}

int HeapEmpty(Heap* HP)
{
	assert(HP);
	return HP->_size == 0;
}

void HeapDestory(Heap* HP)
{
	assert(HP);
	free(HP->_a);
	HP->_a = NULL;
	HP->_capacity = HP->_size = 0;
}

int main()
{
	HeapDataType a[] = { 2,8,7,6,5,4,1 };
	Heap HP;
	HeapCreat(&HP, a, sizeof(a) / sizeof(int));
	HeapPush(&HP, 1);
	HeapPush(&HP, -2);
	HeapPush(&HP, -6);
	HeapPush(&HP, -10);
	HeapPush(&HP, -100);
	while (!HeapEmpty(&HP))
	{
		printf("%d %d\n", HeapTop(&HP), HeapSize(&HP));
		HeapPop(&HP);
	}
	HeapEmpty(&HP);
	return 0;
}

结论: 堆是一种强大而灵活的数据结构,能够有效地解决各种算法和数据处理问题。通过理解堆的基本概念、操作和应用场景,我们能够更好地利用堆来优化算法的性能,提高程序的效率。

  • 43
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值