C语言实现堆

堆的实现

今天来介绍一个新的数据结构:

大家都记得,内存空间存储中,将局部变量,函数参数等存储在栈上,系统自动释放;全局变量存储在静态存储区;动态开辟的内存空间都在堆上开辟,系统不会自动释放空间,由程序员手动释放。

当然,此处的堆是内存空间的一种存储结构

今天我们所介绍的堆结构是一种二叉树结构,主要应用于 系统目录的存储,比如linux系统下目录的存储方式就是一个二叉树的堆来存储,以根目录下分级目录存储

堆是一种二叉树的结构,满足二叉树的性质

堆的性质:

1.堆中某个节点的值总是不大于或不小于其父节点的值;

2.堆总是一棵完全二叉树。

二叉树的性质:

1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1) 个结点.

2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h- 1.

3. 对任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为 n2,则有n0=n2+1

4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=Log2(n+1). (ps:Log2(n+1)是log以2为 底,n+1为对数)

5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,

则对 于序号为i的结点有: 1. 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点

                                          2. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子

                                          3. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子
为直观表示堆结构,小堆结构如下图:以顺序表的方式存储

大堆结构如下图:

堆的实现:

(1)堆的结构创建及初始化

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;

// 堆的构建
void HeapInit(Heap* hp)
{
	if (hp == NULL)
	{
		return;
	}
	hp->_a = NULL;
	hp->_capacity = hp->_size = 0;
}
// 堆的销毁
void HeapDestory(Heap* hp)
{
	free(hp->_a);
	hp->_a = NULL;
	free(hp);
	hp = NULL;
}

(2)维护最小堆  向下调整

找到当前节点的子节点,先找到子节点中最小的一个

                                       如果当前节点大于最小的子节点,交换,更新当前位置为最小孩子位置

                                       否则  跳出,结束调整

void shiftdown(int* arr, int n, int curpos)
{
	int child = 2 * curpos + 1;//根据当前位置,给出左孩子位置由于关系:左孩子+1=右孩子
	while (child < n)//直到孩子索引越界  即再找不到孩子节点
	{
		if (child + 1 < n&&arr[child + 1] < arr[child])//左右孩子比较找最小值
			child++;//右孩子小,child++, 否则  child不变      由于关系:左孩子+1=右孩子
		if (arr[child] < arr[curpos])//a当前位置与孩子中的小值比较  当前位置值大 向下调整
		{
			//交换
			int tmp = arr[child];
			arr[child] = arr[curpos];
			arr[curpos] = tmp;

			curpos = child;//交换完成后  用之前孩子位置 更新当前位置
			child = 2 * curpos + 1;//根据当前位置,给出左孩子位置
		}
		else
		{
			break;
		}
	}
}

(3)维护最小堆,向上调整  

当前节点和父节点比较,如果小于父节点,交换二者,更新当前节点位置为父节点位置,从当前父节点继续向上查找

                                        否则,跳出  ,结束调整

void shiftup(int* newarr, int n, int curpos)
{
	//维护小堆  从底向上找
	//int curpos = n;
	int parent = (curpos - 1) / 2;//根据当前位置,给出左孩子位置由于关系:左孩子+1=右孩子
	while (curpos > 0)
	{
		if (newarr[curpos] < newarr[parent])//和父节点比较
		{
			//交换
			int tmp = newarr[parent];
			newarr[parent] = newarr[curpos];
			newarr[curpos] = tmp;

			curpos = parent;//交换完成后  更新当前位置
			parent = (curpos - 1) / 2;
		}
		else
			break;
	}
}

(4)堆的插入:

尾部插入,然后执行向上调整,维护最小堆

void checkcapacity(Heap *hp)
{
	if (hp->_size == hp->_capacity)
	{
		hp->_capacity = hp->_capacity == 0 ? 1 : 2 * hp->_capacity;
		hp->_a = (HPDataType*)realloc(hp->_a, sizeof(HPDataType)*hp->_capacity);
	}
}
// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	//检测容量
	checkcapacity(hp);
	//尾插
	hp->_a[hp->_size++] = x;
	//向上调整
	shiftup(hp->_a, hp->_size, hp->_size - 1);
}

(5)堆的删除

堆顶元素和堆的最后一个元素交换,然后尾删操作,并维护堆的特性

void swap(int*a, int*b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

// 堆的删除
void HeapPop(Heap* hp)
{
	if (hp == NULL || hp->_size == 0)
		return;
	//堆顶元素和最后一个元素交换
	swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
	//尾删
	hp->_size--;
	//向下调整  维护最小堆  从小到大
	shiftdown(hp->_a, hp->_size, 0);
}

(6)堆顶元素,堆判空

// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	return hp->_a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{
	return hp->_size;
}
// 堆的判空
int HeapEmpty(Heap* hp) 
{
	if (hp == NULL || hp->_size == 0)
		return 1;
	else
		return 0;
}

(7)堆排序:

维护大堆 从小到大排序  维护小堆 从大到小排序

以下图为例:

void HeapSort(int* a, int n)
{
	//1 建堆
	for (int i = (n - 2) / 2; i >= 0; i--)//经过每一个节点的父节点
	{
		shiftdown(a, n, i);//i为调整的起始位置
	}
	while (n > 0)
	{
		swap(&a[0], &a[n - 1]);
		n--;
		shiftdown(a, n, 0);
	}
}

结果如下:从小打到排序前提就是维护大堆

(8)前k大值:

// TopK问题:找出N个数里面最大/最小的前K个问题。
// 比如:未央区排名前10的泡馍,西安交通大学王者荣耀排名前10的韩信,全国排名前10的李白。等等问题都是Topk问题,
// 需要注意:
// 找最大的前K个,建立K个数的小堆
// 找最小的前K个,建立K个数的大堆
void PrintTopK(int* a, int n, int k)
{
	Heap hp;
	HeapInit(&hp);
	for (int i = 0; i < n; i++)
	{
		HeapPush(&hp, a[i]);//建大堆
	}
	while (k--)
	{
		printf("%d ",HeapTop(&hp));
		HeapPop(&hp);
	}
}

测试代码:

void TestTopk()
{
	int n = 10000;
	int*a = (int*)malloc(sizeof(int)*n);
	srand(time(0));
	for (int i = 0; i < n; i++)
	{
		a[i] = rand() % 1000000;
	}
	a[5] = 1000000 + 1;
	a[1231] = 1000000 + 2;
	a[531] = 1000000 + 3;
	a[5121] = 1000000 + 4;
	a[115] = 1000000 + 5;
	a[2335] = 1000000 + 6;
	a[9999] = 1000000 + 7;
	a[76] = 1000000 + 8;
	a[423] = 1000000 + 9;
	a[3144] = 1000000 + 10;
	PrintTopK(a, n, 10);
}

输出结果如下:

最终测试:以维护大堆结构 依次弹出栈顶元素,以及从小到大排序和前k大元素

//*************************************
void test1()
{
	Heap hp;
	HeapInit(&hp);
	HeapPush(&hp, 28);
	HeapPush(&hp, 29);
	HeapPush(&hp, 45);
	HeapPush(&hp, 46);
	HeapPush(&hp, 25);
	HeapPush(&hp, 38);
	HeapPush(&hp, 19);
	HeapPush(&hp, 8);
	while (!HeapEmpty(&hp))
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
}

void test2()
{
	int arr[] = { 100,20,3,6,89,12,15,36,25 };
	int len = sizeof(arr) / sizeof(int);
	HeapSort(arr, len);
	for (int i = 0; i < len; i++)
		printf("%d ", arr[i]);
}

void TestTopk()
{
	int n = 10000;
	int*a = (int*)malloc(sizeof(int)*n);
	srand(time(0));
	for (int i = 0; i < n; i++)
	{
		a[i] = rand() % 1000000;
	}
	a[5] = 1000000 + 1;
	a[1231] = 1000000 + 2;
	a[531] = 1000000 + 3;
	a[5121] = 1000000 + 4;
	a[115] = 1000000 + 5;
	a[2335] = 1000000 + 6;
	a[9999] = 1000000 + 7;
	a[76] = 1000000 + 8;
	a[423] = 1000000 + 9;
	a[3144] = 1000000 + 10;
	PrintTopK(a, n, 10);
}


int main()
{
	printf("大堆堆顶元素依次输出:\n");
	test1();
	printf("\n");
	printf("从小到大依次排序:\n");
	test2();
	printf("\n");
	printf("输出前k大元素:\n");
	TestTopk();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HT . WANG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值