二叉树介绍及堆

文章目录

概念及结构

二叉树

概念及结构

特殊的二叉树

完全二叉树

满二叉树

性质

储存

顺序存储

链式储存

概念及结构

小堆

大堆

建堆

向上调整建堆

向下调整建堆

TOPK问题

法一:

法二:


概念及结构

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

  • 树的第一个结点被称为根结点。
  • 除根结点外,其余结点被分成M(M>0)个互不相交的集合T1、T2.....、Tm,其中每一个集合Ti(1<=i<=m)又是一颗结构与树类似的子树。所以树是用递归定义的。

下面是树的常见相关概念

  • 度:一个结点含有的子树的个数称为该结点的度。如上图:A的度为6。
  • 叶结点:度为0的节点称为叶结点。如上图:P是叶结点。
  • 子结点:一个结点含有的子树的根结点称为该结点的子结点。如上图:B是A的子结点。
  • 父结点:若一个结点含有子结点,则这个结点称为父结点。如上图:A是B的父结点。
  • 树的高度或深度:树中节点的最大层次。如上图:该树的高度是4。

注意:子树之间不能有交集(F只能有一个父结点)。

二叉树

概念及结构

二叉树是一种特殊的树,其特点是每个结点最多只能有两棵子树,且有左右之分。所以二叉树是有序树。

一般称左边的树为左子树,右边为右子树最上边的结点是根结点。

特殊的二叉树

完全二叉树

假设该树有h层,则前h-1层的结点数都达到了最大,第h层结点连续集中在左边。

满二叉树

如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。

满二叉树是属于完全二叉树的。

性质

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

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

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

  3. 若规定根结点的层数为1,具有n个结点的满二叉树的深度,h= log2(n+1)。(log以2为底,n+1为对数)

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

(1)若i>0,i位置结点的父结点序号:(i-1)/2;若i=0,i为根结点无父结点。

(2)若2*i+1<n,左孩子序号:2i+1,若2*i+1>=n否则无左孩子。

(3)若2*i+2<n,右孩子序号:2i+2,若2*i+2>=n否则无右孩子。

储存

顺序存储

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。

在物理上是数组,在逻辑上是二叉树。

链式储存

链式结构存储就是用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。每个结点通常有三个域,数据域和左右指针域。

概念及结构

堆是一种特殊的树形数据结构,通常表现为一棵完全二叉树,是用二叉树的顺序存储方式来存储元素的。

堆分为小堆和大堆

小堆

父结点小于子结点,但是父结点下面的两个子结点没大小区分。

大堆

父结点大于子结点,但是父结点下面的两个子结点没大小区分。

建堆

以下均为建小堆

向上调整建堆

适用于一边插入一边建堆的情况。

找到该孩子的父结点(利用上面性质5),在让父结点与它进行比较如果小于它则退出,如果大于则交换位置,重复上述操作直至循环结束或跳出循环。

cpp 复制代码
void AdjustUp(int* arr, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (arr[child] < arr[parent])//将<改为>即建大堆
		{
			Swap(&arr[child], &arr[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
			break;
	}
}

注:Swap是交换位置函数。

cpp 复制代码
void Swap(int* p1, int* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

向下调整建堆

适用于对根节点进行调整。

找到它的两孩子中小的那个,在让这个小的孩子与它进行比较如果孩子大于它则退出,如果小于则交换位置,重复上述操作直至循环结束或跳出循环。

cpp 复制代码
void AdjustDown(int* arr, int n, int parent)
{
	int child = (parent * 2) + 1;
	while (child < n)
	{
		if (child + 1 < n && arr[child + 1] < arr[child])//child + 1 < n防止非法访问,将后面的<改为>即建大堆
        {
			child++;
        }
		if (arr[parent] > arr[child])//将<改为>即建大堆
		{
			Swap(&arr[parent], &arr[child]);
			parent = child;
			child = (parent * 2) + 1;
		}
		else
			break;
	}
}
//以上两个大小号均需要更改才能是建大堆

TOPK问题

TOPK问题:即求数据结合中前K个最大的元素或者最小的元素。一般来说数据量比较大。

如果有一个数组,数组里有N个数据,我们要找最大的前K个数据,要怎么做呢?

找最大的前K个元素建大堆。

找最小的前K个元素建小堆。

法一:

先建一个大堆,然后让第一个结点的数据与最后一个结点交换,拿出最后的那个结点,然后再让根结点向下调整。重复上述操作K次。

cpp 复制代码
void Heapsort(int* a, int n)
{
	int k = 0;
	scanf("%d", &k);
	for (int i = 0; i < n; i++)
	{
		AdjustUp(a, i);
	}
	while (k--)
	{
		Swap(&a[n - 1], &a[0]);
		AdjustDown(a, n - 1, 0);
		n--;
	}
}

搞完后,数值大的数据都在数组的后面。我在这里没有拿出来。

法二:

先建一个K个数据的小堆,然后让后面的N-K个数据跟该小堆的根结点进行比较,如果大于则进行交换,然后向下调整。最后拿出该堆即可。

cpp 复制代码
void Heapsort(int* a, int n)
{
	int k = 0;
	scanf("%d", &k);
	for (int i = (k - 2) / 2; i >= 0; i--)
	{
		AdjustDown(a, k, i);
	}

	for (int i = k; i < n; i++)
	{
		if (a[i] > a[0])
		{
			Swap(&a[i], &a[0]);
			AdjustDown(a, k, 0);
		}
	}
}

大家应该注意到了哈,这次建堆的方式有点不一样,这是先找到树的最后一个根结点(画四角星的是树的最后一个根结点)

然后向下调整建堆,然后遍历最后一个根结点之前的结点建堆。使用这种方式建堆会更方便时间复杂度为N,使用上面建堆方式时间复杂度是NlogN。

总的来说法二要比法一要更优,因为当数据比较多时法一把所有的数据建堆就有点浪费时间了。

在下篇中我会介绍二叉树的链式结构实现。

相关推荐
代码雕刻家几秒前
课设实验-数据结构-单链表-文教文化用品品牌
c语言·开发语言·数据结构
龙图:会赢的11 分钟前
[C语言]--编译和链接
c语言·开发语言
小字节,大梦想1 小时前
【C++】二叉搜索树
数据结构·c++
Cons.W2 小时前
Codeforces Round 975 (Div. 1) C. Tree Pruning
c语言·开发语言·剪枝
我是哈哈hh2 小时前
专题十_穷举vs暴搜vs深搜vs回溯vs剪枝_二叉树的深度优先搜索_算法专题详细总结
服务器·数据结构·c++·算法·机器学习·深度优先·剪枝
挥剑决浮云 -2 小时前
Linux 之 安装软件、GCC编译器、Linux 操作系统基础
linux·服务器·c语言·c++·经验分享·笔记
丶Darling.2 小时前
LeetCode Hot100 | Day1 | 二叉树:二叉树的直径
数据结构·c++·学习·算法·leetcode·二叉树
labuladuo5202 小时前
Codeforces Round 977 (Div. 2) C2 Adjust The Presentation (Hard Version)(思维,set)
数据结构·c++·算法
Indigo_code3 小时前
【数据结构】【链表代码】合并有序链表
数据结构·windows·链表