二叉堆的实现

文章目录

    • 堆的概念及性质
  • 二叉堆的实现
    • Heap.h
    • Heap.c
      • 堆的初始化
      • 堆的销毁
      • 向堆中插入数据
      • 删除堆中的数据
      • 找堆顶元素
      • 判断堆是否为空
      • Heap.c完整代码
    • test.c

堆的概念及性质

二叉堆的实现_第1张图片
二叉堆的实现_第2张图片

二叉堆的实现

Heap.h

#pragma once
#include
#include
#include
#include

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

void HPInit(HP* php);
void HPDestroy(HP* php);
void HPPush(HP* php, HPDataType x);
void HPPop(HP* php);
HPDataType HPTop(HP* php);
bool HPEmpty(HP* php);

void Adjustup(HPDataType* a, int child);

Heap.c

堆的初始化

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

堆的本质是数组,初始化让数组首地址指向空,数组大小(元素个数)和容量赋值为0.

堆的销毁

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

首先断言确保传入的堆存在,再释数组地址并置空,将数组大小和容量重置为0

向堆中插入数据

void HPPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, newcapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;
	Adjustup(php->a, php->size - 1);
}

前面先判断数组的空间是否足够,不够要先进行扩容,用newcapacity代表新数组的容量,如果初始容量为0则给newcapacity赋值为4,如果一开始有容量就将容量扩大为原来的2倍。(为什么扩容2倍呢?因为如果选更大的倍数扩容容易浪费过多的内存空间,而每次扩容太少又容易造成多次扩容的情况,增加算法是时间复杂度。)扩容完后将a指向新扩容的地址,新的容量赋值给capacity。如果扩容失败程序结束。

	php->a[php->size] = x;
	php->size++;
	Adjustup(php->a, php->size - 1);

之后将要插入的值赋给a的末位,然后根据堆的性质要对新插入的数进行位置调整,这里将向上调整用另一个函数实现。

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void Adjustup(HPDataType*a,int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

这里实现的是大堆(根节点最大)的向上调整,swap函数对要调整的两值进行交换,首先我们要知道在二叉堆中子节点和父节点的关系。
二叉堆的实现_第3张图片
有图可以看出56和30是70的第一,第二孩子,我们很容易得到他们的下标关系,孩子1的下标是父节点下标的2倍+1,孩子2的下标是父节点下标的2倍+2。由此我们能反推父节点的下标是(子节点下标-1)/2(整数的除法省略小数,故2个孩子这样计算的结果都是父节点的下标)。接下来就是判断大小,子节点的数大就和父节点交换,再和新的父节点比较,直到找到合适的位置或称为根节点就调整完成了。

删除堆中的数据

删除堆中的数据之前我们要先考虑删除数据后的效果,因为我们堆的性质是根节点最大或最小,所以删根节点能让我们找出堆中数据的大小循序。首先我们直接删根节点的数据试试,在上图的大堆中,我们如果直接把70删去,那么重新构成大堆就要下面的数据一一向上调整,很麻烦。所以我们不妨将根节点和尾节点交换位置,交换后将就的根节点删除,新的根节点执行一次向下调整就好了。

void HPPop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	php->a[0] = php->a[php->size - 1];
	php->size--;
	Adjustdown(php->a, php->size, 0);

}

这里同样将向下调整放在另一函数里

void Adjustdown(HPDataType* a, int size,int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child+1<size&&a[child + 1] >a[child])
		{
			child++;
		}
		if (a[parent] <a[child])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

大堆中向下调整是和子节点中大的值交换,一开始我们不知道哪个子节点是更大的,所以不妨假设第一个子节点最大,如果假设不成立就让子节点++,变成第二子节点

	if (child+1<size&&a[child + 1] >a[child])
		{
			child++;
		}

找堆顶元素


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

判断堆是否为空

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

这里判空的作用便于遍历堆的元素。

Heap.c完整代码

#include"Heap.h"

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


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

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void Adjustup(HPDataType*a,int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void HPPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, newcapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;
	Adjustup(php->a, php->size - 1);
}

void Adjustdown(HPDataType* a, int size,int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child+1<size&&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 HPPop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	php->a[0] = php->a[php->size - 1];
	php->size--;
	Adjustdown(php->a, php->size, 0);

}


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

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

test.c

接下来看看我们实现的大堆的效果,第一行是打印堆的元素,第二是利用堆来排序

#include"Heap.h"

int main()
{
	HP hp;
	HPInit(&hp);
	int a[] = { 2,8,6,49,5,3,11,3,7 };
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HPPush(&hp, a[i]);
	}
	for (int i = 0; i < 9; i++)
	{
		printf("%d ", hp.a[i]);
	}
	printf("\n");
	while (!HPEmpty(&hp))
	{
		printf("%d ", HPTop(&hp));
		HPPop(&hp);
	}
	return 0;

二叉堆的实现_第4张图片

你可能感兴趣的:(数据结构,数据结构,算法)