当前位置: 首页 > article >正文

数据结构--二叉树_链式(下)

实现链式结构二叉树

链式结构就是由一个一个的节点组成。

 ⽤链表来表⽰⼀棵⼆叉树,即⽤链来指⽰元素的逻辑关系。 通常的⽅法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别⽤来给出该结点左孩⼦和右孩⼦所在的链结点的存储地址。

二叉树节点结构的定义

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

关于二叉树的遍历

想要实现链式结构二叉树,就一定要掌握二叉树的遍历相关的知识,二叉树的遍历分为前序遍历,中序遍历和后序遍历。

前序遍历:访问顺序为:根结点、左⼦树、右⼦树(根左右)。

假如,我现在有下图二叉树,那么前序遍历的顺序就为:

A,B,D,NULL,NULL,NULL,C,E,NULL,NULL,F,NULL,NULL.

我们从A开始遍历,A是根他的左孩子是B,但在左子树中,B也是根,继续往下遍历,他的左孩子是d,D与B同理,我们继续往下遍历NULL为空,然后我们就往上走,D的左孩子遍历完成后,我们遍历D的右孩子也是NULL,那么此时,对于D这颗左子树我们已经便利完成,再继续往上走B这颗子树的左孩子和根节点我们已经遍历完成,就剩下它的右孩子NULL,此时整个左子树我们都遍历完成了,再去遍历右子树。右子树与左子树同理,便利的顺序为C,E,BULL,NULL,F,NULL,NULL。如果大家还有不懂,可以在评论区问我,这里就不再赘述了。

中序遍历:访问顺序为:左⼦树、根结点、右⼦树(左根右)。

 假如,我现在有下图二叉树,那么中序遍历的顺序就为:

NULL, D, NULL, B, NULL, A, NULL, E, NULL, C, NULL, F, NULL.

后序遍历:访问顺序为:左⼦树、右⼦树,根结点(左右根)。

 假如,我现在有下图二叉树,那么后序遍历的顺序就为:

NULL, NULL, D, NULL, B, NULL, NULL, E, NULL, NULL, F, C, A.

了解二叉树的遍历后,我来代码实现一下。开启递归的暴力美学!

老规矩,我们创建三个项目分别是Tree.h,Tree.c,test.c

首先,我们先手动实现一个二叉树:

代码:test.c

//手动构造一棵二叉树
BTNode* CreateTree()
{
	BTNode* nodeA = buyNode('A');
	BTNode* nodeB = buyNode('B');
	BTNode* nodeC = buyNode('C');
	BTNode* nodeD = buyNode('D');
	BTNode* nodeE = buyNode('E');
	BTNode* nodeF = buyNode('F');

	nodeA->left = nodeB;
	nodeA->right = nodeC;
	nodeB->left = nodeD;
	nodeC->left = nodeE;
	nodeC->right = nodeF;

	return nodeA;
}

前序遍历代码实现:

//前序遍历 根左右
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}

前序遍历的过程:

前序遍历的顺序为根左右,我们首先要判断根是不是空,如果根为空我们就不需要遍历了,如果根不为空,我们就要先把根打印出来,打印完成后,我们就又要递归调用root->left,也就是B,B这个节点不为空,我们把B的左孩子D打印出来,打印完成后继续递归,D的左孩子,D的左孩子为空,就满足root=NULL这个条件,打印一下NULL,打印完成后return,return就意味函数栈帧将会释放,也就意味4这个代码执行完了,我们回到上一级3,D的左孩子打印完了,继续递归打印右孩子。此刻,D的左右孩子和它自己都已经打印完成,就意味着D这个函数已经调用完了,我们就要释放函数栈帧,继续向上走。回到2,继续打印B的右孩子(操作6),打印完NULL后,就要return,释放函数栈帧回到2,B的左右孩子和它自身已经打印完了,释放函数栈帧回到A,A的左孩子和它自身打印完了,继续打印它的右孩子C,所以这里的参数root就是C,C不为空,我们接下来打印C里面的数据。递归调用C,打印C的左孩子E,E不为空,我们就打印E里面的数据,E的左孩子为空,我们打印NULL,然后return 释放函数栈帧。然后再去递归E的右孩子,E的右孩子也是空,所以再打印一个NULL。E的左右孩子和它本身都打印成功后,释放函数栈帧,返回C,继续递归C的右孩子F,与E同理,递归完F后,释放函数栈帧回到C,C的全部内容也打印完成,现在要销毁函数栈帧,然后再回到A,A的全部内容也打印完成,也要销毁函数栈帧。A的函数栈帧销毁就意味着我们的遍历完成了,所有通过递归创建的的函数栈帧都销毁完了。

测试结果:

中序遍历代码实现:

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

遍历过程:

首先这里传过来的根节点A,判断A是不是空。不为空就要去递归A。A的左孩子就是是B这个节点,所以这里创建了一个B的函数栈帧,判断B的函数栈帧为空吗,不为空,那我们继续去调用。B的left就是D。所以这里我们创建了一个函数栈帧参数为D,,D不为空再继续去递归它的左孩子在这里创建新的函数栈帧,参数为NULL。在参数为NULL的函数栈帧中,参数为空我们就打印空。然后return当前函数栈帧被销毁。销毁之后我们回到根节点D,D的函数栈帧的左孩子我们已经递归完了,接下来我们要打印一下D,D打印完了之后,接下来我们要递归调用D的右孩子,那么D的右孩子为NULL,我们就打印一下NULL。打印完了之后要return也就意味着NULL这个函数栈帧我们已经调用完了。然后我们销毁这个函数栈帧,然后再继续回到D这个函数。D这个函数里面所有的代码都已经执行完了那么函数战争要销毁回到了B的函数栈帧。再去递归B的右孩子,我们又要去创建一个参数为NULL的函数栈帧,然后打印NULL,再继续return,销毁函数栈帧回到B。那么B所有内容我们已经递归完了,也就意味着这一行代码就已经执行完了,那么接下来我们要打印当前A节点里的值,然后再继续去递归A的右子树,那么A的的的右子树在这里创建了一个函数栈帧参数为C,C不为空我们就在这个函数栈帧里面继续递归它的左孩子。C的左孩子是E,我们就创建参数为E的函数栈帧,E不为空再继续递归它的左孩子,它的左孩子创建函数栈帧的参数NULL,那我们就打印NULL,然后再让它return,然后销毁NULL这个函数栈帧。在E这个函数栈帧中,它的左孩子已经递归完了,接下来是要打印根节点E。接下来是右孩子,继续递归创建函数栈帧为NULL,再打印NULL然后return,然后让函数栈帧消毁。E的全部内容递归完了,那么就要销毁它的函数栈帧,然后回到C,接下来递归它的右孩子F,F不为空,再继续创建函数栈帧参数NULL,为NULL我就打印NULL。然后直接return,销毁函数栈帧。F的左孩子我已经递归完了,接下来我们要打印一下当前节点里的值F, F打印完了之后,在F这个函数栈帧中继续递归它的它的右孩子,创建一个参数为NULL的函数栈帧然后打印NULL,直接return销毁函数栈帧。回到F,F的全部内容我已经打印完了,再继续往上回溯到C这个函数栈帧,C里面的所有内容都已经递归完成,再继续回溯到A这个函数栈帧中,A的全部内容也处理完了,销毁函数栈帧,至此整个递归完成。

测试结果:

后序遍历代码实现:

后续遍历的过程大家可以自己思考一下,有什么问题打在评论区。

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

测试结果:

求二叉树结点的个数

求整个二叉树节点的个数只需要根节点+左子树的节点个数+右子树的节点个数。

左子树又是一颗子树,也分左右子树,所以左子树的节点个数也是 1+左子树的节点个数+右子树的节点个数。右子树同理。这就又是一个递归问题。

代码:

// ⼆叉树结点个数
int BinaryTreeSize(BTNode* root) {
	if (root == NULL)
	{
		return 0;
	}
	return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

测试结果:

求二叉树叶子节点的个数

叶子节点:该节点的左孩子和右孩子都为空

我们依然可以把这个二叉树分成左右子树,然后依次求左子树叶子结点的个数和右子树叶子结点的个数,然后相加。还是使用递归。

代码:

// ⼆叉树叶⼦结点个数
int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

测试结果:

求二叉树第K层结点的个数

假设我们要找的是k=2层结点的个数。首先我们要判断根节点为不为空,不为空我们继续遍历, 我们可以递归让k--,找到我们要找的那一层。然后遍历,让左子树节点的个数+右子树节点的个数,就是第k层总的节点的个数。

代码:

// ⼆叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

测试结果:

求二叉树的深度/高度

注意 : 不平衡的二叉树 的深度按照最大的深度计算 如下图 , 二者的深度都为3.

根节点的层次+左子树和右子树中最大的层次。

代码:

//⼆叉树的深度/⾼度
int BinaryTreeDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int leftDep = BinaryTreeDepth(root->left);
	int rightDep = BinaryTreeDepth(root->right);
	return 1 + (leftDep > rightDep ? leftDep : rightDep);
}

测试结果:

二叉树查找值为x的节点

首先我们需要判断根节点是否为空,不为空我们就进行遍历,使用上述讲过的哪种都可以,这里我们使用先序遍历。先遍历左子树,如果找到了就直接返回,如果没找到就继续遍历右子树。

代码:

// ⼆叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* leftFind = BinaryTreeFind(root->left, x);
	if (leftFind)
	{
		return leftFind;
	}
	BTNode* rightFind = BinaryTreeFind(root->right, x);
	if (rightFind)
	{
		return rightFind;
	}
	return NULL;
}

测试结果:

二叉树的销毁

二叉树是由链表组成,而链表是由节点组成,所以我们销毁节点即可,那么我们用什么方法销毁节点呢?

我们可以使用前面讲的后序遍历,边遍历边销毁,那为什么不使用前序遍历或者后序遍历呢?

如果我们的根先被销毁了,是不是就不容易找到它的左右孩子啊,所以我们使用后序遍历。

代码:

// ⼆叉树销毁
void BinaryTreeDestory(BTNode** root) {
	if (*root == NULL)
	{
		return;
	}
	BinaryTreeDestory(&((*root)->left));
	BinaryTreeDestory(&((*root)->right));
	free(*root);
	*root = NULL;
}

测试结果:

全部代码

Tree.h

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

typedef char BTDataType;
//二叉树结点结构的定义
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;
//前序遍历
void PreOrder(BTNode* root);
//中序遍历
void InOrder(BTNode* root);
//后序遍历
void PostOrder(BTNode* root);
// ⼆叉树结点个数
int BinaryTreeSize(BTNode* root);
// ⼆叉树叶⼦结点个数
int BinaryTreeLeafSize(BTNode* root);
// ⼆叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
//⼆叉树的深度/⾼度
int BinaryTreeDepth(BTNode* root);
// ⼆叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// ⼆叉树销毁
void BinaryTreeDestory(BTNode** root);

 Tree.c

#include"Tree.h"


//前序遍历 根左右
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->data);
	PreOrder(root->left);
	PreOrder(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 BinaryTreeSize(BTNode* root) {
	if (root == NULL)
	{
		return 0;
	}
	return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}
// ⼆叉树叶⼦结点个数
int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
// ⼆叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
//⼆叉树的深度/⾼度
int BinaryTreeDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int leftDep = BinaryTreeDepth(root->left);
	int rightDep = BinaryTreeDepth(root->right);
	return 1 + (leftDep > rightDep ? leftDep : rightDep);
}
// ⼆叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* leftFind = BinaryTreeFind(root->left, x);
	if (leftFind)
	{
		return leftFind;
	}
	BTNode* rightFind = BinaryTreeFind(root->right, x);
	if (rightFind)
	{
		return rightFind;
	}
	return NULL;
}
// ⼆叉树销毁
void BinaryTreeDestory(BTNode** root) {
	if (*root == NULL)
	{
		return;
	}
	BinaryTreeDestory(&((*root)->left));
	BinaryTreeDestory(&((*root)->right));
	free(*root);
	*root = NULL;
}

tese.c 

#include"Tree.h"

BTNode* buyNode(BTDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	node->data = x;
	node->left = node->right = NULL;

	return node;
}
//手动构造一棵二叉树
BTNode* CreateTree()
{
	BTNode* nodeA = buyNode('A');
	BTNode* nodeB = buyNode('B');
	BTNode* nodeC = buyNode('C');
	BTNode* nodeD = buyNode('D');
	BTNode* nodeE = buyNode('E');
	BTNode* nodeF = buyNode('F');

	nodeA->left = nodeB;
	nodeA->right = nodeC;
	nodeB->left = nodeD;
	nodeC->left = nodeE;
	nodeC->right = nodeF;

	return nodeA;
}
void test() {
	BTNode* root = CreateTree();
	//PreOrder(root);
	//InOrder(root);
	//PostOrder(root);
	//printf("size:%d\n", BinaryTreeSize(root));
	//printf("leaf size:%d\n", BinaryTreeLeafSize(root));
	//printf("K size:%d\n", BinaryTreeLevelKSize(root, 2));
	//printf("K size:%d\n", BinaryTreeLevelKSize(root, 3));
	//printf("depth:%d\n", BinaryTreeDepth(root));
	BTNode* find = BinaryTreeFind(root, 'Z');
	if (find == NULL)
	{
		printf("未找到!\n");
	}
	else {
		printf("找到了!\n");
	}
	BinaryTreeDestory(&root);
}
int main() {
	test();
	return 0;
}

祝大家生活愉快。


http://www.kler.cn/a/381105.html

相关文章:

  • VMWARE ESXI VMFS阵列故障 服务器数据恢复
  • Python网络爬虫:入门与实战
  • 力扣动态规划基础版(矩阵型)
  • Qt中的动态链接库编程(Q_DECL_IMPORT、Q_DECL_EXPORT)
  • DFS求解迷宫最长移动路线
  • Qt中的Model与View 2
  • Node.js:Express 中间件 CORS 跨域资源共享
  • ETLCloud怎么样?深度解析其在数据管理中的表现
  • 小菜家教平台(二):基于SpringBoot+Vue打造一站式学习管理系统
  • 数据结构与算法——Java实现 54.力扣1008题——前序遍历构造二叉搜索树
  • C语言中如何实现动态内存分配
  • Unity网络开发基础(part5.网络协议)
  • 软硬链接与动静态库
  • [N-155]基于springboot,vue宿舍管理系统
  • Java项目实战II基于Spring Boot的交通管理在线服务系统设计与实现(开发文档+数据库+源码)
  • VSCode Markdown pdf导出修改字体、行距等
  • MySQL之JDBC入门详解
  • MySQL初学之旅(1)配置与基础操作
  • 大数据-205 数据挖掘 机器学习理论 - 线性回归 最小二乘法 多元线性
  • Vue3版本的uniapp项目运行至鸿蒙系统
  • 数据结构(8.7_3)置换——选择排序
  • 【P2-8】ESP8266 WIFI模块在STA+AP模式下相关指令及注意事项
  • RDD转换算子:重分区算子:【repartition、coalesce】
  • [C++ 核心编程]笔记 4.2.5 深拷贝与浅拷贝
  • Hive学习笔记
  • SQL入门的基础知识