数据结构:链表二叉树

一、什么是二叉树?

二叉树是一种数据结构,其中每个节点最多有两个子节点,分别称为左子节点右子节点。二叉树的根节点是整个树的入口,通常我们可以通过递归的方式来访问树中的所有节点。二叉树在计算机科学中应用非常广泛,例如在表达式解析、搜索算法、排序等方面。

在这篇文章中,我们将学习如何在C语言中实现一个简单的二叉树,并讲解其各个功能模块,包括如何创建节点、遍历树、计算节点数量、查找节点等。通过这篇博客,大家会掌握如何设计和实现二叉树,避免常见的错误,并理解这些操作背后的原理。

二、程序的作用和意义

这段程序实现了一个二叉树的基本操作,包括:

  1. 节点创建:动态分配内存并创建二叉树的节点。
  2. 遍历树:通过不同的顺序(前序、中序、后序)来遍历二叉树。
  3. 计算树的信息:例如计算二叉树的节点数、叶子节点数、指定层数的节点个数、树的深度等。
  4. 查找节点:在二叉树中查找某个值。
  5. 销毁树:释放二叉树占用的内存。

这些操作是理解二叉树的重要基础,能够帮助我们更好地应用到实际问题中,比如搜索树、排序树等。

三、如何创建二叉树节点

函数:BTNode* buyNode(BTDataType x)

这个函数用于创建一个新的二叉树节点,并为其分配内存。

BTNode* buyNode(BTDataType x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL) // 检查内存分配是否成功
	{
		perror("malloc error!"); // 打印错误信息
		exit(1); // 终止程序
	}
	newnode->data = x; // 设置节点数据
	newnode->left = newnode->right = NULL; // 初始时,左右孩子指针为空
	return newnode; // 返回新创建的节点
}
  • 为什么要这样写?
    • 使用malloc动态分配内存,使得节点可以在程序运行时根据需要创建。
    • 每个节点的数据由data存储,并且左右孩子初始化为NULL,表示它们还没有子节点。
  • 易错点
    • 忘记检查malloc的返回值可能会导致程序崩溃,必须确保内存分配成功。
    • 忘记初始化左右孩子指针为NULL,会导致一些遍历操作发生错误。

图示:

四、如何遍历二叉树

4.1、前序遍历:PreOrder

前序遍历的顺序是:根-左-右。即首先访问当前节点,然后递归地遍历左子树,再遍历右子树。

void PreOrder(BTNode* root)
{
	if (root == NULL) // 如果节点为空,直接返回
	{
		return;
	}
	printf("%d ", root->data); // 先访问根节点
	PreOrder(root->left); // 递归访问左子树
	PreOrder(root->right); // 递归访问右子树
}

输出:1243

递归图如下:

  • 为什么要这样写?
    • 前序遍历首先访问根节点,再访问左子树,最后访问右子树。
  • 易错点
    • 忘记检查节点是否为空,可能导致程序访问空指针,造成崩溃。

4.2、中序遍历:InOrder

中序遍历的顺序是:左-根-右,即先遍历左子树,访问根节点,再遍历右子树。

void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	InOrder(root->left); // 先递归访问左子树
	printf("%d ", root->data); // 访问根节点
	InOrder(root->right); // 再递归访问右子树
}

输出:4213

4.3、后序遍历:PostOrder

后序遍历的顺序是:左-右-根。先遍历左右子树,最后访问根节点。

void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	PostOrder(root->left); // 先递归访问左子树
	PostOrder(root->right); // 再递归访问右子树
	printf("%d ", root->data); // 最后访问根节点
}

输出:4231

五、如何计算二叉树的节点总数

函数:int BinaryTreeSize(BTNode* root)

我们可以通过递归来计算二叉树中的节点总数。对于每个节点来说,它的总节点数等于其左子树的节点数、右子树的节点数,再加上自身。

int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0; // 空节点返回0
	}
	return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}
  • 为什么要这样写?
    • 每个节点的大小等于其左、右子树的大小加1(根节点本身)。
  • 易错点
    • 忘记处理root == NULL的情况,递归可能陷入死循环或崩溃。

六、如何计算二叉树的叶子节点个数

函数:int BinaryTreeLeafSize(BTNode* root)

叶子节点是没有子节点的节点,即它的leftright都是NULL。通过递归遍历整棵树,可以判断每个节点是否是叶子节点。

int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL) // 判断是否为叶子节点
	{
		return 1; // 是叶子节点,返回1
	}
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
  • 为什么要这样写?
    • 通过递归遍历树,并判断每个节点是否有左右孩子节点,没有孩子节点的就是叶子节点。

七、如何查找二叉树中的某个节点

函数:BTNode* BinaryTreeFind(BTNode* root, BTDataType 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;
	}
	return BinaryTreeFind(root->right, x); // 否则在右子树继续查找
}

八、如何销毁二叉树

函数:void BinaryTreeDestory(BTNode** root)

销毁二叉树时,我们需要释放每个节点所占的内存。可以使用后序遍历的方式,确保每个节点的子节点都被释放之后,才释放自己。

void BinaryTreeDestory(BTNode** root)
{
	if (*root == NULL)
	{
		return;
	}
	BinaryTreeDestory(&((*root)->left)); // 递归销毁左子树
	BinaryTreeDestory(&((*root)->right)); // 递归销毁右子树
	free(*root); // 释放当前节点
	*root = NULL; // 将指针置为NULL,防止野指针
}

九、总结

这篇博客带领大家一步步实现了二叉树的基本操作。通过这些操作,大家掌握了如何创建、遍历、计算、查找和销毁二叉树。写二叉树时常见的错误包括:

  • 忘记检查空节点的情况。
  • 忘记正确地释放内存。
相关推荐
湫ccc19 分钟前
《Python基础》之字符串格式化输出
开发语言·python
mqiqe1 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin1 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python
哭泣的眼泪4081 小时前
解析粗糙度仪在工业制造及材料科学和建筑工程领域的重要性
python·算法·django·virtualenv·pygame
Ysjt | 深2 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__2 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
码农飞飞2 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货2 小时前
Rust 的简介
开发语言·后端·rust