二叉树顺序结构之堆

目录

什么是堆

堆详解

堆的实现

建堆

堆的构建


什么是堆

堆是一种特树的二叉树,如果一颗二叉树满足以下条件,那么它就是一个堆:

  1. 堆是一颗完全二叉树。
  2. 堆中的所有父亲节点都大于(包括等于)或小于(包括等于)自己的孩子节点。

其中前一种堆结构我们称为大堆,后一种堆结构称为小堆。大堆的根节点称为大堆顶,小堆的根节点称为小堆顶。堆顶的数据总是堆中最大或最小的。

堆详解

堆为啥非得是完全二叉树呢?

堆一般用数组来存储,根据层序遍历的方式来存储。如果不是完全二叉树在存储时就会有空间浪费,因此堆才是完全二叉树。

你说堆一般用数组来表示,那我就非常不理解,如果用数组来表示一颗二叉树那树的节点关系那不就全乱了吗?我怎么知道谁是谁的父亲,谁是谁的孩子?

堆的存储结构在物理上是一个数组,在逻辑上是一颗二叉树。它是按照二叉树层序的方式来存储节点的,所以节点间的关系不会乱。而我们可以通过下面的公式来计算一个节点的父亲和左右孩子是谁:

  • 父亲节点:(i-1)/ 2
  • 左孩子节点:i*2 + 1
  • 右孩子节点:i*2 + 2

注:i表示当前节点

 那什么又是层序遍历呢?

如下图,就是字面意思就是一层一层的来存储节点,上一层节点是下一层节点的父亲。

堆的实现

你肯定很好奇如何将一颗普通的完全二叉树,建成一个堆,当然这句话有两种意思:建一个堆和将数组调整成一个堆。

建堆

typedef int HPDataType;
//大堆
typedef struct Heap
{
	HPDataType* a; //堆
	int size; //当前元素
	int capacity; //当前容量
}Heap;

堆的物理结构是一个数组,size表示堆的元素个数、 capacity表示堆的容量大小

关于堆的接口

//初始化
void HeapInit(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的判空
bool HeapEmpty(Heap* hp);

//初始化

void HeapInit (Heap* hp);

//初始化
void HeapInit(Heap* hp)
{
	assert(hp);
	hp->a = (HPDataType*)malloc(sizeof(HPDataType) * 4);
	if (NULL == hp->a)
	{
		perror("malloc fail");
		return;
	}
	hp->capacity = 4;
    hp->size = 0;
}

// 堆的插入
void HeapPush (Heap* hp, HPDataType x);

//向上调整
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	//如果孩子不是根且孩子大于父亲就调整
	while (child != 0 && a[child] > a[parent])
	{
		//交换
		Swap(&a[child], &a[parent]);
		//孩子成为父亲
		child = parent;
		//新孩子的父亲
		parent = (child - 1) / 2;
	}
}

// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	//检查增容
	if (hp->size == hp->capacity)
	{
		HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * hp->capacity * 2);
		if (NULL == tmp)
		{
			perror("realloc fail");
			return;
		}
		hp->a = tmp;
	}
	hp->a[hp->size++] = x;
	//向上调整
	AdjustUp(hp->a,hp->size-1);
}
// 堆的删除

堆的插入是在数组的尾部插入的,但如果就是这样简单插入的话会破坏堆的结构。所以在插入元素后还要进行向上调整:孩子节点与父亲节点进行比较,如果孩子节点大于父亲节点就交换位置在与它的父亲节点比较,直到孩子节点不大于父亲节点或成为堆顶。

// 堆的删除

void HeapPop (Heap* hp);

//向下调整 -- 大
void AdjustLargeDown(HPDataType* a, int size, int parent)
{
	//默认是左孩子
	int child = (parent + 1) * 2 -1;
	while (size > child)
	{
		//右孩子是否存在并大于左孩子
		if (size > child + 1 && a[child + 1] > a[child])
		{
			child++;
		}
		//孩子大于父亲
		if (a[child] > a[parent])
		{
			Swap(&a[child],&a[parent]);
			//让父亲成为孩子
			parent = child;
			//让它成为它孩子
			child = (parent + 1) * 2 - 1;
		}
		else
		{
			break;
		}
	}
}
// 堆的删除
void HeapPop(Heap* hp)
{
    //hp不能为空并且堆不能为空
	assert(hp && !HeapEmpty(hp));
	//删除堆顶元素
	Swap(&hp->a[0],&hp->a[--hp->size]);
	AdjustLargeDown(hp->a,hp->size-1,0);
}

对于堆来说堆底的删除毫无意义,而堆顶的数据又是最大或最小的,所以堆删除删的是堆顶数据。但直接删除会改变堆的结构,所以是先和数组的尾部交换在删除之后进行向下调整。

向下调整:左右孩子中选出一个较大的,在于其进行比较如果小于较大的那个还孩子就进行交换,然后再去调整下一层,直到没有孩子节点为止。

 其他接口

// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	return hp->a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->size;
}
// 堆的判空
bool HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->size == 0;
}
// 堆的销毁
void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->a);
	hp->a = NULL;
	hp->capacity = 0;
	hp->size = 0;
}

堆的构建

这里的建堆的意思是将数组调整成一个堆,将数组调整成堆一般是用于堆排序。

堆的构建:使用向下调整算法,先对最后一个父亲节点进行调整,让后在对该节点减1对其下一个父亲节点进行调整。

//向下调整 -- 小
void AdjustMinorDown(int* a, int size, int parent)
{
	//默认是左孩子
	int child = (parent + 1) * 2 -1;
	while (size > child)
	{
		//右孩子是否存在并小于左孩子
		if (size > child + 1 && a[child + 1] < a[child])
		{
			child++;
		}
		//孩子小于父亲
		if (a[child] < a[parent])
		{
			Swap(&a[child],&a[parent]);
			//让父亲成为孩子
			parent = child;
			//让它成为它孩子
			child = (parent + 1) * 2 - 1;
		}
		else
		{
			break;
		}
	}
}

// 堆的构建 --- 小
void HeapCreate(int* a, int n)
{
	assert(a);
	//建小堆---向下调整
	for (i = (n - 2) / 2; i >= 0; i--)
	{
		//从最后一个父亲节点开始调整
		AdjustMinorDown(a n, i);
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值