堆的实现与操作详解

目录

堆的概念

功能介绍

向上&&向下调整算法

向上调整算法

向下调整算法

堆的创建和销毁

声明

堆的初始化

堆的销毁

pop函数和push函数

push函数

pop函数

功能实现


堆的概念

堆是一个完全二叉树,示意图如下:

堆的表现形式有两种:

1、也是所有树通用的,如图:

这种方法被称为leftchild,rightbrother,即存储两个指针,一个指向它的儿子,一个指向它的兄弟,(整体遵循从左到右的顺序)这是一种非常高明的方式,但是我们今天不使用它。

2、使用数组来进行储存

因为堆中的数据是高度有序的,所以我们可以采用数组存储的方法

在数组中,child和parent有下述关系:

1、leftchild=parent*2+1

2、rightchild=parent*2+2;

功能介绍

#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<stdbool.h>
#include<time.h>
typedef int HpDataType;

typedef struct HP
{
	HpDataType* arr;
	int size;
	int capacity;
}HP;

HP* HPIint();//初始化堆

void DestoryHP(HP* hp);//销毁堆

void HPPush(HP* hp,int val);//向堆中插入数据

void AdjustUp_s(HpDataType* arr, int size);//向上调整

void AdjustDown_s(HpDataType* arr, int size, int parent);//向下调整-小堆

void AdjustDown_b(HpDataType* arr, int size, int parent);//向下调整-大堆

void AdjustUp_b(HpDataType* arr, int size);//向上调整-大堆

void Swap(HpDataType* p1, HpDataType* p2);//交换两个数

HpDataType HPPop(HP* hp);//返回最顶端的数

bool HPIsEmpty(HP* hp);//判断堆是否是空的

向上&&向下调整算法

向上调整算法

向上调整算法是从下往上,从儿子到父亲

parent = (child - 1) / 2;

这一步是通过child找到parent,因为child和parent都是int类型的,leftchild自不必说,rightchild减一除二也可以实现找到parent的效果。

这里的逻辑实际上是在堆里面一层一层往上推,沿途检验数据是否符合标准,不符合就交换。

void AdjustUp_s(HpDataType* arr, int size)//向上调整-小堆
{
	int child = size - 1;
	int parent = 0;
	while (child > 0)
	{
		parent = (child - 1) / 2;
		if (arr[parent] > arr[child])
		{
			Swap(&arr[parent], &arr[child]);
		}
		child = parent;
	}
}

向下调整算法

我们这里以小堆的算法做例子

向下调整算法是从上往下,从父亲到儿子

向下调整算法就比较难了

向下调整算法实际上是从堆的顶端开始,然后找到parent的child在两个child中(如果有的话)选出一个最小的

int small = child;//假设法,避免了if else 的使用
		if (child + 1 < size && arr[child] > arr[child + 1])
		{
			small = child + 1;
		}

在使用这个最小的child和这个parent对比,如果parent小于最小的,那么意味着parent是符合小堆的规则的,这时就可以break了

如果small大于parent,就代表我们的paent是不符合小堆的规则,的我们就得接着遍历,接着交换

void AdjustDown_s(HpDataType* arr, int size,int parent)//向下调整-小堆
{
    //这里多设置了一个接口,是为了方便以后数组建堆算法调用
	int child = 0;
	while (parent * 2 + 1 < size)
	{
		child = parent * 2 + 1;
		int small = child;//假设法,避免了if else 的使用
		if (child + 1 < size && arr[child] > arr[child + 1])
		{
			small = child + 1;
		}

		if (arr[small] < arr[parent])
		{
			Swap(&arr[small], &arr[parent]);
			parent = small;//参数错了,原来是child,这里要注意
		}
		else//因为之前的数据都是有序地,只要在某一次验证中通过了,就不用进行接下来的验证了
		{
			break;
		}
	}
}

我们在pop中使用向下调整算法时,只需要将0作为形参parent传进去(即从0开始)

堆的创建和销毁

声明

typedef struct HP
{
	HpDataType* arr;
	int size;
	int capacity;
}HP;

堆的初始化

HP* HPIint()
{
	HP* hp = (HP*)malloc(sizeof(HP));
	if (NULL == hp)
	{
		perror("malloc");
		return NULL;
	}
	hp->capacity = 4;
	hp->arr = (HpDataType*)malloc(sizeof(HpDataType) * hp->capacity);
	hp->size = 0;
	return hp;
}

此处的arr就是用来储存数据的数组,size是此时堆中元素的个数,同时指向下一个数据填充的位置

capacity储存了数组的大小,后面扩容时会用于判断

堆的销毁

void DestoryHP(HP* hp)
{
	free(hp->arr);
	free(hp);
}

这没啥好说的,arr和hp的空间都是使用malloc动态开辟的,存放在堆区,是不会自动释放的,我们需要手动释放

pop函数和push函数

push函数

在push之前,我们应该先检验一下数组是不是满的,如果不满就要将数组扩容,再进行push的操作

push就是在堆的最后插入一个新的数据,并执行向上调整算法,使堆的结构不被打乱

void HPPush(HP* hp, int val)
{
	if (hp->size == hp->capacity)
	{
		hp->capacity += 4;
		HP* ret = realloc(hp->arr, hp->capacity*sizeof(HpDataType));
		if (NULL != ret)
		{
			hp->arr = ret;
		}
	}
	hp->arr[hp->size] = val;
	hp->size++;
	AdjustUp_s(hp->arr,hp->size);
}

pop函数

在pop之前,我们肯定要检查一下数组是不是空的

pop时,我们pop的数据肯定是根位置的数据(其他的数据pop了是没有意义的),然后我们将pop的数据和最后一个数据交换,再执行向下调整算法。

这一步的交换是为什么呢?

如果我们直接将第一个数据踢出去的话,不仅要将剩下的数据挪动位置,还要面临剩下的数据原来的顺序被全部打乱的问题,这时再进行调整就和重新建了一个堆差不多了

所以我们这里将最后一个数据和根节点的数据交换,再将我们的hp中的size--(代表最后一个数据已经废置不用了),此时我们的堆的结构还是完好的,执行一次向下调整算法就可以将顺序重新调整好

HpDataType HPPop(HP* hp)
{
	if (HPIsEmpty(hp))
	{
		printf("\n警告,堆现在是空的\n");
		return -1;
	}
	else
	{
		HpDataType ret = hp->arr[0];
		Swap(&hp->arr[0], &hp->arr[hp->size - 1]);
		hp->size--;//代表被换下的数据已经是失效的了
		AdjustDown_s(hp->arr, hp->size , 0);
		return ret;
	}
}

功能实现

#include"hp.h"


HP* HPIint()
{
	HP* hp = (HP*)malloc(sizeof(HP));
	if (NULL == hp)
	{
		perror("malloc");
		return NULL;
	}
	hp->capacity = 4;
	hp->arr = (HpDataType*)malloc(sizeof(HpDataType) * hp->capacity);
	hp->size = 0;
	return hp;
}

void Swap(HpDataType* p1, HpDataType* p2)
{
	HpDataType* temp = 0;
	temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

void AdjustUp_s(HpDataType* arr, int size)//向上调整-小堆
{
	int child = size - 1;
	int parent = 0;
	while (child > 0)
	{
		parent = (child - 1) / 2;
		if (arr[parent] > arr[child])
		{
			Swap(&arr[parent], &arr[child]);
		}
		child = parent;
	}
}

void AdjustDown_s(HpDataType* arr, int size,int parent)//向下调整-小堆
{
	int child = 0;
	while (parent * 2 + 1 < size)
	{
		child = parent * 2 + 1;
		int small = child;
		if (child + 1 < size && arr[child] > arr[child + 1])
		{
			small = child + 1;
		}

		if (arr[small] < arr[parent])
		{
			Swap(&arr[small], &arr[parent]);
			parent = small;//参数错了,原来是child
		}
		else
		{
			break;
		}
	}
}

void AdjustUp_b(HpDataType* arr, int size)//向上调整-大堆
{
	//这个可以改为
	int child = size - 1;
	int parent = 0;
	while (child > 0)
	{
		parent = (child - 1) / 2;
		if (arr[parent] < arr[child])
		{
			Swap(&arr[parent], &arr[child]);
		}
		child = parent;
	}
}

void AdjustDown_b(HpDataType* arr, int size,int parent)//向下调整-大堆
{
	int child = 0;
	while (parent * 2 + 1 < size)
	{
		child = parent * 2 + 1;
		int big = child;
		if (child + 1 < size && arr[child] < arr[child + 1])
		{
			big = child + 1;
		}

		if (arr[big] > arr[parent])
		{
			Swap(&arr[big], &arr[parent]);
			parent = big;
		}
		else
		{
			break;
		}
	}
}

void HPPush(HP* hp, int val)
{
	if (hp->size == hp->capacity)
	{
		hp->capacity += 4;
		HP* ret = realloc(hp->arr, hp->capacity*sizeof(HpDataType));
		if (NULL != ret)
		{
			hp->arr = ret;
		}
	}
	hp->arr[hp->size] = val;
	hp->size++;
	AdjustUp_s(hp->arr,hp->size);
}

HpDataType HPPop(HP* hp)
{
	if (HPIsEmpty(hp))
	{
		printf("\n警告,堆现在是空的\n");
		return -1;
	}
	else
	{
		HpDataType ret = hp->arr[0];
		Swap(&hp->arr[0], &hp->arr[hp->size - 1]);
		hp->size--;//代表被换下的数据已经是失效的了
		AdjustDown_s(hp->arr, hp->size , 0);
		return ret;
	}
}

void DestoryHP(HP* hp)
{
	free(hp->arr);
	free(hp);
}

bool HPIsEmpty(HP* hp)
{
	if (hp->size == 0)
	{
		return true;
	}
	else
	{
		return false;
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值