数据结构——堆的实现(详解)

呀哈喽,我是结衣。

堆的介绍

如果有一个关键码的集合K= {k0,k1,k2,…,kn-1},把它的所有元素按照完全二叉树的顺序储存方式储存在一个一维数组中,并满足:Ki<=K2i+1且ki<=K2i+2(Ki>=K2i+1且Ki>-K2i+2)i = 1,2,3…,则称为小堆(或大堆)。将节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫最小堆或小根堆。

性质

堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
数据结构——堆的实现(详解)_第1张图片
大小堆如同所示。

堆的实现

介绍的话就到此为止,下面我们来进行堆的实现。无非就是那几样。

结构体的创建

typedef int HpDataType;
typedef struct heap
{
	HpDataType* a;
	int size;
	int capacity;
}HP;

是不是很熟悉,没错就是顺序表,但实现起来会比顺序表更难一些哦。

函数的声明

我们先把初始化,销毁,插入,删除等等要实现的函数声明一下:

void HpInit(HP* php);
void Hppush(HP* php, HpDataType x);
void Hpdestroy(HP* php);
void Hppop(HP* php);
bool Hpempty(HP* php);
HpDataType Hptop(HP* php);
int Hpsize(HP* php);

好像没多少函数?

初始化

和顺序表相同。

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

销毁

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

也是一样的说。

插入

这里开始就不一样了,我们要想怎么插才能维持小堆的结构。这里的插入是尾插,是直接插进去就不管了吗?
在这里插入图片描述
如果我们在最后插入的数据是一个大于等于56的数就没有问题,但是如果我们插入的是一个小于56的数呢?直接插入就不会满足小堆的结构了。
数据结构——堆的实现(详解)_第2张图片
这里我们插入40,40比56小那么它应该是“父亲”,所以我们就要把他和56调换位置,但是如果我们再多想一想,我插入的是9呢,是不是就还要和10调换一下位置了?对的,这就是小堆的内涵,小的永远再上面。下面我们来写一下代码:

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,sizeof(HpDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size++] = x;
	adjustup(php->a, php->size - 1);
}

中间有个扩容,我们讲过很多次了,就不展开讲了。
我们发现下面我有写了有个函数,向上调整的函数adjustup,没错上面我们讲的谁小于谁交换就要通过这个函数来实现。

向上调整

我们把size-1作为孩子传归来,这个还有一个重要的知识就是**父下标是子下标-1后除以2的结果。**这个用到是整形会自动忽略小数的机制。至于公式怎么来的,我把下标标出来了,你自己推推看就了解了。
数据结构——堆的实现(详解)_第3张图片

void adjustup(HpDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (a[child] < a[parent])
		{
			swap(a,child, parent);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
		
	}
}

里面还有一个swap函数,就是交换函数,它的实现在下面。回到adjustup,里面的判断大家应该是没有问题的吧,子与父交换后,我们就让child来到了parent的位置了,但是我们还要继续向上判断,所以parent改变为新child的父下标。
数据结构——堆的实现(详解)_第4张图片

swap函数

void swap(HpDataType*a,int child, int parent)
{
	HpDataType tmp = a[child];
	a[child] = a[parent];
	a[parent] = tmp;
}

交换就是了

删除

在堆里删除也是一大难点,首先你来猜我们是头删还是尾删?
3
2
1
答案是头删加尾删,单纯尾删在这里没有意义,但这个头删也要用到尾删,你想想如果我们是把头删除了,后面的数再向前覆盖的话,那效率低且不说,而且亲子关系全部乱了。所以绝对不是单纯的头删,我们要把头数据和尾数据交换然后尾删。删除后再向下调整。

void Hppop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	//int child = php->size-1;
	//int parent = (child - 1) / 2;
	swap(php->a, php->size - 1, 0);
	php->size--;
	adjustdown(php->a,0,php->size);
}

向下调整

void adjustdown(HpDataType* a,int parent,int size)
{
	int child = parent * 2 + 1;
	if (a[child] > a[child + 1])
	{
		child++;
	}
	while (child<size)
	{
		if (a[parent] > a[child])
		{
			swap(a, child, parent);
			parent = child;
			child = parent * 2 + 1;
			if (a[child] > a[child + 1])
			{
				child++;
			}
		}
		else
		{
			break;
		}
	}
}

向下调整和向上调整也没太大的区别,都是判断后再交换,你只要清楚循环结束的条件就可以了。最重要的还是判断孩子中较小的数据,这里我们用的是假设法,我们先假设左孩子为小的数据,然后我们在判断如果不对就让孩子++,变成右孩子。

判空

bool Hpempty(HP* php)
{
	assert(php);
	return php->a == NULL;
}

返回顶部数据

HpDataType Hptop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

判断有效数据的个数

int Hpsize(HP* php)
{
	assert(php);
	return php->size;
}

测试

数据结构——堆的实现(详解)_第5张图片

代码

heap.h

#include 
#include 
#include 
#include 
typedef int HpDataType;
typedef struct heap
{
	HpDataType* a;
	int size;
	int capacity;
}HP;
void HpInit(HP* php);
void Hppush(HP* php, HpDataType x);
void Hpdestroy(HP* php);
void Hppop(HP* php);
bool Hpempty(HP* php);
HpDataType Hptop(HP* php);
int Hpsize(HP* php);

heap.c

#include "heap.h"
void HpInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}
void Hpdestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}
void swap(HpDataType*a,int child, int parent)
{
	HpDataType tmp = a[child];
	a[child] = a[parent];
	a[parent] = tmp;
}

void adjustup(HpDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (a[child] < a[parent])
		{
			swap(a,child, parent);
			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,sizeof(HpDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size++] = x;
	adjustup(php->a, php->size - 1);
}

void adjustdown(HpDataType* a,int parent,int size)
{
	int child = parent * 2 + 1;
	if (a[child] > a[child + 1])
	{
		child++;
	}
	while (child<size)
	{
		if (a[parent] > a[child])
		{
			swap(a, child, parent);
			parent = child;
			child = parent * 2 + 1;
			if (a[child] > a[child + 1])
			{
				child++;
			}
		}
		else
		{
			break;
		}
	}
}

void Hppop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	//int child = php->size-1;
	//int parent = (child - 1) / 2;
	swap(php->a, php->size - 1, 0);
	php->size--;
	adjustdown(php->a,0,php->size);
}
bool Hpempty(HP* php)
{
	assert(php);
	return php->a == NULL;
}

HpDataType Hptop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

int Hpsize(HP* php)
{
	assert(php);
	return php->size;
}

test.c

#include "heap.h"

int main()
{
	HP hp;
	HpInit(&hp);
	HpDataType* arr[] = { 21,141,41,1,1,42,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
	{
		Hppush(&hp, arr[i]);
		
	}
	for (int i = 0; i < hp.size; i++)
	{
		printf("%d ", hp.a[i]);
	}
	printf("\n");
	printf("%d ", hp.a[Hptop(&hp)]);
	printf("\n");
	Hppop(&hp);
	Hppop(&hp);
	Hppop(&hp);
	Hppop(&hp);
	Hppop(&hp);
	Hppop(&hp);
	for (int i = 0; i < hp.size; i++)
	{
		printf("%d ", hp.a[i]);
	}
	return 0;
}


数据结构——堆的实现(详解)_第6张图片

你可能感兴趣的:(数据结构,c语言,算法,笔记)