【数据结构】二叉树链式结构的实现(二叉树的遍历、使用二叉树的基本方法、二叉树的创建和销毁)

小编主页详情<-请点击
小编gitee代码仓库<-请点击


本文主要介绍了二叉树链式结构的实现,内容全由作者原创(无AI),同时深度解析了二叉树链式结构常用的功能(二叉树的遍历、使用二叉树的基本方法、二叉树的创建和销毁),并带有配图帮助博友们更好的理解,点个关注不迷路,下面进入正文~~


目录

[1. 前置说明](#1. 前置说明)

2.二叉树的遍历

[2.1 前序、中序以及后序遍历](#2.1 前序、中序以及后序遍历)

[2.2 层序遍历](#2.2 层序遍历)

3.使用二叉树的基本方法

[3.1 二叉树结点个数](#3.1 二叉树结点个数)

3.2二叉树叶子结点个数

[3.3 二叉树第k层结点个数](#3.3 二叉树第k层结点个数)

3.4二叉树查找值为x的结点

3.5二叉树的高度

3.6判断二叉树是否是完全二叉树

[4. 二叉树的创建和销毁](#4. 二叉树的创建和销毁)

4.1二叉树的构建及遍历

4.2二叉树销毁

结语:


1. 前置说明

在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

cpp 复制代码
typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

BTNode* BuyNode(int x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

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

	return node;
}

BTNode* CreatBinaryTree()
{
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);
	BTNode* node6 = BuyNode(6);
	BTNode* node7 = BuyNode(6);


	node1->left = node2;
	node1->right = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;
	node5->right = node7;

	return node1;
}

注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解。

再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:

  1. 空树

  2. 非空:根结点,根结点的左子树、根结点的右子树组成的

从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。

2.二叉树的遍历

2.1 前序、中序以及后序遍历

学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的结点进行相应的操作,并且每个结点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

  1. 前序遍历(Preorder Traversal 亦称先序遍历)--访问根结点的操作发生在遍历其左右子树之前。

  2. 中序遍历(Inorder Traversal)--访问根结点的操作发生在遍历其左右子树之中(间)。

  3. 后序遍历(Postorder Traversal)--访问根结点的操作发生在遍历其左右子树之后。

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

cpp 复制代码
// 二叉树前序遍历 
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	printf("%d ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}
// 二叉树中序遍历
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}
// 二叉树后序遍历
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}

前序遍历结果:1 2 3 4 5 6

中序遍历结果:3 2 1 5 4 6

后序遍历结果:3 2 5 6 4 1

2.2 层序遍历

层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根结点所在层数为1,层序遍历就是从所在二叉树的根结点出发,首先访问第一层的树根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,自上而下,自左至右 逐层访问树的结点的过程就是层序遍历

层序遍历可以使用我们之前学习过的队列实现,总体思路是**上一层带下一层。**先让根进队列,保存根的指针,再将第一层的指针从队列里删除。如果当前根的左子树不为空就向队列插入左子树的根,如果当前根的右子树不为空就向队列插入右子树的根。直到队列为空时,说明全部节点都已经层序遍历了。

底层原理是队列先进先出的特性恰好满足从上往下遍历

下面是层序遍历的完整代码:

cpp 复制代码
void TreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%d ", front->data);
		if(front->left)
			QueuePush(&q, front->left);
		if(front->right)
			QueuePush(&q, front->right);
	}

	QueueDestroy(&q);
}

3.使用二叉树的基本方法

3.1 二叉树结点个数

这里需要注意的是,不建议用size计数算出节点个数,因为在递归中用size计数需要传size的地址,这样就不可避免的要创建一个新变量size,并且size的值在计算完节点个数时并不会置零,因此想要再次计算二叉树节点个数就需要手动将size置零,使用起来会相当麻烦。

在计算节点个数时,直接使用递归即可

下面为正确代码:

cpp 复制代码
int TreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	return TreeSize(root->left) + TreeSize(root->right) + 1;
}

3.2二叉树叶子结点个数

在计算二叉树叶子结点个数时,节点可以分为三种情况:

1.空节点,直接返回0
2.左子树和右子树都为空,说明该节点是叶子节点,返回1
3.左子树和右子树至少有一个不为空,返回左子树的叶子结点个数加上右子树叶子结点个数

下面为计算二叉树叶子结点个数的正确代码:

cpp 复制代码
int TreeLeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;

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

	return TreeLeafSize(root->left)
		+ TreeLeafSize(root->right);
}

3.3 二叉树第k层结点个数

这题的关键在于用K判断层数,每往下走一层,K都要减一。例如我们要算第三层节点的个数,遍历第一层时,k等于3;第二层时,k等于2;第三次时,k等于1。因此,当k等于1时,就是我们要算的节点。如果k不等于1,就返回左子树k-1层节点的个数加上右子树k-1层节点的个数

下面为计算 二叉树第k层结点个数的完整代码:

cpp 复制代码
// 二叉树第k层节点个数
int TreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return TreeLevelKSize(root->left, k - 1)
		 + TreeLevelKSize(root->right, k - 1);
}

3.4二叉树查找值为x的结点

如果节点是空,就返回NULL;如果节点的值是x,就返回root;如果以上都不是,就先计算左子树的查找值为x的结点,如果不为空,就返回左子树查找到的值为x的结点;如果为空,就返回右子树查找值为x的结点。

下面为二叉树查找值为x的结点的完整代码:

cpp 复制代码
// 二叉树查找值为x的节点
BTNode* TreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;
	BTNode* ret = TreeFind(root->left, x);
	if (ret)
		return ret;
	return TreeFind(root->right, x);
}

3.5二叉树的高度

如果节点是空,就返回NULL;如果左子树的高度大于右子树,就返回左子树高度加1;如果右子树的高度大于左子树,就返回右子树高度加1。我们很容易写出下面的代码:

cpp 复制代码
// 有效率问题
//int TreeHeight(BTNode* root)
//{
//	if (root == NULL)
//		return 0;
//
//	return TreeHeight(root->left) > TreeHeight(root->right) ?
//		TreeHeight(root->left) + 1 : TreeHeight(root->right) + 1;
//}

这样的代码存在很严重的效率问题 ,因为每次比较高度时,都会忘记原来左子树和右子树的高度,需要重新计算。树的高度越高,最底下被重复计算的次数就越多,为2^(n-1)次,因此我们必须避免写出这样的代码。在计算完左子树和右子树的高度时,需要用变量保存他们的值。

下面为计算二叉树高度的正确代码:

cpp 复制代码
int TreeHeight(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int leftHeight = TreeHeight(root->left) ;
	int rightHeight = TreeHeight(root->right);

	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

3.6判断二叉树是否是完全二叉树

判断二叉树是否是完全二叉树和层序遍历非常类似。同样的我们都要先创建一个队列,与层序遍历不同的是,左子树和右子树为空,我们也要插入。直到遇到第一个空,就可以开始判断,如果队列中还有非空,就不是完全二叉树。因为当遇到第一个空时,前面非空的孩子肯定都入队列了,如果非空的孩子都为空,说明二叉树是是完全二叉树。

下面为判断二叉树是否是完全二叉树的正确代码:

cpp 复制代码
// 判断二叉树是否是完全二叉树
bool TreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		
		// 遇到第一个空,就可以开始判断,如果队列中还有非空,就不是完全二叉树
		if (front == NULL)
		{
			break;
		}
		
		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);

		// 如果有非空,就不是完全二叉树
		if (front)
		{
			QueueDestroy(&q);
			return false;
		}
		QueuePop(&q);
	}
	QueueDestroy(&q);
	return true;
}

4. 二叉树的创建和销毁

4.1二叉树的构建及遍历

题目链接:二叉树的构建及遍历

创建二叉树时,我们需要遍历字符串。当遇到'#'时,先让*pi++,再返回NULL。如果没有遇到'#',就说明是有效节点,malloc一个节点存储。因为是前序遍历,所以先存储数据到根节点,不要忘了*pi++,再创建左子树和右子树,最后返回root。

下面是这道题目的完整代码:

cpp 复制代码
#include <stdio.h>
typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

BTNode* CreateTree(char* a,int* pi)
{
    if(a[*pi]=='#')
    {
        (*pi)++;
        return NULL;
    }
    BTNode* root =(BTNode*)malloc(sizeof(BTNode));
    root->data = a[(*pi)++];
    root->left = CreateTree(a,pi);
    root->right = CreateTree(a,pi);
    return root;
}
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	InOrder(root->left);
	printf("%c ", root->data);
	InOrder(root->right);
}
int main() 
{
    char a[100];
    scanf("%s",a);
    int p = 0;
    BTNode* root = NULL; 
    root = CreateTree(a, &p);
    InOrder(root);
}

4.2二叉树销毁

二叉树销毁推荐使用后序遍历,因为这样可以不用保存根节点的地址

下面为二叉树销毁的正确代码:

cpp 复制代码
// 二叉树销毁
void TreeDestory(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	TreeDestory(root->left);
	TreeDestory(root->right);
	free(root);
}

结语:

这篇文章全文由作者手写,图片由画图软件所制,无AI制作,希望各位博友能有所收获

欢迎各位博友的讨论,觉得不错的小伙伴,别忘了点赞关注哦~

相关推荐
水木流年追梦2 小时前
CodeTop Top 300 热门题目8-字符串解码
linux·运维·服务器·前端·算法·leetcode
玖別ԅ(¯﹃¯ԅ)2 小时前
C++ Qt + OpenCV 实现本地人脸识别系统:摄像头采集、ONNX模型加载、人脸库比对完整流程
c++·qt
lcj25112 小时前
精选5大高频链表与数组算法详解:从旋转数组到链表公共节点,LeetCode实战代码+图解全解析
算法·leetcode·链表
xin_nai2 小时前
LeetCode热题100(Java)(4)子串
java·算法·leetcode
如君愿2 小时前
考研复习 Day 24 | 习题--计算机网络第二章(物理层)、数据结构(栈与队列)
数据结构·计算机网络·考研·课后习题·记录考研
永远睡不够的入2 小时前
C++11新特性(3):lambda不是玄学:从编译器生成的仿函数类彻底搞懂 C++ 匿名函数
开发语言·c++
一只数据集2 小时前
机器学习多领域综合数据集分析-包含基因表达时间序列分类回归数据-适用于算法训练模型评估科研应用
人工智能·算法·数据分析
HAPPY酷2 小时前
UE5 C++ 避坑指南:暴力移除 Electronic Nodes 插件,回归纯净开发
开发语言·c++·ue5
小此方2 小时前
Re:思考·重建·记录 现代C++ C++11篇 (四)C++ Lambda 全解析:编译器是如何为你生成仿函数的?
开发语言·c++·c++11·现代c++