二叉树链式结构的实现(二叉树的遍历以及各种常用功能函数的实现)

news2024/11/25 10:46:07

之前也是把堆部分的知识点梳理完毕(即二叉树链式顺序的实现):堆的应用:堆排序和TOP-K问题

那么讲完了二叉树链式结构的实现。今天就进入二叉树链式结构的实现:


文章目录

  • 1.准备工作
  • 2.二叉树的遍历
    • 2.1前序遍历
    • 2.2中序遍历
    • 2.3后序遍历
    • 2.4层序遍历
  • 3.节点个数以及高度等
    • 3.1二叉树节点个数
    • 3.2二叉树叶子节点(度为1的节点)个数
    • 3.3树的高度
    • 3.4二叉树第k层节点个数
    • 3.5二叉树查找值为x的节点
  • 4.二叉树的构建及销毁
    • 4.1通过前序遍历的数组构建二叉树
  • 4.2二叉树的销毁
  • 5.判断二叉树是否是完全二叉树


1.准备工作

我们先手动快速创建一棵简单的二叉树来先进入二叉树操作,等对二叉树递归和结构有了一定的熟练后我们反过头再来看二叉树真正的创建方式

这不是真正的创建方法,是自己手动构建

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

typedef int BTDataType;
typedef struct BinaryTreeNode//节点
{
	BTDataType data;//数据
	struct BinaryTreeNode* left;//左孩子
	struct BinaryTreeNode* right;//右孩子
}TreeNode;

TreeNode* BuyTreeNode(int x)//创建数据为x的节点
{
	TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
	assert(node);

	node->data = x;
	node->left = NULL;
	node->right = NULL;

	return node;
}

TreeNode* CreateTree()//形成树(手动连接)
{
	TreeNode* node1 = BuyTreeNode(1);
	TreeNode* node2 = BuyTreeNode(2);
	TreeNode* node3 = BuyTreeNode(3);
	TreeNode* node4 = BuyTreeNode(4);
	TreeNode* node5 = BuyTreeNode(5);
	TreeNode* node6 = BuyTreeNode(6);//创建六个节点

	node1->left = node2;
	node1->right = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;//进行连接

	return node1;
}

构建的二叉树如图:

请添加图片描述

2.二叉树的遍历

递归实现的关键在于将问题分解为规模更小的子问题,然后通过函数的递归调用来解决子问题。在二叉树遍历中,递归的思想可以很自然地应用,因为遍历左子树和右子树本质上就是对规模更小的子树进行遍历

二叉树遍历(Traversal)是指按照一定规则,依次访问二叉树中的所有节点,每个节点只被访问一次

二叉树的遍历有:前序/中序/后序的递归结构遍历和层序遍历

  1. 前序遍历(Preorder Traversal):先访问根节点,再访问左子树,最后访问右子树(根>左>右)
  2. 中序遍历(Inorder Traversal):先访问左子树,再访问根节点,最后访问右子树(左>根>右)
  3. 后序遍历(Postorder Traversal):先访问左子树,再访问右子树,最后访问根节点(左>右>根)
  4. 层序遍历是一种按层级顺序逐层遍历树结构的方法。它从树的根节点开始,逐层向下遍历,按照从左到右的顺序访问每一层的节点

2.1前序遍历

先访问根节点,再访问左子树,最后访问右子树(根>左>右);每进入一个新节点(先访问自身)

按照此规则之前构建的二叉树访问结果:1 2 3 NULL NULL NULL 4 5 NULL NULL 6 NULL NULL

方便大家理解,我就把NULL也写上:

请添加图片描述

void PreTraversal(TreeNode* root) 
{
	if (root == NULL) 
	{
		printf("NULL ");//正常是不需要的,这里是为了让大家看到NULL(看的全过程)
		return;
	}
	// 访问根节点
	printf("%d ", root->data);
	// 遍历左子树
	PreTraversal(root->left);
	// 遍历右子树
	PreTraversal(root->right);
}

int main()
{
	TreeNode* root = CreateTree();//创建好树
	PreTraversal(root);
	return 0;
}

结果:
请添加图片描述

2.2中序遍历

先访问左子树,再访问根节点,最后访问右子树(左>根>右);每进入一个新节点(先访问左子树)

按照此规则之前构建的二叉树访问结果:NULL 3 NULL 2 NULL 1 NULL 5 NULL 4 NULL 6 NULL

请添加图片描述

void InTraversal(TreeNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");//正常是不需要的,这里是为了让大家看到NULL(看的全过程)
		return;
	}
	// 遍历左子树
	InTraversal(root->left);
	// 访问根节点
	printf("%d ", root->data);
	// 遍历右子树
	InTraversal(root->right);
}

int main()
{
	TreeNode* root = CreateTree();//创建好树
	InTraversal(root);
	return 0;
}

结果:

请添加图片描述

2.3后序遍历

先访问左子树,再访问右子树,最后访问根节点(左>右>根);每进入一个新节点(先访问右子树)

按照此规则之前构建的二叉树访问结果:NULL NULL 3 NULL 2 NULL NULL 5 NULL NULL6 4 1

请添加图片描述

void PostTraversal(TreeNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");//正常是不需要的,这里是为了让大家看到NULL(看的全过程)
		return;
	}
	// 遍历左子树
	PostTraversal(root->left);
	// 遍历右子树
	PostTraversal(root->right);
	// 访问根节点
	printf("%d ", root->data);
}

int main()
{
	TreeNode* root = CreateTree();//创建好树
	PostTraversal(root);
	return 0;
}

结果:

请添加图片描述

2.4层序遍历

它从树的根节点开始,逐层向下遍历,按照从左到右的顺序访问每一层的节点

在 C 语言中,一般使用队列来实现层序遍历:

  1. 从根节点开始,将根节点入队列

  2. 循环执行以下步骤,直到队列为空:

    • 弹出队列的首节点,并访问该节点
    • 将该节点的左右子节点(如果存在)依次入队列;即弹出节点后把子节点入队列

    队列为空时,循环结束

void LevelOrder(TreeNode* root)
{
	Queue q;//创建队列,队列节点里的data储存root的地址
	QInit(&q);//初始化
	QPush(&q, root);//根进去了
	int LevelSize = 1;//要一层一层输出,来辅助
	while (!QEmpty(&q))
	{
		while (LevelSize--)//LevelSize是几就循环几次,一开始是1,输出一个就换行了,再Push后再次更新,来输出下一行
		{
			TreeNode* first = QFront(&q);//先存下来
			QPop(&q);//再pop掉第一个


			printf("%c ", first->data);
			if (first->left)//有左节点就push进来
			{
				QPush(&q, first->left);
			}
			if (first->right)//有右节点就push进来
			{
				QPush(&q, first->right);
			}
		}
		printf("\n");
		LevelSize = QSize(&q);
	}

	QDestroy(&q);
}

请添加图片描述


3.节点个数以及高度等

3.1二叉树节点个数

同样是递归

  1. 计算二叉树节点个数的问题就是计算左子树节点个数和计算右子树节点个数两个子问题
  2. 对于当前节点,我们需要计算以当前节点为根的子树的节点个数。这个节点本身占据一个节点(后面加一),所以我们将问题拆分为计算左子树节点个数和计算右子树节点个数两个子问题,然后将左右子树节点个数相加,并加上当前根节点,即 TreeSize(root->left) + TreeSize(root->right) + 1
  3. 对于左子树和右子树,我们同样可以按照上述步骤继续拆分分析,直到遇到空节点为止(递归终止条件)
int TreeSize(TreeNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return TreeSize(root->left) + TreeSize(root->right) + 1;
}

int main()
{
	TreeNode* root = CreateTree();//创建好树
	printf("%d\n", TreeSize(root));
	return 0;
}

结果:

请添加图片描述

代码也可以这样写:

int TreeSize(TreeNode* root)
{
	return root == NULL ? 0 : 
		TreeSize(root->left) + TreeSize(root->right) + 1;//利用三目运算符
}

3.2二叉树叶子节点(度为1的节点)个数

  1. 判断当前节点是否为空。如果为空,子树的叶子节点个数为 0,直接返回 0
  2. 节点不为空,我们需要判断当前节点是否为叶子节点。如果当前节点的左右子树均为空,即 root->left == NULL && root->right == NULL,则表示当前节点为叶子节点,返回 1
  3. 不是叶子节点,则需要计算以当前节点为根的子树的叶子节点个数。这个节点本身不是叶子节点,所以我们将问题拆分为计算左子树叶子节点个数和计算右子树叶子节点个数两个子问题,然后将左右子树叶子节点个数相加,即 TreeLeafSize(root->left) + TreeLeafSize(root->right)
  4. 对于左子树和右子树,我们同样可以按照上述步骤继续拆分,直到遇到空节点或叶子节点为止
int TreeLeafSize(TreeNode* root)
{
	if (root == NULL)//是NULL就返回0
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)//是叶子就返回1
	{
		return 1;
	}
	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
	//不是叶子就返回左子树叶子与右子树叶子之和
}

int main()
{
	TreeNode* root = CreateTree();//创建好树
	//printf("%d\n", TreeSize(root));
	printf("%d\n", TreeLeafSize(root));
	return 0;
}

请添加图片描述

3.3树的高度

  1. 如果树为空,返回高度为 0
  2. 如果树不为空,则树的高度等于左子树的高度和右子树的高度中的较大值加 1,即 fmax(TreeHeight(root->left), TreeHeight(root->right)) + 1
  3. 对于左子树和右子树,我们同样可以按照上述步骤继续递归计算
int TreeHeight(TreeNode* root)
{
	if (root == NULL)
		return 0;
	return fmax(TreeHeight(root->left), TreeHeight(root->right)) + 1;
}

int main()
{
	TreeNode* root = CreateTree();//创建好树
	//printf("%d\n", TreeSize(root));
	//printf("%d\n", TreeLeafSize(root));
	printf("%d\n", TreeHeight(root));
	return 0;
}

请添加图片描述

3.4二叉树第k层节点个数

  1. 如果树为空,返回节点数为 0
  2. 如果 k 等于 1,表示计算树的根节点,返回 1
  3. 如果 k 大于 1,则第 k 层节点数等于左子树的第 k-1 层节点数加上右子树的第 k-1 层节点数,即 TreeLevelNodes(root->left, k-1) + TreeLevelNodes(root->right, k-1)
  4. 对于左子树和右子树,我们同样可以按照上述步骤继续递归计算
int TreeLevelKSize(TreeNode* root, int k)
{
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;
	return TreeLevelKSize(root->left, k - 1) + TreeLevelKSize(root->right, k - 1);
}

int main()
{
	TreeNode* root = CreateTree();//创建好树

	printf("%d\n", TreeLevelKSize(root,2));//求个第二层的
	return 0;
}

3.5二叉树查找值为x的节点

  1. 如果为空,那肯定找不到,直接返回NULL(同时也作为递归终止的情况之一)
  2. 先找跟节点,找到了即root->data == x,就返回该节点地址(同时也作为递归终止的情况之一)
  3. 找不到,就递归左节点,再右节点
  4. 都没有return的机会后,最后return NULL

递归停止的两种情况:要么找到了返回地址, 要么没找到返回NULL

  • 当不是NULL时,说明找到了,就一路返回上去,得到结果
  • 是NULL,就说明没找到,就去另外一边继续
TreeNode* TreeFind(TreeNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	// 先看根节点
	if (root->data == x)
	{
		return root;
	}
	// 看左子树
	TreeNode* ret1= TreeFind(root->left, x);//递归停止的两种情况:要么找到了返回地址
                                                 // 要么没找到返回NULL
                                              //当不是NULL时,说明找到了,就一路返回上去,得到结果
	if (ret1 != NULL)
	{
		return ret1;
	}
	// 看右子树
	TreeNode* ret2 = TreeFind(root->right, x);//如上
	if (ret2 != NULL)
	{
		return ret2;
	}
	return NULL;
}

int main()
{
	TreeNode* root = CreateTree();//创建好树
	//printf("%d\n", TreeSize(root));
	//printf("%d\n", TreeLeafSize(root));
	//printf("%d\n", TreeHeight(root));
	//printf("%d\n", TreeLevelKSize(root,2));//求个第二层的
	printf("%p\n", TreeFind(root, 5));
	return 0;
}

请添加图片描述

返回地址,说明找到了


4.二叉树的构建及销毁

4.1通过前序遍历的数组构建二叉树

通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树

先根据这个画出来(#为NULL就不画了):

请添加图片描述

TreeNode* CreateTreeTrue(char* arr, int* pi)
{
	if (arr[*pi] == '#')
	{
		(*pi)++;//调到下一个字符
		return NULL;
	}
	TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
	assert(root);
	root->data = arr[*pi];//根创建且赋值好了
	(*pi)++;

	root->left=CreateTreeTrue(arr, pi);//创建左树
	root->right=CreateTreeTrue(arr, pi);//创建右树
	return root;
}

严格按照> 左子树> 右子树的顺序,每一个左子树又是作为一个根,有自己的左子树及右子树。所以以创建根的方法来创建左子树和右子树,直到遇到#来返回NULL进行终止

4.2二叉树的销毁

销毁我们都使用后序销毁

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

因为最后要把root置空,传一级指针不行(没用,形参是临时拷贝),想要改变root本身就传入root的地址(即二级指针


5.判断二叉树是否是完全二叉树

也是利用队列,pop出一个后把他的左右节点push进去,遇到NULL就出循环,再看此时队列里是否全是NULL,如果全是NULL那就是完全二叉树,只要还有节点,那就不是完全二叉树

int BinaryTreeComplete(TreeNode* root)
{
	Queue q;
	QInit(&q);
	QPush(&q, root);//根进去了
	int LevelSize = 1;
	while (!QEmpty(&q))
	{
		TreeNode* first = QFront(&q);
		QPop(&q);
		if (first == NULL)
			break;//通过这个出循环
		QPush(&q, first->left);
		QPush(&q, first->right);

	}
    // 已经遇到空节点,如果此时队列中后面的节点还有非空,就不是完全二叉树
	while (!QEmpty(&q))
	{
		TreeNode* first = QFront(&q);
		QPop(&q);
		if (first != NULL)
		{
            QDestroy(&q);
			return 0;
		}
	}
	QDestroy(&q);
	return 1;
}

终于啊,二叉树中较为基本的地方都已经梳理完毕了,相信在不久的将来,还会有二叉树的内容,大家敬请期待吧!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1354949.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从vue小白到高手,从一个内容管理网站开始实战开发第三天,使用Element UI构建页面-登录(一)

上次我们介绍了如何安装Element UI库,这次我们使用Element UI中的组件开始开发我们的页面。 开发之前要先在项目中建立好几个目录,方便我们下面的开发。 一、在项目中创建页面管理目录 1、pages目录(文件夹) 首先在src文件夹下创建一个名为pages的文件夹,该文件夹用来统…

elasticsearch的查询方式和数据库事务隔离级别的思考

项目中用到了 elasticsearch&#xff0c;发现有几种查询方式不太一样&#xff0c;思考了一下&#xff0c;总结如下 普通分页 等同于关系数据库的分页查询&#xff0c;例如 mysql 的 limit&#xff0c;如下 sql select * from test limit 100000,10 这种查询方式有一个问题&a…

软文推广宣发遵循的基本流程

在软文发稿的旅程中&#xff0c;制定明确的策略思路是确保成功的关键。软文发稿有一定的流程需要我们遵循&#xff0c;才能达到理想的软文宣发效果。首先&#xff0c;我们要明确发稿的地区&#xff0c;然后精准选择目标受众&#xff0c;最后才能展开内容的创作。下面&#xff0…

springboot整合springbatch批处理

springboot整合springbatch实现批处理 简介项目搭建步骤 简介 项目搭建 参考博客【场景实战】Spring Boot Spring Batch 实现批处理任务&#xff0c;保姆级教程 步骤 1.建表 建表sql CREATE TABLE student (id int NOT NULL AUTO_INCREMENT,name varchar(100) NOT NULL C…

02-SpringCloud-Eureka注册中心

Eureka注册中心 假如我们的服务提供者user-service部署了多个实例&#xff0c;如图&#xff1a; 大家思考几个问题&#xff1a; order-service在发起远程调用的时候&#xff0c;该如何得知user-service实例的ip地址和端口&#xff1f;有多个user-service实例地址&#xff0c;…

力扣hot100 翻转二叉树 递归

&#x1f468;‍&#x1f3eb; 题目地址 &#x1f60b; AC code /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNod…

CNN——AlexNet

1.AlexNet概述 论文原文&#xff1a;ImageNet Classification with Deep Convolutional Neural Networks 在LeNet提出后&#xff0c;卷积神经网络在计算机视觉和机器学习领域中很有名气。但卷积神经网络并没有主导这些领域。这是因为虽然LeNet在小数据集上取得了很好的效果&am…

jQuery常用的 四大基本选择器

文章目录 简介&#xff1a;基础选择器1. 通过 ID 选择器获取元素2. 通过 class 选择器获取元素3. 通过标签名选择器获取元素 层级选择器1. 通过直接子元素选取2. 通过后代元素选取 属性选择器1.[attributevalue]属性选择器2.[attribute!value] 属性选择器3.[attribute*value]属…

如何在Windows安装Wnmp服务并实现固定地址远程访问

文章目录 前言1.Wnmp下载安装2.Wnmp设置3.安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3.4 创建公网地址 4.固定公网地址访问 前言 WNMP是Windows系统下的绿色NginxMysqlPHP环境集成套件包&#xff0c;安装完成后即可得到一个Nginx MyS…

字符串处理-第11届蓝桥杯省赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第26讲。 字符串处理&#…

Typescript---webpack和Babel的使用 03

webpack 通常情况下&#xff0c;实际开发中我们都需要使用构建工具对代码进行打包&#xff0c;TS同样也可以结合构建工具一起使用&#xff0c;下边以webpack为例介绍一下如何结合构建工具使用TS。 步骤&#xff1a; 初始化项目 进入项目根目录&#xff0c;执行命令 npm init -…

Dockerfile + harbor详解

Dockerfileharbor私服 一 docker工作流 1. docker管理流程 2. 镜像仓库阿里 (1) 阿里私有仓库 公司内部管理项目涉及到的所有docker镜像&#xff0c;会使用私有仓库的方式&#xff0c;集中管理。 (2) 创建阿里Docker仓库 登录阿里云创建私有仓库 网址&#xff1a;容器镜像服…

希亦、觉飞、小吉三款婴儿洗衣机大比拼!全方位对比测评

由于年龄幼小的婴儿的皮肤都非常的幼嫩&#xff0c;因此婴儿衣物材质的类型大部分都是采用为纯棉&#xff0c;并且婴儿的衣物不能够与大人的衣物一起进行混洗&#xff0c;容易把细菌感染到宝宝的衣物上&#xff0c;因此很多家庭为了保证宝宝衣服的有效清洁&#xff0c;避免交叉…

感恩客户·持续向上-契约锁电子签章

2023年&#xff0c;电子签章成为组织数字化建设中的刚性需求&#xff0c;市场机遇帮助契约锁实现了产品、伙伴、客户、应用场景等全方位的持续发展。 感恩客户和伙伴的支持&#xff0c;让契约锁在2023年不断成长和进步。 感恩客户相伴成长 2023年&#xff0c;契约锁为“政府机关…

IDEA 控制台中文乱码问题解决方法(UTF-8 编码)

设置 IDEA 编码格式 1&#xff1a;打开 IntelliJ IDEA>File>Setting>Editor>File Encodings&#xff0c;将 Global Encoding、Project Encoding、Default encodeing for properties files 这三项都设置成 UTF-8 2&#xff1a;将 vm option 参数改为&#xff1a; -…

os.path.join(a,b)末尾出现\问题

文章目录 问题描述解决 问题描述 config[save_path] dataset/data/output 并且 config[model_name] &#xff08;空字符串&#xff09; os.path.join() 函数在处理路径时会正确处理路径分隔符&#xff0c;并避免出现多余的斜杠。但是&#xff0c;如果 config[‘save_path’]…

Linux进程以及计划任务

一.程序和进程以及线程 内核功用&#xff1a;进程管理、内存管理、文件系统、网络功能、驱动程序、安全功能等 对于所有的操作系统&#xff0c;都有基本的功能 1.程序 保存在硬盘、光盘等介质中的可执行代码和数据&#xff08;硬盘上躺着&#xff09; 静态保存的代码 执行…

MongoDB—SQL到MongoDB映射图表

一、术语和概念 下表显示了各种 SQL 术语和概念 以及相应的 MongoDB 术语和概念。 SQL Terms/Concepts MongoDB Terms/Concepts database database table collection row document or BSON document column field index index table joins $lookup, embedded docu…

ssm基于echarts的基金交易网站的设计与实现论文

摘 要 计算机网络发展到现在已经好几十年了&#xff0c;在理论上面已经有了很丰富的基础&#xff0c;并且在现实生活中也到处都在使用&#xff0c;可以说&#xff0c;经过几十年的发展&#xff0c;互联网技术已经把地域信息的隔阂给消除了&#xff0c;让整个世界都可以即时通话…

C语言中指针变量如何使用

一、指针变量的定义与声明 1.1 定义 指针变量是用来存储另一个变量的内存地址的变量。在C语言中&#xff0c;指针变量的类型是指向某个类型的指针。例如&#xff0c;int *p; 表示一个整型指针变量p。 1.2 声明 指针变量的声明分为两种形式&#xff0c;一种是直接声明&#…