数据结构-----堆的实现

本文详细介绍了堆(包括大根堆和小根堆)的概念,以及如何在数组中表示,涉及节点关系、堆的结构设计、初始化、销毁、插入、删除和堆的创建过程。重点讲解了向上调整和向下调整法,分析了这两种建堆方法的时间复杂度。
摘要由CSDN通过智能技术生成

1.堆的概念

堆的底层是一个完全二叉树,是按照完全二叉树的储存方式放入一维数组中的。同时,堆还分为大根堆和小根堆。大根堆就是根是最大的,小根堆就是根为最小的(子树也是一样的)

 此图就是一个小根堆,根部的数是最小。

 在数组中的储存是这样的:

012345678
589131510141618

 在二叉树的概念中,我们知道有父节点和子节点,两者之间的关系是什么呢?

比如,5的孩子为8和9,那么怎么找到5的两个孩子,或者怎么从孩子找父亲呢?

规律:(很重要哦)

左边孩子的下标(leftchild)=父亲的下标(parent)*2+1;

右边孩子的下标(rightchild)=父亲的下标(parent)*2+2;

父亲的下标(parent)=(孩子的下标(child)-1)/ 2;(这里就不用分左右孩子了)

【英文为下标】

可以来验证一下:

我们取13 ,下标为3。根据上述公式可以得出,其左边孩子下标为3*2+1=7,右边孩子下标为3*2+2=8,发现为左边孩子为16,右边孩子为17,与图中相符,说明公式正确。

同样,我们找下标为6的孩子的父亲,根据公式,其父亲的下标为(6-1)/ 2=2,发现其父亲为9,与图中相符,说明公式正确。


一棵节点为N的完全二叉树,树的高度为h=logN+1【以二为底】

一棵节点为N的满二叉树,树的高度为h=log(N+1)【以二为底】

推导过程: 


 

2.如何实现一个堆

2.1结构设计

用数组储存数据

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

2.2初始化

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

2.3堆的销毁

void HeapDestory(HP* hp)
{
	assert(hp);
	free(hp->a);
	hp->a = NULL;
	hp->capacity = hp->size = 0;

}

2.4堆的插入 

要用到向上调整法。就是拿插入的这个数和上层的父节点进行比较,若小于,则交换(这里是建小堆)。前提是:除了插入的这个节点,其余节点必须满足小堆的特性。

比如:插入一个数据后,要建成小堆。

 比如,插入5。【要用到上面孩子和父亲的公式了】

左边孩子的下标(leftchild)=父亲的下标(parent)*2+1;

右边孩子的下标(rightchild)=父亲的下标(parent)*2+2;

父亲的下标(parent)=(孩子的下标(child)-1)/ 2;(这里就不用分左右孩子了)

【英文为下标】

最开始,child=6,parent=2,将下标所在的两数进行比较,若小于,就交换。然后child=(上一轮)parent=2,parent=(child-1)/2=0, 将下标所在的两数进行比较,若小于,就交换。最后child=(上一轮)parent=0,parent=(child-1)/2,不存在,则循环结束。

代码如下:

void Swap(HPDataType* x, HPDataType* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

void AdjustUp(HPDataType* a, int child)
{
	int 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* hp, HPDataType x)
{
	assert(hp);

	if (hp->capacity == hp->size)
	{
		int newcapacity = 0 ? 4 : 2 * (sizeof(int));
		HPDataType* arr = (HPDataType*)malloc(sizeof(HPDataType) * newcapacity);
		hp->a = arr;
		hp->capacity = newcapacity;
	}
	hp->a[hp->size] = x;
	hp->size++;

	AdjustUp(hp->a, hp->size - 1);//size是数组的个数,size-1才为最后一个元素下标
}

 

 2.5堆的删除

堆的删除是删除堆顶元素。如果不做任何调整的情况下,直接删除堆顶元素,会导致堆的结构遭到破坏,要重新建堆。所以这里我们将堆顶元素和最后一个元素进行交换,删除最后一个元素,然后用向下调整法,完成堆顶元素的删除。

向下调整法就是将数据跟两个孩子中最小的进行比较,如果小于,就交换。(这里建小堆)。前提是:左右子树必须都是一个堆,且两个堆相同,才能进行向下调整法。

 

我们找到了循环条件,但是还存在一种情况。比如这个堆:

 (图源网络)

12的右孩子不存在,所以我们要保证右孩子存在才能进行比较。

代码如下:

void AdjustDown(HPDataType* a, int n, int parent)
{
	//假设法
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] < a[child])//child+1代表右孩子存在
		{
			child++;
		}
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
	
}

// 堆的删除
void HeapPop(HP* hp)
{
	assert(hp);
	assert(hp->size > 0);

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

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

 2.6取堆顶数据

HPDataType HeapTop(HP* hp)
{
	assert(hp);

	return hp->a[0];
}

2.7堆的判空

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

	return hp->size == 0;
}

3.建堆

给你一个数组,进行建堆。

学了上述两种方法,可以知道建堆有两种方法可以解决。但是用向上调整法建堆,时间复杂度更高,所以我们在这里介绍向下调整法建堆

向下调整法有个前提:左右两个子树必须是相同的堆,但是,我们不能保证给的数组满足这个条件,所以我们从最后一个非叶子节点进行向下调整法。

 

代码如下:

void HeapCreate(HP* hp, HPDataType* a, int n)
{
	assert(hp);

	hp->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (hp->a == NULL)
	{
		perror("malloc fail");
		return;
	}
	memcpy(hp->a, a, sizeof(HPDataType) * n);
	hp->capacity = hp->size = n;
	//建堆
	//向上建堆
	/*for (int i = 1; i < hp->size; i++)
	{
		AdjustUp(hp->a, i);
	}*/
	
	//向下调整建堆
	for (int i = (hp->size - 1 - 1) / 2; i > 0; i--)
	{
		AdjustDown(hp->a, hp->size, i);
	}
}

 

 向下建堆的时间复杂度:

向上建堆的时间复杂度:

over~

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值