C语言实现堆

前言

        进入堆的学习,代表了整个C语言学习阶段进入了一块新的高度,它不在局限于对于数据的不同存储方式,更是提高了我们对于程序的思想,利用空间想象能力,实现对数据的整理,并且打破了常规处理方式,节约了系统处理时间。


一、堆

定义:

        堆的本质是一个完全二叉树,但是完全二叉树并不是堆。堆作为完全二叉树的一个分支,有两种形式,大堆与小堆,大堆表示每一个结点的儿子的值都小于该节点;小堆与之相反,每一个结点的儿子的值都大于该节点。

C语言实现堆_第1张图片

        我们看到大堆与小堆的两个结构图,看上去像是一个链式结构,层层往下连接,但其实不然,堆的二叉树形式只是我们想象出来的,俗称逻辑结构,但是实际的物理结构却是和顺序表的连续物理空间。当然并不是说堆不能通过链式结构实现,而是顺序结构会实现起来更加方便,使用起来也更便捷。

C语言实现堆_第2张图片

         看到这个结构大家应该也能理解了堆在顺序表中的排列为从堆顶开始一层一层的记录。从这里你们可能不能理解顺序表的优势,甚至感觉将数据打乱成我们不好理解的样子,接下来我讲解一个公式,会让你对这个结构的优势表示感叹。

        从根结点开始,每一个结点都有本身以及两个子结点,这里我们对其称为父亲和儿子结点。因为是完全二叉树形式,所以左儿子的下标为父亲结点下标乘二加一,右儿子的下标为父亲结点下标乘二加二。LeftChild = Parent * 2 + 1;RightChild = Parent * 2 + 2;反过来得知儿子的结点,都可以算出父亲结点下标,又因为C语言有取整这个特性,无论是左孩子还是右孩子都有Parent = (child - 1)/2;


代码:

1.  结构组成、初始化、销毁

        正如我开头所讲解,我的堆实现的底层逻辑为顺序表,所以结构里面包含动态数组,数据个数和容量3个变量表示。

        这里的初始化仅作为对结构的置空,不提前开辟空间。

        销毁时需要注意本身没有动态开辟空间时还要销毁逻辑上有错误,所以得断言一下。

#include
#include
#include
#include
#include
#include

typedef int BElementType;

typedef struct Binary_Tree
{
	BElementType* arr;
	int size;
	int capacity;
}BinaryTree;


//初始化
void BinaryTreeInit(BinaryTree* php)
{
	assert(php);

	php->arr = NULL;
	php->capacity = 0;
	php->size = 0;
}

//销毁
void BinaryTreeDestroy(BinaryTree* php)
{
	assert(php);
	assert(php->arr);
	free(php->arr);
	php->arr = NULL;
	php->capacity = 0;
	php->size = 0;
}

2. 插入、向上调整法

        插入需要考虑的逻辑有几点,一是我们不知道插入的数据大小,二是我们不知道插入后影响了多少结点,三是应该将数据插在哪里。

        我们这里简单的将堆的结构表示为数组,一个数组最简单的插入数据方式为尾插,如果头插那么需要将所有数据都往后移动一位,极为浪费时间,并且没有意义。随机插入我们也不能直接改变堆的排序方式,所以我们只能选择尾插

        尾插之后,我们回到堆的逻辑结构图,它所影响的结点只有它的祖先结点,也即是从根结点找到该尾插结点那一条路径上的所有结点。

        因为在尾插之前,数组当中的所有数据都是按照堆的逻辑排列,所以直接将插入结点与其父节点比较,当为大堆时,儿子大于父亲就交换两个的值,在比较,直到比较到跟结点。

插入:

//插入
void BinaryTreePush(BinaryTree* php, BElementType data)
{
	assert(php);

	//检查容量
	if (php->capacity == php->size)
	{
		php->capacity = php->capacity == 0 ? 4 : php->capacity * 2;
		BElementType* temp = realloc(php->arr, php->capacity*(sizeof(BElementType)));
		if (temp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		php->arr = temp;
	}

	php->arr[php->size] = data;
	php->size++;
	//调整为堆
	AdjustUP(php->arr, php->size - 1);
}

 向上调整:

void Swap(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}

void AdjustUP(BElementType* arr, int index)
{
	assert(arr);

	//插入位置下标
	int child = index;

	//插入位置父亲的下标
	int parent = (child - 1) / 2;

	//比较,交换
	while (child)
	{
		if (arr[child] > arr[parent])
		{
			Swap(arr + child, arr + parent);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

3. 删除、向下调整法

        删除方式的实现主要是利用堆的特性,堆顶一定为最大或则最小,将其与最后一个元素对调位置,那么一定会产生一个结果,交换后的堆顶元素不在是满足堆的排列方式,所以这个时候就需要通过向下比较,谁大交换谁,直到没有儿子比较。就将原来次大的元素排在堆顶位置。

删除:

//删除
void BinaryTreePop(BinaryTree* php)
{
	assert(php);
	assert(php->size);

	//将堆顶与最后元素对调位置
	Swap(&php->arr[0], &php->arr[php->size - 1]);

	//删除最后一个元素的位置,也就是删除堆顶
	php->size--;

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

 向下调整:

//向下调整
void AdjustDown(BElementType* arr, int Max, int parent)
{
	assert(arr);

	int child = parent * 2 + 1;

	//当儿子不存在时,退出
	while (child < Max)
	{
		//比较左右儿子大小
		if (child + 1 < Max && (arr[child] > arr[child + 1]))
		{
			child++;
		}
		//儿子大于父亲时,交换位置
		if (arr[child] < arr[parent])
		{
			Swap(arr + child, arr + parent);
			parent = child;
			child = parent * 2 + 1;
		}
		//当儿子小于父亲时,退出
		else
		{
			break;
		}
	}
}

4. 堆顶元素访问、判空、对数据个数

(太简单啦,不想讲!!!!)

//堆顶元素
BElementType BinaryTreeTop(BinaryTree* php)
{
	assert(php);

	return php->arr[0];
}

//判断堆为空
int BinaryTreeEmpty(BinaryTree* php)
{
	assert(php);

	return php->size == 0;
}

//堆数据个数
int BinaryTreeSize(BinaryTree* php)
{
	assert(php);

	return php->size;
}

以上就是我对堆基本内容的讲解了,接下来还会更新堆排序,和一道关于堆的题

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