[C数据结构]堆

Heap.h

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

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

//堆的初始化
void HeapInit(HP* php);

//堆的销毁
void HeapDestroy(HP* php);

//向上调整(数组地址,向上调整的子节点坐标)
void AdjustUp(HPDataType* a, int child);

//插入新数据,并且保证还是堆。
void HeapPush(HP* php, HPDataType x);

//删除堆顶数据,并且保证还是堆。
void HeapPop(HP* php);

//取堆顶的数据
HPDataType HeapTop(HP* php);

//判断堆是否为空
bool HeapEmpty(HP* php);

//返回堆的节点个数
int HeapSize(HP* php);

//打印堆
void HeapPrint(HP* php);

//向下调整的递归写法
//void DownDiGui(HPDataType* arr, int size, int father);

 Heap.c

 

#include"Heap.h"

//堆的初始化
void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

//堆的销毁
void HeapDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

//(子节点)向上调整成小根堆
void AdjustUp(HPDataType* arr,int child)
{
	int father = (child - 1) / 2;
	while (child > 0)//如果一直向上调整,那么当arr[child]作为根节点时,结束循环。
	{
		if (arr[child] < arr[father])//向上调整一次
		{
			HPDataType tmp = arr[child];
			arr[child] = arr[father];
			arr[father] = tmp;
			child = father;
			father = (child - 1) / 2;//当arr[child]为根节点时,father的值依然是0 因为[(0-1)/2==0]。
		}
		else//当前节点的值大于等于其父节点时,无需再继续调整。
		{
			break;
		}
	}
}

//(父节点)向下调整成小根堆
void AdjustDown(HPDataType* arr,int size,int father)
{
	assert(arr);
	int son = father * 2 + 1;//这个son是左子节点的下标
	while (son < size)//有左子树才能继续向下调整
	{
		//存在右子节点,并且右子节点小于左子节点,son就变成右子节点。son最终表示的都是最小的节点.
		//son+1小于size表示存在右节点,并且这个表达式要写在&&的左边,防止不存在右节点时arr[son+1]越界访问.
		if (son + 1 < size && arr[son + 1] < arr[son])
		{
			son++;
		}
		if (arr[son] < arr[father])//如果最小的子节点小于父节点,就互换两个节点。
		{
			HPDataType tmp = arr[son];
			arr[son] = arr[father];
			arr[father] = tmp;
			father = son;
			son = father * 2 + 1;
		}
		else//最小的子节点都大于父节点,不需要再继续换了,跳出循环。
		{
			break;
		}
	}
}

//插入新数据,并且保证还是堆。
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)//空间不足则扩容
	{
		//首次扩容给4个空间,否则扩容为原来的两倍.
		int new_capacity = ((php->capacity) == 0) ? 4 : (2 * php->capacity);
		//用临时指针tmp接收realloc得到的空间地址
		HPDataType* tmp = (HPDataType*)realloc(php->a,new_capacity * sizeof(HPDataType));
		//tmp不为空则php->a接收tmp.否则扩容失败.
		if (tmp)
		{
			php->a = tmp;
			php->capacity = new_capacity;
		}
		else
		{
			printf("realloc扩容失败\n");
			perror("realloc:");
			return;
		}
	}
	php->a[php->size] = x;
	php->size++;
	//向上调整(小根堆)
	AdjustUp(php->a,php->size-1);
}

//删除堆顶数据,并且保证还是堆。
void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);//至少得有一个数据
	//将最后一个节点和根节点互换后。
	HPDataType tmp = php->a[0];
	php->a[0] = php->a[php->size - 1];
	php->a[php->size - 1] = tmp;
	//删除最后一个节点(原来的根节点)
	php->size--;
	//根节点向下调整
	AdjustDown(php->a, php->size, 0);
	//DownDiGui(php->a, php->size, 0);
}

//取堆顶的数据
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);//保证堆中有数据
	return php->a[0];
}

//判断堆是否为空
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;//判断堆中元素是否为0
}

//返回堆的节点个数
int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

//打印堆
void HeapPrint(HP* php)
{
	assert(php);
	int cen = 1;
	for (int i = 0; i < php->size; i++)
	{
		printf("%2d ", php->a[i]);
		if (i == pow(2, cen) - 2)
		{
			printf("\n");
			cen++;
		}
	}
	printf("\n\n");
}


//向下调整的递归写法(小根堆)
//void DownDiGui(HPDataType* arr, int size, int father)
//{
//	int son = 2 * father + 1;//算出左节点
//	if (son < size)//存在左子树才能进行判断是否与父节点互换.
//	{
//		if (son + 1 < size && arr[son + 1] < arr[son])//存在右节点就选出最小的节点出来
//		{
//			son++;
//		}
//		if (arr[father] > arr[son])//互换节点
//		{
//			HPDataType tmp = arr[father];
//			arr[father] = arr[son];
//			arr[son] = tmp;
//		}
//		DownDiGui(arr, size, son);
//	}
//	else
//	{
//		return;
//	}
//	
//}

Test.c

 

#include"Heap.h"

void HeapTest()//测试写出来的小根堆是否正确
{
	HP hp;
	HeapInit(&hp);
	int arr[] = { 22,41,32,35,47,53,66,71,83,49, };

	//测试新增元素到堆中
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		HeapPush(&hp, arr[i]);
	}
	HeapPrint(&hp);

	//测试删除堆顶
	HeapPop(&hp);
	HeapPrint(&hp);
}

void HeapSortPrintTest()//使用小根堆来升序打印数组元素
{
	HP hp;
	HeapInit(&hp);
	int arr[] = { 22,41,32,35,47,53,66,71,83,49, };
	
	//新增元素到小根堆中
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		HeapPush(&hp, arr[i]);
	}
	HeapPrint(&hp);
	
	//升序打印
	while (HeapSize(&hp))
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
}

/*
* 1、这种排序方法还得先写出一个数据结构。
* 2、并且空间复杂度为O(n^2)效率很低
*/
void HeapSort1()//使用小根堆给数组元素进行排升序
{
	HP hp;
	HeapInit(&hp);
	int arr[] = { 22,41,32,35,47,53,66,71,83,49, };
	
	//新增元素到小根堆中,这里的时间复杂度为O(N*logN)
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		HeapPush(&hp, arr[i]);//该函数使用的是向上调整O(logN)
	}
	HeapPrint(&hp);

	//给数组中的元素排序,这里的时间复杂度为O(N^2)
	int i = 0;
	while (HeapSize(&hp))
	{
		arr[i++] = HeapTop(&hp);//取堆顶元素赋值给对应位置的数组元素
		HeapPop(&hp);//堆顶元素与最后的元素互换后,删除成为最后的元素的堆顶元素,同时对剩下的元素重新建堆(从最后一个分支节点开始向下调整)。
	}
	
	//按顺序打印数组元素
	/*for (int j = 0; j < sizeof(arr) / sizeof(arr[0]); j++)
	{
		printf("%d ", arr[j]);
	}*/
}

void HeapSort2(int* arr,int size)
{
	//向上调整建堆方式:O(N*logN)
	//就当arr[i]是已经插入的新元素,插入新元素就向上调整,这里的函数是调整为小根堆。
	for (int i = 1; i < size; i++)
	{
		AdjustUp(arr, i);//最多可能互换logN次
	}

	//向下调整建堆方式:O(N)
	//向下调整的前提是左子树和右子树都是堆,调整完后整个才是堆,所以必须从最后的第一个分支节点(树)开始调整,叶子节点没有子树无需调整。
	for (int i = (size - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, size, i);//对完全二叉树的每一层进行分析,越是下层,调整的节点个数越多,但是调整的次数就越少,越是上层则反之。
	}

	//建好堆以后形象地打印出来
	int cen = 1;
	for (int i = 0; i < size; i++)
	{
		printf("%2d ", arr[i]);
		if (i == pow(2, cen) - 2)
		{
			printf("\n");
			cen++;
		}
	}
	printf("\n\n");


	//对传递过来的数组元素,选择时间复杂度更小的向下调整的方式建堆。
	//升序建大堆!
	//降序建小堆!(下面的代码是排降序)

	//O(N*logN)
	for (int end = size - 1; end > 0; end--)
	{
		int tmp = arr[0];
		arr[0] = arr[end];
		arr[end] = tmp;

		//向下调整时会和最小的子节点互换,已经排好序的倒数几个节点是不参与互换的,所以互换时左子节点下标必须小于排好序的第一个节点下标。
		AdjustDown(arr, end, 0);

		//交换 调整一次就形象打印出来便于调试观察
		cen = 1;
		for (int i = 0; i < size; i++)
		{
			printf("%2d ", arr[i]);
			if (i == pow(2, cen) - 2)
			{
				printf("\n");
				cen++;
			}
		}
		printf("\n\n");
	}
}



int main()
{
	int arr[] = { 22,41,32,35,47,53,66,71,83,49, };
	HeapSort2(arr,sizeof(arr)/sizeof(int));

	//按顺序打印数组元素
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值