【数据结构】二叉树 & 堆 & 堆的实现

目录

一,树概念及结构

二,二叉树概念及结构 

特殊的二叉树:

二叉树的性质:

二叉树的存储结构:

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

二叉树的顺序结构:

堆的概念及结构:

堆的实现:

建堆:

堆向下调整算法:

堆排序:

建堆的时间复杂度:

四,堆的实现:

堆的初始化

堆的打印

堆的销毁

堆的插入

判空

删除堆顶的数据

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

二叉树销毁

层序遍历

具体代码: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;
}

运行测试: 

二叉树销毁

void BinaryTreeDestory(BTNode*root)
{
	if (root == NULL)
		return;
	BinaryTreeDestory(root->left);
	BinaryTreeDestory(root->right);
	free(root);
}

层序遍历

核心思路:先入第一层——根,上一层出来,带入下一层

具体代码: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);
}
  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值