数据结构二叉树之堆的实现

Posted Huang_ZhenSheng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构二叉树之堆的实现相关的知识,希望对你有一定的参考价值。

目录

一,树概念及结构

二,二叉树概念及结构 

特殊的二叉树:

二叉树的性质:

二叉树的存储结构:

三,二叉树的顺序结构及实现

二叉树的顺序结构:

堆的概念及结构:

堆的实现:

建堆:

堆向下调整算法:

堆排序:

建堆的时间复杂度:

四,堆的实现:

堆的初始化

堆的打印

堆的销毁

堆的插入

判空

删除堆顶的数据

获取堆顶的数据,也就是最大值,并计算堆的元素个数

具体代码:Gitee堆的实现

五,TopK问题


一,树概念及结构

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

特点:
·有一个特殊的结点,称为根结点,根节点没有前驱结点。
·除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i<= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继,因此,树是递归定义的。

二,二叉树概念及结构 

特殊的二叉树:

1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

完全二叉树的节点个数范围最多:2^k-1,最少:2^(k-1)-1+1 = 2^(k-1) 

二叉树的性质:

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)  

二叉树的存储结构:

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

这里目前所学的知识有限,本文只列出了顺序结构:

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

物理公式:下标表示树中父子关系公式:

leftchild = parent*2+1

rightchild = parent*2+2 

parent = (child-1)/2

三,二叉树的顺序结构及实现

二叉树的顺序结构:

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段

堆的概念及结构:

小根堆(根是最小值):树中所有父亲都小于等于孩子

大根堆(根是最大值):树中所有父亲都大于等于孩子 

堆的实现:

建堆:

给了一个随机的数组,我们可以把这个数组看做完全二叉树,思考如果变成堆(大堆/小堆)

思考:如何将其变为大堆/小堆?

 堆向下调整算法:

 特殊数据的特征:根下面左右子树都是小堆/大堆

堆排序:

代码实现:

void swap(int* px,int* py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}
//向下调整算法(建大堆)
void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;//指向的是左孩子
	while (child < n)
	{
		//选出小的孩子中大的那个
		if (child+1 < n && a[child+1] > a[child])
		{
			child++;
		}
		//如果大的孩子比父亲大,则交换,继续往下调整
		//如果大的孩子比父亲小,则结束调整
		if (a[child] > a[parent])
		{
			swap(&a[child],&a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
//堆排序——高效率
void HeapSort(int* a, int n)
{
	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--;
	}
}
int main()
{
	int a[] = { 27, 15, 19, 18, 28, 34, 65, 49, 25, 37 };
	HeapSort(a,sizeof(a)/sizeof(int));
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		printf(" %d",a[i]);
	}
	return 0;
}

建堆的时间复杂度:

注意点:

第一点:倒着走,第一个非叶子节点的子树开始调,它是最后一个节点的父亲

第二点:堆排序,将最大的数和最后一个数交换,再向下调整,时间复杂度O(N*logN)

四,堆的实现:

 堆的初始化

先初始化为数组a,然后在建堆(这里建大堆)

void HeapInit(Hp* php, HPDataType* a, int n)
{
	assert(php);
	php->a = (HPDataType*)malloc(sizeof(HPDataType)*n);
	if (php->a == NULL)
	{
		printf("malloc fail\\n");
		exit(-1);
	}
	memcpy(php->a, a, sizeof(HPDataType)*n);

	//建堆
	for (int i = (n - 1) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, n, i);
	}
	php->size = n;
	php->capacity = n;
}

初始化调试: 

堆的打印

void HeapPrint(Hp*php)
{
	for (int i = 0; i < php->size; i++)
	{
		printf(" %d", php->a[i]);
	}
	printf("\\n");
}

堆的销毁

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

堆的插入

插入x,首先要先判断是不是满了,满了就需要扩容!

其次,还要继续保持其实堆,这里采用向上调整法

向上调整: 

//向上调整算法:
void AdjustUp(int* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[parent] < a[child])
		{
			swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void HeapPush(Hp* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		HPDataType*tmp = (HPDataType*)realloc(php->a, php->capacity * 2 * sizeof(HPDataType));
		if (php->a == NULL)
		{
			printf("realloc fail");
			exit(-1);
		}
		php->capacity = (php->capacity) * 2;
	}

	php->a[php->size] = x;
	php->size++;

	AdjustUp(php->a, php->size - 1);
}

判空

bool HeapEmpty(Hp* php)
{
	assert(php);
	return php->size == 0;
}

删除堆顶的数据

这里删除堆顶的数据,要先判空,然后删除后还继续保持其是大堆

//删除堆顶即根节点的数据,删除后保持其继续是堆
void HeapPop(Hp* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	AdjustDown(php->a, php->size,0);
}

获取堆顶的数据,也就是最大值,并计算堆的元素个数

//获取堆顶的数据,也就是最值
HPDataType HeapTop(Hp* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	return php->a[0];
}
int HeapSize(Hp* php)
{
	assert(php);
	return php->size;
}

运行测试: 

具体代码:Gitee堆的实现

五,TopK问题

思路:

void PrintTopK(int*a, int n, int k)
{
	Hp hp;
	HeapInit(&hp,a,k);
	for (int i = k; i < n; i++)
	{
		if (a[i]>HeapTop(&hp))
		{
			HeapPop(&hp);
			HeapPush(&hp,a[i]);
		}
	}
	HeapPrint(&hp);
	HeapDestroy(&hp);
}

思考:n个数的数组a——如何用向上调整建堆?

for (int i = 1; i < n; i++)
{
	adjustup(a,i);
}

以上是关于数据结构二叉树之堆的实现的主要内容,如果未能解决你的问题,请参考以下文章

排序算法之堆排序

数据结构与算法之堆

数据结构之堆的插入取值排序(细致讲解+图片演示)

基本排序算法之堆排序

基本排序算法之堆排序

数据结构之堆的相关知识详解