【数据结构】二叉树 二叉树的深度优先遍历和广度优先遍历 完全二叉树和满二叉树的性质 二叉树的节点个数以及叶子节点个数

1. 树的概念及结构(了解)

1.1 树的概念

树是一种非线性的数据结构,它是由 n(n >= 0)个有限个节点组成的一个具有层次关系的集合。把它叫做树是因为他看起来像一颗倒挂的树,也就是说,它是根朝上,而叶朝下的。
树的定义有两点: 1)有且仅有一个特定的称为根(root)的结点; 2)当结点数量>1时,其余结点可分为 若干个互不相交的有限集 ,其中每个集合也可以看作一颗树,称之为根的子树。

在这里插入图片描述

  • 有一个特殊的节点,称为根节点(root),根节点没有前驱节点。
  • 除根节点外,其余节点别分为M(M > 0)个互不相交的集合T1、T2……其中每一个集合又是一棵树结构与树类似的子树。每颗子树的根节点有且只有一个前驱,可以有0个或多个后继。
  • 树是递归定义的。

在这里插入图片描述
判断是否是一个树

  • 子树是不相交的
  • 出了根节点外,每个节点有且只有一个父节点
  • 一颗N个节点的树有N-1条边

几个重要的知识点:
在这里插入图片描述

  • 节点的:一个节点含有的子树的个数称为该节点的度;如上图:A的度为6,B的度为0
  • 叶节点或终端节点:度为0的节点称为叶节点;如上图:B、C、H、I、P……等都是叶节点
  • 非终端节点或分支节点:度不为0的节点;如上图:D、E、F……等节点为分支节点
  • 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;如上图:A是B的父节点
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;如上图:B是A的子节点
  • 兄弟节点:具有相同父节点的节点称为兄弟节点;如上图:B、C是兄弟节点
  • 树的度:一棵树中,最大节点的度称为树的度;如上图:树的度为6
  • 节点的层次:从根开始定义起,根为第一层,根的子节点为第二层,以此类推;
  • 树的高度深度:树中节点的最大层次;如上图:树的高度为4
  • 节点的祖先:从根到该节点所经的分支上所有的节点;如上图:A是所有节点的祖先
  • 子孙:以某点为根的子树中任一节点都称为该节点的子孙;如上图:所有节点都是A的子孙
  • 森林:有m(m>0)棵互不相交的多棵树的集合称为树;(数据结构中的学习并查集本质就是一个森林)

1.2 树的表示方法

树的结构相对线性表就相对复杂很多,要存储起来更加复杂。实际中树有很多种表示方法,如:双亲表示法、孩子表示法、孩子兄弟表示法等等

  1. 左孩子右兄弟表示法
typedef int DataType;
struct Node
{
	struct Node* firstChild1;//第一个孩子节点
	struct Node* pNextBrother;//指向下一个兄弟节点
	DataType data;//数据域
}

在这里插入图片描述

  1. 双亲表示法

在这里插入图片描述

1.3 树在实际中的应用

  • 表示文件系统的目录

在这里插入图片描述

2. 二叉树的概念及节点(重点)

2.1 二叉树的概念

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

在这里插入图片描述

2.2 二叉树的特点

  1. 每个节点最多有两棵子树,即二叉树不存在度大于2的节点
  2. 二叉树的子树有左右之分,其子树的次序不能颠倒

在这里插入图片描述

2.3 特殊的二叉树

  • 满二叉树:一个二叉树,如果每一层的节点的度都打到最大数,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为k,且节点总数为(2^k)- 1,则他就是满二叉树
  • 完全二叉树:完全二叉树是效率最高的数据结构,完全二叉树是由满二叉树而引起的。对于深度为k的,有n个节点的二叉树,当且仅当其每一个节点都与深度为k的满二叉树中编号从1至n的节点都一一对应时称为完全二叉树。注意:满二叉树是一种特殊的完全二叉树

在这里插入图片描述
在这里插入图片描述

2.5 二叉树的性质

  1. 若规定根节点层数为1,则一颗非空二叉树的第i层上最多有2^(n-1)个节点(满二叉树)
  2. 若规定根节点层数为1,则深度为h的二叉树最大节点数为2^h - 1(满二叉树)
  3. 对任何一颗二叉树,如果度数为0的节点个数为n0,度为2的节点个数为n2,则有n0=n2+1
  4. 若规定根节点层数为1,具有n个节点的满二叉树的深度,h = log2(N+1) (以2为底,(N+1)为对数)
    在这里插入图片描述

相关习题:
在这里插入图片描述
解析2:
在这里插入图片描述
解析3:
在这里插入图片描述

2.6 二叉树的遍历

所谓遍历就是指沿着某条搜索路线,依次对树中的每个节点均做一次且仅做一次的访问。遍历二叉树是很重要的运算之一,是二叉树上进行其他运算的基础。

2.6.1 深度优先遍历

二叉树的前序、中序、后序遍历也称为深度优先遍历,因为他们都是先往下遍历的,而不管左右。

遍历方式:

  • 前序遍历(NLR):访问根节点的操作发生在遍历其左右子树之前(根左右)
  • 中序遍历(LNR):访问根节点的操作发生在遍历其左右子树之种(左根右)
  • 后序遍历(LRN):访问根节点的操作发生在遍历其左右子树之后(左右根)

在这里插入图片描述

代码实现:

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

typedef char BTDataType;

typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left;//左孩子
	struct BinaryTreeNode* right;//右孩子
	BTDataType data;//数据域
}BTNode;

void PrevOrder(BTNode* root)//前序
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->data);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

void InOrder(BTNode* root)//中序
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->left);
	printf("%c ", root->data);
	InOrder(root->right);
}

void PostOrder(BTNode* root)//后序
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%c ", root->data);
}


int main()
{
	BTNode* A = (BTNode*)malloc(sizeof(BTNode));
	A->data = 'A';
	A->left = NULL;
	A->right = NULL;

	BTNode* B = (BTNode*)malloc(sizeof(BTNode));
	B->data = 'B';
	B->left = NULL;
	B->right = NULL;

	BTNode* C = (BTNode*)malloc(sizeof(BTNode));
	C->data = 'C';
	C->left = NULL;
	C->right = NULL;

	BTNode* D = (BTNode*)malloc(sizeof(BTNode));
	D->data = 'D';
	D->left = NULL;
	D->right = NULL;

	BTNode* E = (BTNode*)malloc(sizeof(BTNode));
	E->data = 'E';
	E->left = NULL;
	E->right = NULL;

	A->left = B;
	A->right = C;
	B->left = D;
	B->right = E;

	PrevOrder(A);
	printf("\n");

	InOrder(A);
	printf("\n");

	PostOrder(A);
	printf("\n");

	system("pause");
	return 0;
}

在这里插入图片描述

我们主要要了解具体递归的顺序,我们可以自己在纸上画一画

2.6.2 广度优先遍历

层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在的层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的根节点,然后从左到右访问第二层上的节点,以此类推,自上而下,自左至右逐层访问树的节点的过程就是层序遍历,也称为广度优先遍历。

在这里插入图片描述

二叉树的广度优先遍历思想:我们利用队列(先进先出),上一层出的时候带着下一层进

在这里插入图片描述

//层序遍历	广度优先遍历
void LevelOrder(BTNode* root)
{
	//核心思想——上一层出的时候带下一层节点进
	queue<BTNode*> q;//创建队列容器
	if (root)
	{
		q.push(root);//入队
	}
	while (!q.empty())
	{
		BTNode* front = q.front();
		q.pop();
		printf("%c ", front->data);
		if (front->left)
		{
			q.push(front->left);
		}
		if (front->right)
		{
			q.push(front->right);
		}
	}
	printf("\n");
}

二叉树遍历的例题:
在这里插入图片描述
答案为:A A D

2.7 计算二叉树的节点个数

很多人看到计算二叉树的节点个数,心想那么简单,直接遍历一下二叉树不就好了吗。

//节点个数
int size = 0;
void TreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	else
	{
		size++;
	}
	TreeSize(root->left);
	TreeSize(root->right);
}

在这里插入图片描述

其实,vs2022已经报错了,为什么呢?因为我们定义了一个全局的size,在我们计算了节点为A的节点个数时,得到的大小是5,但是在计算节点为B的节点个数为8,。就是因为我们这个变量A是个全局变量,会累加的。

void TreeSize(BTNode* root, int* psize)
{
	if (root == NULL)
	{
		return;
	}
	else
	{
		++(*psize);
	}
	TreeSize(root->left, psize);
	TreeSize(root->right, psize);
}


	int Asize = 0;
	TreeSize(A, &Asize);
	printf("TreeSize(A): %d", Asize);

	int Bsize = 0;
	TreeSize(B, &Bsize);
	printf("TreeSize(A): %d", Bsize);

我们要记得把地址传过去。
但是,如果我们知道多线程的时候,程序在运行时,可能有多个进程在同时进行,我们就无法保证在求一个节点的数时的节点前,先把size赋值为0了。怎么办呢?看下面的代码就很好的解决了这个问题。

//返回节点个数
int TreeSize(BTNode* root)
{
	return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}

在这里插入图片描述

这个程序很巧,我们求一棵二叉树节点个数,就计算它左子树节点个数 + 右子树节点个数 + 1(根节点)

2.8 计算二叉树叶子节点个数

计算叶子节点个数和求节点个数的思想是一样的

//求叶子节点个数
int TreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
		return 1;
	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值