链式二叉树的实现

在这里插入图片描述

🎯引言

欢迎来到HanLop博客的C语言数据结构初阶系列。在这个系列中,我们将深入探讨各种基本的数据结构和算法,帮助您打下坚实的编程基础。在本篇文章中,我们将探讨另一种重要的二叉树数据结构——链式二叉树(Linked Binary Tree)。不同于顺序结构的二叉树,链式二叉树使用链表节点来存储树中的元素,使其在内存分配和动态调整上具有更大的灵活性。链式二叉树在许多编程问题中都有广泛应用,特别是在递归算法和树遍历等方面。本篇文章将介绍链式二叉树的基本概念、创建方法、基本操作以及其在实际编程中的应用。通过代码示例,您将学会如何在C语言中实现链式二叉树,并理解其优缺点,为进一步学习更复杂的数据结构做好准备。

👓链式二叉树的实现

链式二叉树(Linked Binary Tree)是一种数据结构,它使用链表节点来表示树中的每个元素。每个节点包含一个数据域和两个指针域,分别指向该节点的左子节点和右子节点。这种结构与顺序结构的二叉树不同,它不需要连续的内存空间,因此在动态变化的数据量下具有更好的灵活性和效率。

1.链式二叉树的结构

每个节点通常包括以下部分:

  1. 数据域(Data):存储节点的值或数据。
  2. 左指针域(Left Child):指向左子节点的指针。
  3. 右指针域(Right Child):指向右子节点的指针。
typedef int BTNDataType;
typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
	BTNDataType x;
}BTNode;

在上面的C语言结构体定义中,TreeNode是一个二叉树节点,包含一个整数数据域x,以及两个指针leftright,分别指向左子节点和右子节点

2.链式二叉树相关操作实现

2.1源码展示

BinaryTree.h源码

//BinaryTree.h文件中
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
//包含队列的头文件为了实现层序遍历,
//队列需要自己去实现
//队列的实现在之前文章中有讲过
#include "Queue.h"
typedef int BTNDataType;
typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
	BTNDataType x;
}BTNode;

//前序遍历
void PerOrder(BTNode* root);
//中序遍历
void InOrder(BTNode* root);
//前序遍历
void PostOrder(BTNode* root);


//二叉树的节点个数
int BinaryTreeSize(BTNode* root);

//二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root);

// ⼆叉树第k层结点个数
int BinaryTreeLeavelKSize(BTNode* root, int k);

//二叉树的深度
int BinaryTreeDepth(BTNode* root);

//二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTNDataType x);

// ⼆叉树销毁
void BinaryTreeDestory(BTNode** root);

//二叉树的层序遍历
void LevelOrder(BTNode* root);

//判断二叉树是否为完全二叉树
bool BinaryTreeComplete(BTNode* root);

BinaryTree.c源码

//BinaryTree.c文件中
#include "BinaryTree.h"

//前序遍历 根左右
void PerOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}

	printf("%d ", root->x);
	PerOrder(root->left);
	PerOrder(root->right);
}

//中序遍历 左根右
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}

	InOrder(root->left);
	printf("%d ", root->x);
	InOrder(root->right);
}

//前序遍历 左右根
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}

	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->x);
}

//二叉树的节点个数
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 BinaryTreeLeavelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}

	if (k == 1)
	{
		return 1;
	}

	return BinaryTreeLeavelKSize(root->left, k - 1) + BinaryTreeLeavelKSize(root->right, k - 1);
}

int BinaryTreeDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	//if (root->left == NULL && root->right == NULL)
	//{
	//	return 1;
	//}

	int numLeft = BinaryTreeDepth(root->left);
	int numRight = BinaryTreeDepth(root->right);

	return 1 + (numLeft > numRight ? numLeft : numRight);
}

//找到结点为x的值
BTNode* BinaryTreeFind(BTNode* root, BTNDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}

	if (root->x == x)
	{
		return root;
	}

	BTNode* retLeft = BinaryTreeFind(root->left, x);
	if (retLeft)
		return retLeft;

	BTNode* retRight = BinaryTreeFind(root->right, x);
	if (retRight)
		return retRight;

	return NULL;
}

//二叉树的销毁
void BinaryTreeDestory(BTNode** root)
{
	if (*root == NULL)
	{
		return;
	}

	BTNode* leftTree = (*root)->left;
	BTNode* rightTree = (*root)->right;
	free(*root);
	*root = NULL;
	BinaryTreeDestory(&leftTree);
	BinaryTreeDestory(&rightTree);
}

void LevelOrder(BTNode* root)
{
	Queue pq;
	QueueInit(&pq);
	//将根结点放入队列中

	QueuePush(&pq, root);

	while (!QueueEmpty(&pq))
	{
		if (QueueFront(&pq) != NULL)
			printf("%d ", QueueFront(&pq)->x);
		else
			printf("NULL ");

		if (QueueFront(&pq) != NULL)
		{
			QueuePush(&pq, QueueFront(&pq)->left);
			QueuePush(&pq, QueueFront(&pq)->right);
		}

		QueuePop(&pq);
	}

	QueueDestory(&pq);
}

bool BinaryTreeComplete(BTNode* root)
{
	Queue pq;
	QueueInit(&pq);

	QueuePush(&pq, root);

	while (QueueFront(&pq)!=NULL)
	{
		QueuePush(&pq, QueueFront(&pq)->left);
		QueuePush(&pq, QueueFront(&pq)->right);

		QueuePop(&pq);
	}

	while (!QueueEmpty(&pq))
	{
		if (QueueFront(&pq) != NULL)
		{
			QueueDestory(&pq);
			return false;
		}
		QueuePop(&pq);
	}

	QueueDestory(&pq);
	return true;
}

2.2函数实现详解

2.2.1前中后序遍历

我们下面讲解前中后序遍历都通过下面该树来讲解
在这里插入图片描述

(只详细讲解前序遍历,其他遍历类似,只讲解代码)

1. 前序遍历 (Pre-order Traversal)

定义: 前序遍历按照 “根 -> 左 -> 右” 的顺序访问节点。

访问顺序:

  1. 访问根节点。
  2. 前序遍历左子树。
  3. 前序遍历右子树。

特点: 前序遍历首先访问当前节点,然后依次访问左子树和右子树。这种遍历方式可以用于表达式树的拷贝或克隆,因为它首先处理的是每个节点的值

动图讲解:

在这里插入图片描述

前序遍历 (Pre-order Traversal)

void PerOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}

	printf("%d ", root->x);
	PerOrder(root->left);
	PerOrder(root->right);
}

详细解析:

  • 功能: 前序遍历按照"根-左-右"的顺序访问节点。
  • 过程:
    1. 根节点: 首先访问当前节点,如果节点不为空,则打印节点的值 root->x
    2. 左子树: 递归调用 PerOrder(root->left) 访问左子树。
    3. 右子树: 递归调用 PerOrder(root->right) 访问右子树。

递归思路: 递归地访问左子树和右子树,直到访问到叶子节点为止。每次递归调用时,将当前节点的子节点作为新的根节点。

中序遍历 (In-order Traversal)

void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}

	InOrder(root->left);
	printf("%d ", root->x);
	InOrder(root->right);
}

详细解析:

  • 功能: 中序遍历按照"左-根-右"的顺序访问节点。
  • 过程:
    1. 左子树: 递归调用 InOrder(root->left) 访问左子树。
    2. 根节点: 访问当前节点,打印节点的值 root->x
    3. 右子树: 递归调用 InOrder(root->right) 访问右子树。

用途: 在二叉搜索树中,中序遍历会按升序输出节点的值。

后序遍历 (Post-order Traversal)

void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}

	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->x);
}

详细解析:

  • 功能: 后序遍历按照"左-右-根"的顺序访问节点。
  • 过程:
    1. 左子树: 递归调用 PostOrder(root->left) 访问左子树。
    2. 右子树: 递归调用 PostOrder(root->right) 访问右子树。
    3. 根节点: 访问当前节点,打印节点的值 root->x

用途: 通常用于删除或释放树中的节点,因为在访问根节点之前,确保了其子节点已经被处理。

2.2.2二叉树的其他方法实现

二叉树的节点个数 (BinaryTreeSize)

int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

详细解析:

  • 功能: 计算二叉树的总节点数。
  • 过程:
    1. 空节点: 如果当前节点为空,返回 0。
    2. 递归计数: 返回值为 1(当前节点)加上左子树和右子树的节点数总和。

递归思路: 每次调用都会返回当前节点加上左右子树的节点数,总体上遍历了整个树。

二叉树叶子结点个数 (BinaryTreeLeafSize)

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);
}

详细解析:

  • 功能: 计算二叉树中叶子节点的数量。叶子节点是没有子节点的节点。
  • 过程:
    1. 空节点: 如果当前节点为空,返回 0。
    2. 叶子节点: 如果节点的左右子树均为空,返回 1。
    3. 递归计数: 返回左子树和右子树的叶子节点数之和。

二叉树第 k 层结点个数 (BinaryTreeLeavelKSize)

int BinaryTreeLeavelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}

	if (k == 1)
	{
		return 1;
	}

	return BinaryTreeLeavelKSize(root->left, k - 1) + BinaryTreeLeavelKSize(root->right, k - 1);
}

详细解析:

  • 功能: 计算二叉树第 k 层的节点数。
  • 过程:
    1. 空节点: 如果当前节点为空,返回 0。
    2. 当前层: 如果 k 为 1,表示当前层即为第 k 层,返回 1。
    3. 递归计数: 递归调用函数,计算左右子树中第 k-1 层的节点数之和。

递归思路: 每递归一次,层数减一,直到 k 为 1 时,表示到达目标层。

二叉树的深度 (BinaryTreeDepth)

int BinaryTreeDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	int numLeft = BinaryTreeDepth(root->left);
	int numRight = BinaryTreeDepth(root->right);

	return 1 + (numLeft > numRight ? numLeft : numRight);
}

详细解析:

  • 功能: 计算二叉树的深度(从根节点到最远叶子节点的最长路径)。
  • 过程:
    1. 空节点: 如果当前节点为空,返回 0。
    2. 递归计算: 递归地计算左子树和右子树的深度,取其中较大值加 1(当前节点深度)。

递归思路: 计算每个节点的深度时,考虑左右子树的最大深度,加上当前节点所在的层数。

查找值为 x 的节点 (BinaryTreeFind)

BTNode* BinaryTreeFind(BTNode* root, BTNDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}

	if (root->x == x)
	{
		return root;
	}

	BTNode* retLeft = BinaryTreeFind(root->left, x);
	if (retLeft)
		return retLeft;

	BTNode* retRight = BinaryTreeFind(root->right, x);
	if (retRight)
		return retRight;

	return NULL;
}

详细解析:

  • 功能: 查找二叉树中值为 x 的节点。
  • 过程:
    1. 空节点: 如果当前节点为空,返回 NULL
    2. 匹配节点: 如果当前节点的值为 x,返回当前节点。
    3. 递归查找: 先在左子树中查找,如果找到,返回该节点;否则在右子树中查找。

递归思路: 通过递归遍历树的每一个节点,查找指定值的节点。

二叉树的销毁 (BinaryTreeDestory)

void BinaryTreeDestory(BTNode** root)
{
	if (*root == NULL)
	{
		return;
	}

	BTNode* leftTree = (*root)->left;
	BTNode* rightTree = (*root)->right;
	free(*root);
	*root = NULL;
	BinaryTreeDestory(&leftTree);
	BinaryTreeDestory(&rightTree);
}

详细解析:

  • 功能: 销毁二叉树,释放所有节点的内存。
  • 过程:
    1. 空节点: 如果当前节点为空,返回。
    2. 递归销毁: 递归地销毁左子树和右子树,然后释放当前节点的内存。

递归思路: 先销毁子节点,再销毁当前节点,确保不会丢失任何内存。

2.2.3二叉树的层序遍历和判断是否是完全二叉树

实现这两个方法,需要借助到前面我们所实现的队列的数据结构

Queue.h中需要更改的地方:

typedef struct BinaryTreeNode* QueueDataType;
//我们所需要存储的数据类型是二叉树的结点 所以将int更改为struct BinaryTreeNode*
//注:struct BinaryTreeNode* 中的struct的关键字一定要带着
typedef struct QueueNode
{
	QueueDataType  x;
	struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
	QueueNode* phead;
	QueueNode* ptail;
	int size;
}Queue;

上面就是Queue.h所需更改的地方,其他的代码直接拷贝复制即可使用

层序遍历 (LevelOrder)

void LevelOrder(BTNode* root)
{
	Queue pq;
	QueueInit(&pq);
	QueuePush(&pq, root);

	while (!QueueEmpty(&pq))
	{
		if (QueueFront(&pq) != NULL)
			printf("%d ", QueueFront(&pq)->x);
		else
			printf("NULL ");

		if (QueueFront(&pq) != NULL)
		{
			QueuePush(&pq, QueueFront(&pq)->left);
			QueuePush(&pq, QueueFront(&pq)->right);
		}

		QueuePop(&pq);
	}

	QueueDestory(&pq);
}

详细解析:

  • 功能: 按层次从上到下、从左到右遍历二叉树。
  • 过程:
    1. 队列初始化: 使用队列来保存每层的节点。将根节点入队。
    2. 遍历: 当队列不为空时,取出队首节点并访问(打印)它的值。若该节点不为空,则将其左右子节点依次入队。
    3. 销毁队列: 最后销毁队列,释放内存。

队列的应用: 利用队列的 FIFO 特性实现层序遍历。

动图图解

在这里插入图片描述

判断是否为完全二叉树 (BinaryTreeComplete)

bool BinaryTreeComplete(BTNode* root)
{
	Queue pq;
	QueueInit(&pq);

	QueuePush(&pq, root);

	while (QueueFront(&pq) != NULL)
	{
		QueuePush(&pq, QueueFront(&pq)->left);
		QueuePush(&pq, QueueFront(&pq)->right);

		QueuePop(&pq);
	}

	while (!QueueEmpty(&pq))
	{
		if (QueueFront(&pq) != NULL)
		{
			QueueDestory(&pq);
			return false;
		}
		QueuePop(&pq);
	}

	QueueDestory(&pq);
	return true;
}

详细解析:

  • 功能: 判断一个二叉树是否为完全二叉树。
  • 完全二叉树定义: 除了最后一层外,每层的节点都是满的,并且最后一层的节点从左到右是连续的。
  • 过程:
    1. 队列初始化: 将根节点入队。
    2. 遍历队列: 对于每个节点,如果队列中的节点为非空,则将其左右子节点入队。直到遇到第一个空节点为止。
    3. 验证完全性: 遇到第一个空节点后,检查队列中是否还有非空节点。如果有,则该树不是完全二叉树;如果没有,则是完全二叉树。

关键点: 空节点后不应再出现非空节点,否则树不符合完全二叉树的定义。

🥇结语

在本篇文章中,我们深入探讨了链式二叉树这一重要的数据结构。我们介绍了它的基本概念和结构,讲解了如何创建和操作链式二叉树,并通过实际代码示例展示了其在C语言中的实现。链式二叉树因其灵活性和高效性,在处理许多实际问题时具有重要意义。希望通过本文的学习,您能够更好地理解链式二叉树的工作原理,并能在实际编程中有效地应用这一数据结构。随着对链式二叉树的掌握,您将在数据结构和算法的学习道路上迈出重要一步,为后续的更复杂的树结构和高级算法打下坚实的基础。感谢您阅读本篇文章,我们期待在下一篇文章中继续与您探讨更多有趣的编程知识。

  • 39
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值