解决二叉树的初阶与中阶问题

目录

一、 树的概念与结构

树的常见用语:

二、二叉树的概念与结构

1.概念

2.特殊的二叉树

3. 二叉树的性质

三、二叉树基本操作

1.手搓一颗二叉树

2.二叉树的遍历

2.1. 前序遍历

2.2. 中序遍历

2.3. 后序遍历

2.4. 层序遍历

3.求一棵二叉树的最大深度

4.求解一颗二叉树的节点个数

5.求解一颗二叉树第k层的节点个数

6.查找值为x的节点

7.判断一棵二叉树是否为完全二叉树

结语


一、 树的概念与结构

树是一种非线性结构,数据结构中所说的树,是现实生活中“倒过来的树”,以根为起始,开始衍生

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

注意:在书的结构中,子树间不能有交集

树的常见用语:

  • 节点的度:一个节点含有的子树的个数称为该节点的度,如下图:A的度为6,E的度为2
  • 叶节点:度为0的节点称为叶节点,如下图:B C H I P Q K L M N无子节点,即为叶节点
  • 父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点,如下图:D为H的父节点
  • 子节点:一个节点含有的子树的根节点称为该节点的子节点,如下图:J的子节点有P Q
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点,如下图:P Q互为兄弟节点
  • 树的度:一棵树中,最大的节点的度称为树的度,如下图,树的度为6
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推,如下图:树有4层
  • 树的高度或深度:树中节点的最大层次,如下图:最大深度为4

二、二叉树的概念与结构

1.概念

一棵二叉树是结点的一个有限集合,该集合:

  1. 可能为空
  2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成

从上图可以看出:

  • 二叉树度的取值只可能是0、1、2
  • 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

这是二叉树的几种情况:

2.特殊的二叉树

  • 满二叉树:所有层的节点都被完全填满。在满二叉树中,叶节点的度为0,其余所有节点的度都为2,假设有h层,那么满二叉树共有(2^h - 1)个节点
  • 完全二叉树:只有最底层的节点未被填满,且最底层节点尽量靠左填

3. 二叉树的性质

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

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

  • 对任何一棵二叉树, 如果度为0的结点个数为n0,度为2的结点个数为n2,则有n0 = n2 + 1

  • 若规定根节点的层数为1,具有n个结点的满二叉树的深度 h = log2(𝑛+1) 

三、二叉树基本操作

//定义一个二叉树的结构
typedef char BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

1.手搓一颗二叉树

二叉树是种复杂的非线性结构,增删查改效率太低,我们学习二叉树是为了学习二叉树递归和分治的思想

//接口实现
//创建新的节点
BTNode* BuyNode(BTDataType val);
//手搓一颗树
BTNode* TreeCreate(BTNode* root);

//代码实现
//创建新的节点
BTNode* BuyNode(BTDataType val) {
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL) {
		perror("malloc() err!");
		return;
	}
	newnode->data = val;
	newnode->left = newnode->right;

	return newnode;
}

//手搓一颗树 层序:1 2 4 3 N 5 6
BTNode* TreeCreate(BTNode* root) {
	BTNode* n1 = BuyNode(1);
	BTNode* n2 = BuyNode(2);
	BTNode* n3 = BuyNode(3);
	BTNode* n4 = BuyNode(4);
	BTNode* n5 = BuyNode(5);
	BTNode* n6 = BuyNode(6);

	n1->left = n2;
	n1->right = n4;
	n2->left = n3;
	n4->left = n5;
	n4->right = n6;

	return n1;
}

这是我们手搓出来的二叉树示意图:

2.二叉树的遍历

2.1. 前序遍历

前序遍历按照 根 左子树 右子树的顺序

如图:

开始根1完成,往下走,

走到以2为根的树,根2,完成,往下走

走到以3为根的树,根3,完成,往下走

遇到NULL,3的左子树为空,回溯,回到3,开始找3的右子树,往下走

遇到NULL,3的右子树为空,回溯,回到3,以3为根的树遍历完成,而3是2的左孩子,回溯,回到2,开始找2的右孩子,往下走

遇到NULL,2的右子树为空,回溯,回到2,以w为根的树遍历完成,而2是1的左孩子,回溯,回到1,开始找1的右孩子,往下走

…………………………

这体现了“先走到尽头,再回溯继续”的思想,前中后序都是这种思想

将复杂的一颗树不断细分 分成 根 左子树 右子树,直到这个树不能再分,这便是递归的思想

  • “递”表示开启新方法,程序在此过程中访问下一个节点。
  • “归”表示函数返回,代表当前节点已经访问完毕。

代码实现:

// 二叉树前序遍历 根 左 右
void BinaryTreePrevOrder(BTNode* root) {
	//N代表空
	if (root == NULL) {
		printf("N ");
		return;
	}

	printf("%d ", root->data);
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
}

2.2. 中序遍历

中序遍历按照 左子树 根 右子树的顺序

开始遇到根1,找根1的左子树,往下走

遇到根2,找根2的左子树,往下走

遇到根3,找根3的左子树,往下走

遇到NULL,根3的左子树找到,回溯,回到根3,开始找根3的右子树,往下走

遇到NULL,根3的右子树找到,回溯,回到根3,以3为根的树结束,回溯,回到根2,开始找根2的右子树

………………………………

代码实现:

// 二叉树中序遍历 左 根 右
void BinaryTreeInOrder(BTNode* root) {
	//N代表空
	if (root == NULL) {
		printf("N ");
		return;
	}

	BinaryTreeInOrder(root->left);
	printf("%d ", root->data);
	BinaryTreeInOrder(root->right);
}

2.3. 后序遍历

后序遍历按照 左子树 右子树 根的顺序

代码实现:

// 二叉树后序遍历 左 右 根
void BinaryTreePostOrder(BTNode* root) {
	if (root == NULL) {
		printf("N ");
		return;
	}

	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%d ", root->data);
}

2.4. 层序遍历

顾名思义,按照一层一层遍历,我们需要借助队列先进先出的性质实现

代码实现:

// 层序遍历 利用队列性质
void BinaryTreeLevelOrder(BTNode* root) {
	//创建一个队列来存储二叉树的数据
	Queue helpQueue;
	QueueInit(&helpQueue);
	//根不为空,入队列
	if (root != NULL)
		QueuePush(&helpQueue, root);
	//不断地入队列 出队列 一层出 一层进
	while (!QueueEmpty(&helpQueue)) {

		BTNode* front = QueueFrontVal(&helpQueue);
		QueuePop(&helpQueue);

		if (front != NULL) {
			//front不为空,打印下来
			printf("%d ", front->data);
			//带入下一层
			QueuePush(&helpQueue, front->left);
			QueuePush(&helpQueue, front->right);
		}
		else {
			printf("N ");
		}
	}

	QueueDestory(&helpQueue);
}

3.求一棵二叉树的最大深度

代码实现:

//二叉树的高度
int BinaryTreeHeight(BTNode* root) {
	//空树
	if (root == NULL) {
		return 0;
	}
	//分治 分别求解左右子树高度
	int leftHeight = BinaryTreeHeight(root->left);
	int rightHeight = BinaryTreeHeight(root->right);
	//大的加根节点高度 即是二叉树的高度
	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

4.求解一颗二叉树的节点个数

利用分治的思想,举个例子

一个学院一个院长,两个专业,一个专业四个班,统计这个学院总共有多少人

院长将任务分发给两个专业负责人,两个专业负责人继续分发任务,给自己管理的班级再次下发任务,任务分配给四个班长,班长统计好之后,回溯给两个专业负责人,两个负责人再回溯给院长,院长根据回溯回来的数据即可得到统计

将一个大问题不断细化,分成若干个子问题,直到子问题不可再分,解决最小子问题之后,不断回溯

代码实现:

// 二叉树节点个数
int BinaryTreeSize(BTNode* root) {
	//分治的思想 分别统计左子树节点个数和右子树节点个数 最后加上根节点自己
	return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

5.求解一颗二叉树第k层的节点个数

代码实现:

// 二叉树第k层节点个数
//左子树第k-1层节点数 + 右子树第k-1层节点数
int BinaryTreeLevelKSize(BTNode* root, int k) {
	//空树 无节点
	if (root == NULL) {
		return 0;
	}
	//k == 1 指向根节点
	if (root != NULL && k == 1) {
		return 1;
	}
	//开始找子树
	if (k > 1) {
		return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
	}
}

6.查找值为x的节点

检查根值是否==x,之后先找左子树,如果左子树找到直接返回,若未找到,则继续在右子树中找

代码实现:

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x) {
	//空树
	if (root == NULL) {
		return NULL;
	}
	//分治找左子树 右子树节点
	// 这个方法
	if (root->data == x) {
		return root;
	}
	
	//分治 先找左子树 找到了直接返回 没找到在找右子树
	BTNode* findLeft = BinaryTreeFind(root->left, x);
	if (findLeft != NULL) {
		return findLeft;
	}
	
	BTNode* findRight = BinaryTreeFind(root->right, x);
	if (findRight != NULL) {
		return findRight;
	}
	return NULL;
}

7.判断一棵二叉树是否为完全二叉树

非完全二叉树的层序遍历:

完全二叉树的层序遍历:

代码实现:

// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root) {
	Queue helpQueue;
	QueueInit(&helpQueue);

	// 空树视为完全二叉树
	if (root == NULL) {
		QueueDestroy(&helpQueue);
		return true;
	}

	QueuePush(&helpQueue, root);
	bool reachNull = false; // 标记是否遇到空节点

	while (!QueueEmpty(&helpQueue)) {
		BTNode* front = QueueFrontVal(&helpQueue);
		QueuePop(&helpQueue);

		if (front) {
			// 如果之前已遇到空节点,但当前节点非空 → 不完全
			if (reachNull) {
				QueueDestroy(&helpQueue);
				return false;
			}
			// 无论子节点是否为空都入队
			QueuePush(&helpQueue, front->left);
			QueuePush(&helpQueue, front->right);
		}
		else {
			// 首次遇到空节点时设置标记
			reachNull = true;
		}
	}

	QueueDestroy(&helpQueue);
	return true;
}

结语

二叉树初阶和中阶问题基本讲清楚了,还剩下高阶知识等我去学,诸君共勉!

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vect.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值