二叉树——堆(c语言实现)

一,二叉树的概念及结构

1.1,概念

一颗二叉树是节点的有限集合,该节点或者为空,或者是由该节点加上左子树和右子树的两棵树组成。

1.2,结构

 

对于任意的二叉树,都是由以下几种情况符合而成的:

1.3,特殊的二叉树

满二叉树: 一个二叉树,它的每一层节点数都达到最大值,叫做满二叉树。

完全二叉树:完全二叉树是由满二叉树引出来的。对于深度为k,节点个数为n的二叉树,当且仅当其每一个节点都与深度为k的满二叉树中编号从1到n的节点一一对应,称之为完全二叉树。满二叉树是一种特殊的完全二叉树。

二,堆的概念及结构

堆是一颗完全二叉树, 分为大堆和小堆。

小堆:父亲节点小于孩子节点。

大堆:父亲节点大于孩子节点。

:大堆和小堆只要求满足父子间大小的关系,兄弟间的大小关系不确定。

这里堆用的是顺序存储方式,当然也可以用链式存储方式。 顺序存储的有点是:方便找到一个节点的父亲节点和孩子节点,假设一个节点下标为n,那么它的父亲节点下标为(n-)/2,它的左孩子节点下标为2*n+1,右孩子下标为2*n+2.

三,堆的代码实现

1,堆的结构

用一个数组来存储。

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;//按照顺序存储
	int size; //记录数据个数
	int capacity;//空间大小
}HP;
2,功能实现 (小堆)

2.1,尾插一个数据

该过程由两种可能:

重点在于挪动数据,使插入后该结构仍然是一个小堆。

挪动数据的大致思路:将插入的数据与它的父亲节点进行比较,如果小于父亲节点 ,则交换数据,再次与当前节点的父亲节点比较。结束条件:直到大于父亲节点或者达到根节点为止。该过程可以理解为重新建堆。(小堆,大堆与之相反)

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[child] < a[parent])
		{
			swap(&a[child], &a[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 : 2 * php->capacity;
		HPDataType* tmp = (HPDataType*)realloc(sizeof(HPDataType) * newcapacity);
		php->a = tmp;

		php->capacity = newcapacity;
	}
	php->a[php->size] = x;//size为最后一个数据的下一个位置,所以再size处插入
	php->size++;

	//向上调整建堆
	AdjustUp(php->a, php->size-1);
}

2.2,pop删除一个数据

堆的pop删除数据,是删除堆顶的数据。但是如果直接将堆顶的数据pop掉,那么整个堆的关系就全错乱了。

所以再删除数据的时候,先将堆顶的数据和最后一个数据交换,再删除最后一个数据,最后再进行一次调整建堆。

//向下调整建堆
void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = 2 * parent + 1;//左孩子节点
	while (child<n)//超出数据个数停止
	{
		if (child+1< n && a[child] > a[child + 1])
		{
			child++;//如果右孩子小于左孩子,那么右孩子与父亲交换
		}
		if (a[child] < a[parent])
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}
//删除数据
void HPpop(HP* php)
{
	assert(php);

	swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;//删除最后一个数据

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

2.3,top,返回堆顶数据

//返回堆顶数据
HPDataType HPTop(HP* php)
{
	assert(php);

	return php->a[0];
}

 四,堆排序

思路:通过堆的结构,实现的一种排序。

假设要排一组升序,我们可以先将这组数据建堆(大堆),然后将堆顶数据与最后一个数据交换,这样大的数据就在后面了。每交换一次,都要将最后一个数据除外,将剩下的数据进行建堆(大堆),然后再交换,就可以实现排序了。这个思想有点pop删除数据那里的风格。

//堆排序
void HeapSort(int* a, int n)
{
	//升序,建大堆
	//降序,建小堆
	//向上调整建堆
	for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}

	int end = n - 1;

	while (end > 0)
	{
		swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

这里在建堆时用的是向上调整建堆,相比之下,向下调整建堆的时间复杂度更低。

向下调整建堆思路: 

//堆排序
void HeapSort(int* a, int n)
{
	//升序,建大堆
	//降序,建小堆
	//向上调整建堆
	/*for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}*/
	//向下调整建堆
	//从第一个非叶子节点开始
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}

	int end = n - 1;

	while (end > 0)
	{
		swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

总结:堆排序这里用到知识主要是建堆 方面的,向上调整建堆,向下调整建堆。而与堆这个数据结构关系不大。

五,TOP-K问题

TOP-K问题:求数据中最大或最下的前k个,一般数据量较大。

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

对于TOP-K问题,最简单的想法就是排序,但是由于数据量太大,排序就不太可能。最好的方式就是通过堆来实现。

思路:

1,先用前k个数建堆

求前k个最大的元素,建小堆

求前k个最小的元素,建大堆

2,再用剩下的数据与堆顶的数据进行比较,不满足则替换堆顶数据

求前k个最大的元素为例,先建一个右k个数组成的小堆,再将剩下的数据与堆顶数据比较,如果大于堆顶数据,就替换堆顶数据,形成一个新的小堆。最后,这个小堆存放的就是这组数据中最大的前k个。

这里我们简单实现一个求100000个数据中最大的前10个。

首先要创造出这些数据:

void GreateData()
{
	int n = 100000;
	srand((unsigned int)time(0));
	FILE* fin = fopen("data.txt", "w");
	if (fin == NULL)
	{
		printf("fin erro");
		return 0;
	}

	for (int i = 0; i < n; i++)
	{
		int x = (rand() + i)%1000000;//减少重复的数据,并让数据大小保持在1000000以内
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}

运行结果:

 

接下来就要完成建堆,与堆顶元素比较,和交换了。

void test_topk()
{
	int k = 0;
	printf("请输入k:");
	scanf("%d", &k);

	//用kminHeap来存储k个元素
	int* kminHeap = (int*)malloc(sizeof(int) * k);
	FILE* fout = fopen("data.txt", "r");
	//读取文件中k个数到kminHeap
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &kminHeap[i]);
	}

	//将k个数建成小堆,时间复杂度O(N)
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(kminHeap, k, i);
	}

	//读取文件中的元素与堆顶元素进行比较
	int x = 0;
	while (fscanf(fout, "%d", &x)>0)
	{
		if (x > kminHeap[0])
		{
			swap(&x, &kminHeap[0]);
			AdjustDown(kminHeap, k, 0);
		}
	}
	//kminHeap中存储最大的k个元素
	for (int i = 0; i < k; i++)
	{
		printf("%d ", kminHeap[i]);
	}
	printf("\n");

}

六,TOP-K代码总体 

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>


void swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//向下调整建堆
void AdjustDown(int* a, int n, int parent)
{
	int child = 2 * parent + 1;
	while (child<n)
	{
		if (child+1< n && a[child] > a[child + 1])
		{
			child++;
		}
		if (a[child] < a[parent])
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}
//创建数据
void GreateData()
{
	int n = 100000;
	srand((unsigned int)time(0));
	FILE* fin = fopen("data.txt", "w");
	if (fin == NULL)
	{
		printf("fin erro");
		return;
	}

	for (int i = 0; i < n; i++)
	{
		int x = (rand() + i)%1000000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}
void test_topk()
{
	int k = 0;
	printf("请输入k:");
	scanf("%d", &k);

	//用kminHeap来存储k个元素
	int* kminHeap = (int*)malloc(sizeof(int) * k);
	FILE* fout = fopen("data.txt", "r");
	//读取文件中k个数到kminHeap
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &kminHeap[i]);
	}

	//将k个数建成小堆,时间复杂度O(N)
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(kminHeap, k, i);
	}

	//读取文件中的元素与堆顶元素进行比较
	int x = 0;
	while (fscanf(fout, "%d", &x)>0)
	{
		if (x > kminHeap[0])
		{
			swap(&x, &kminHeap[0]);
			AdjustDown(kminHeap, k, 0);
		}
	}
	//kminHeap中存储最大的k个元素
	for (int i = 0; i < k; i++)
	{
		printf("%d ", kminHeap[i]);
	}
	printf("\n");

}
int main()
{
	//GreateData();
	test_topk();

	return 0;
}

运行结果:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值