数据结构-树的基本概念和堆

文章详细介绍了堆的概念,包括完全二叉树的特性,以及堆的建立、初始化、插入数据(push)、删除数据(pop)的过程。通过向上和向下调整维护堆的性质,展示了如何使用堆进行排序。同时,提供了C语言实现的堆数据结构代码示例。
摘要由CSDN通过智能技术生成

树的基本概念

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2.完全二叉树在这里插入图片描述,最后一层要求至少要有一个结点
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述节点个数为N的完全二叉树的高度h为log以2为底N的对数。
在这里插入图片描述
在一颗度为3的树中,设度为i的节点个数为ni, 树总共有n个节点,则n=n0+n1+n2+n3. 有n个节点的树的总边数为n-1条.根据度的定义,总边数与度之间的关系为:n-1=0n0+1n1+2n2+3n3.

堆的基本概念
在这里插入图片描述

堆的建立

选用数组来表示堆的物理结构(存储结构)

堆的信息

用一个结构体来表示堆的信息:堆的空间,堆的大小,堆的容量

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

堆的初始化

断言,该结构体存在,其指针一定存在。为数组开辟空间,size和capacity初始化

void HPInit(HP* php)
{
	assert(php);

	php->a=(HPDataType*)malloc(sizeof(HPDataType) * 4);
	if (php->a==NULL)
	{
		perror("malloc fail");
		return;
	}

	php->size = 0;
	php->capacity = 4;
}

堆的push(数据插入后,仍要遵守堆的规则)

先检查是否需要扩容;直接插到堆的尾部,再做向上调整;
怎么向上调整?和它的parent节点比较,如果比parent小,则不做任何动作,结束; 如果比parent大,则交换child和parent节点的值,再进行和parent节点比较的操作,直至child节点的下标小于等于0(则,parent节点必然不会再有)

在这里插入图片描述

void swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType temp;
	temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
void AdjustUp(HPDataType* a, int child)
{
	//先找到parent和child
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (a[child]>a[parent])
		{
			//交换值,递进到下一个parent和child
			swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			//符合堆的要求,跳出循环
			break;
		}
	}
}
void HeapPush(HP* php, HPDataType x)
{
	assert(php);

	if (php->size==php->capacity)
	{
		HPDataType* temp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity * 2);
		if (temp==NULL)
		{
			perror("realloc fail");
			return;
		}

		php->a = temp;
		php->capacity = php->capacity * 2;
	}
	//直接插入尾部
	php->a[php->size++] = x;
	//向上调整
	AdjustUp(php->a, php->size - 1);
}

测试push的代码如下:

	HP hp;
	HPInit(&hp);

	HeapPush(&hp, 1);
	HeapPush(&hp, 12);
	HeapPush(&hp, 9);
	HeapPush(&hp, 20);

结果如下:
在这里插入图片描述

堆的pop

删除根,原因,现实世界,用于排序。
并不是直接删除,而是将根和尾节点进行交换,将尾节点删除,对根节点做向下调整。
怎么做向下调整?选择和较大的孩子节点作比较,如果比parent小,则不做任何动作,结束;如果比parent大,则交换child和parent节点的值,再进行和parent节点比较的操作,直至child节点的下标大于n(n为节点的个数size)

在这里插入图片描述

bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

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++;
		}

		if (a[parent] < a[child])
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapPop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	swap(&php->a[0],&php->a[php->size-1]);
	//删尾
	php->size--;

	//向下调整
	AdjustDown(php->a, php->size, 0);
}

测试代码如下:

HP hp;
	HPInit(&hp);

	HeapPush(&hp, 1);
	HeapPush(&hp, 12);
	HeapPush(&hp, 9);
	HeapPush(&hp, 20);
	HeapPop(&hp);

结果如下:
在这里插入图片描述

根的元素值

直接返回a[0]

HPDataType HeapTop(HP* php)
{
	assert(php);
	return php->a[0];
}

堆的大小

直接返回size

HPDataType HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

堆的销毁

void HPDestroy(HP* php)
{
	free(php->a);
	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}

演示一个过程,打印出该堆的前k个元素

代码如下:

	HP hp;
	HPInit(&hp);

	HeapPush(&hp, 1);
	HeapPush(&hp, 12);
	HeapPush(&hp, 9);
	HeapPush(&hp, 20);
	HeapPush(&hp, 27);
	HeapPush(&hp, 20);
	HeapPop(&hp);
	
	int k = 3;
	while (!HeapEmpty(&hp) && k--)
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
	return 0;

结果如下:
在这里插入图片描述

全部代码

Heap.h

#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>

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


void HPInit(HP* php);
void HPDestroy(HP* php);

void HeapPush(HP* php, HPDataType x);
void HeapPop(HP* php);

HPDataType HeapTop(HP* php);
HPDataType HeapSize(HP* php);

bool HeapEmpty(HP* php);

//向上调整
void AdjustUp(HPDataType* a,int child);
//向下调整
void AdjustDown(HPDataType* a, int n, int parent);

Heap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"

void HPInit(HP* php)
{
	assert(php);

	php->a=(HPDataType*)malloc(sizeof(HPDataType) * 4);
	if (php->a==NULL)
	{
		perror("malloc fail");
		return;
	}

	php->size = 0;
	php->capacity = 4;
}

void swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType temp;
	temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
void AdjustUp(HPDataType* a, int child)
{
	//先找到parent和child
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (a[child]>a[parent])
		{
			//交换值,递进到下一个parent和child
			swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			//符合堆的要求,跳出循环
			break;
		}
	}
}
void HeapPush(HP* php, HPDataType x)
{
	assert(php);

	if (php->size==php->capacity)
	{
		HPDataType* temp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity * 2);
		if (temp==NULL)
		{
			perror("realloc fail");
			return;
		}

		php->a = temp;
		php->capacity = php->capacity * 2;
	}
	//直接插入尾部
	php->a[php->size++] = x;
	//向上调整
	AdjustUp(php->a, php->size - 1);
}

bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

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++;
		}

		if (a[parent] < a[child])
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapPop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	swap(&php->a[0],&php->a[php->size-1]);
	//删尾
	php->size--;

	//向下调整
	AdjustDown(php->a, php->size, 0);
}

HPDataType HeapTop(HP* php)
{
	assert(php);
	return php->a[0];
}

HPDataType HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

void HPDestroy(HP* php)
{
	free(php->a);
	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"

int main()
{
	HP hp;
	HPInit(&hp);

	HeapPush(&hp, 1);
	HeapPush(&hp, 12);
	HeapPush(&hp, 9);
	HeapPush(&hp, 20);
	HeapPush(&hp, 27);
	HeapPush(&hp, 20);
	HeapPop(&hp);
	
	int k = 3;
	while (!HeapEmpty(&hp) && k--)
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}

	HPDestroy(&hp);
	return 0;
}

堆的应用(排序)

对数组进行排序,比如排升序.
一般情况下,我们会认为排升序,需要建小堆,但这种想法是不正确的,小堆的第一个元素没有问题,但是从第二个节点开始关系可能会混乱,还需要再进行建小堆,时间复杂度会增加。我们这里需要建大堆。
这里可以通过两种方式来建立大堆,向上调整建堆和向下调整建堆。

  • 向上调整建堆:从第二个元素开始,大的child和小的parent交换;
  • 向下调整建堆:从最后一个叶子的parent开始调整,大的child和小的parent交换,依次倒数

在这里插入图片描述
建完大堆后如何实现排序,首先大堆的根一定是最最大,让它和最后一个元素交换位置,则升序的最后一个数字便确定了下来;对此时的首元素向下调整,可以找到次大的元素;依次循环便可以排好序。

向上调整建堆的代码

void HeapSort(int* a, int n)
{
	//向上调整
	for (int i=1;i<n;i++)
	{
		AdjustUp(a, i);
	}

	int end = n - 1;
	while (end>0)
	{
		swap(&a[end], &a[0]);
		AdjustDown(a, end, 0);
		end--;
	}
}
int main()
{
	int a[10] = { 2, 1, 5, 7, 6, 8, 0, 9, 4, 3 };
	HeapSort(a,10);//排升序
	return 0;
}

结果如下:
在这里插入图片描述
向下调整建堆的代码

void HeapSort(int* a, int n)
{
	//向上调整
	//for (int i=1;i<n;i++)
	//{
	//	AdjustUp(a, i);
	//}
	//向下调整
	for (int i = (n-1-1)/2; i>=0; i--)
	{
		AdjustDown(a,n,i);
	}

	int end = n - 1;
	while (end>0)
	{
		swap(&a[end], &a[0]);
		AdjustDown(a, end, 0);
		end--;
	}
}
int main()
{
	int a[10] = { 2, 1, 5, 7, 6, 8, 0, 9, 4, 3 };
	HeapSort(a,10);//排升序
	return 0;
}

结果如下:
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值