【初阶数据结构10】——链式二叉树的功能实现

文章目录

前言

1.二叉树的链式结构

[1.1 二叉树链式结构的概念](#1.1 二叉树链式结构的概念)

[​编辑1.2 链式结构的构造](#编辑1.2 链式结构的构造)

[1.3 手动创建一个二叉树](#1.3 手动创建一个二叉树)

[2. 二叉树的三种遍历方法](#2. 二叉树的三种遍历方法)

[2.1 前序遍历](#2.1 前序遍历)

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

[2.3 后序遍历](#2.3 后序遍历)

3.部分功能实现与思路解析

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

[3.1.1 创建变量计数](#3.1.1 创建变量计数)

[3.1.2 直接函数递归](#3.1.2 直接函数递归)

[3.2 统计二叉树叶子节点个数](#3.2 统计二叉树叶子节点个数)

[3.3 求二叉树高度](#3.3 求二叉树高度)

[3.4 求第K层节点个数](#3.4 求第K层节点个数)

[3.5 查找值为X的节点](#3.5 查找值为X的节点)


前言

在前面我们将完全二叉树利用数组进行了实现,也就是堆,如果非完全二叉树也利用数组实现,就会导致存储空间的浪费,那我们选择利用利用链式结构进行存储,本文就是主要阐述链式结构的基本实现,以及一些基本功能的实现以及讲解。

1.二叉树的链式结构

1.1 二叉树链式结构的概念

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,目前我们只讲二叉树,后续会有三叉树的学习。
如图是二叉树以及三叉树的结构图

1.2 链式结构的构造

从图中我们不难想到,结构体中应该包含三个元素,两个指针、还有节点数据。那我们根据经验直接写出如下代码:

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

那如果是三叉树的话,结构就是:

cpp 复制代码
struct BinaryTreeNode
{
 struct BinTreeNode* parent; // 指向当前结点的双亲
 struct BinTreeNode* left; // 指向当前结点左孩子
 struct BinTreeNode* right; // 指向当前结点右孩子
 BTDataType data; // 当前结点值域
};

1.3 手动创建一个二叉树

一般我们进行一个新的结构的学习,在创建完结构体之后就要进行增删改查等函数的创建,但是这里稍有不懂,因为在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

我们根据上面给出的结构创建一个对应的二叉树。

cpp 复制代码
Btnode* Buynode(BtDataType x)
{
	Btnode* node = (Btnode*)malloc(sizeof(Btnode));
	node->data = x;
	node->left = node->right = NULL;
	return node;
}
void Test01()
{
	Btnode* node1 = Buynode(1);
	Btnode* node2 = Buynode(2);
	Btnode* node3 = Buynode(3);
	Btnode* node4 = Buynode(4);
	Btnode* node5 = Buynode(5);
	Btnode* node6 = Buynode(6);
	node1->left = node2;
	node2->left = node3;
	node1->right = node4;
	node4->left = node5;
	node4->right = node6;
}

上面的代码应该很容易理解,这里不做过多赘述。

2. 二叉树的三种遍历方法

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

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

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

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

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

下面是对应的遍历顺序:

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

2.1 前序遍历

这里我先直接进行代码展示,让你大概感受一下,函数递归与二叉树遍历的巧妙之处:

cpp 复制代码
void PrevOrder(Btnode* root)
{
	if (root == NULL)//如果根节点是空,那么返回上层函数
	{
		printf("N ");
		return;
	}
	else
		printf("%d ", root->data);

	PrevOrder(root->left);
	PrevOrder(root->right);
}

我们知道,在调用函数时会建立栈帧,如果是函数的递归,那么会建立多个函数栈帧,同时建立的栈帧之间的位置相近,当被调用函数结束之后,会返回到上一层函数当中,继续执行当前函数未完成的那一部分操作,当前操作结束之后,又会返回到上一层函数,如此循环往复,直至到第一个函数结束。

大概思路就是上面,可以自己画一下图理解一下。如果理解了二叉树的前序遍历,那么后面的中序遍历以及后续遍历就很好理解了,后面的遍历过程会直接给出代码。

我们大概推理一下这个流程,首先从根节点1开始,然后到它的左子树,再从左子树的的根节点开始,输出了根节点数据之后,再进入这个根节点的左子树,直至把3这个节点的左子树输出N,返回以三为根节点的函数,再进入它的右子树,依旧是输出N,返回到以三为根节点的函数,这时候以三为根节点的子树遍历完成,可以看成以二为根节点的左子树遍历完成,返回到了以二为根节点的函数中,继续这个函数右子树遍历,遍历完成后,返回到以一为根节点的函数,继续右子树的遍历,如此循环往复,就完成了整个二叉树的前序遍历。

2.2 中序遍历

cpp 复制代码
void MiddleOrder(Btnode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	MiddleOrder(root->left);
	printf("%d ", root->data);
	MiddleOrder(root->right);
}

2.3 后序遍历

cpp 复制代码
void BackOrder(Btnode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	BackOrder(root->left);
	BackOrder(root->right);
	printf("%d ", root->data);
}

3.部分功能实现与思路解析

下面我们将利用上面遍历二叉树的函数递归思想,完成下面这些函数的实现

cpp 复制代码
// 二叉树结点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root);
int BinaryTreeHeight(BTNode* root);
// 二叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

3.1 二叉树的节点个数

3.1.1 创建变量计数
cpp 复制代码
int size;
void TreeSize(Btnode* root)//用前序遍历
{
	if (root == NULL)
		return;
	size++;
	TreeSize(root->left);
	TreeSize(root->right);
}

这里创建的计数变量如果在栈中的,那么会随着函数栈帧的销毁而销毁,那么就失去了原本的作用了,所以我们这里需要创建一个全局变量,因为全局变量是存放在堆区的,只有当整个程序结束之后,才会销毁,但是如果要重复使用函数,我们需要先将该变量更新为0,才能继续使用,这就容易导致使用错误。

3.1.2 直接函数递归
cpp 复制代码
int TreeSize(Btnode* root)//看成根+左子树+右子树
{
	return root == NULL ? 0 : 1 + TreeSize(root->left) + TreeSize(root->right);
}

这里我们的思路是,如果根节点为空,那么就意味着节点为0,不为空,要统计该树的节点个数,那么就是1+左子树的节点数+右子树的节点数。

3.2 统计二叉树叶子节点个数

cpp 复制代码
int TreeleafSize(Btnode* root)
{
	if (root == NULL)
		return;
	if (root->left == NULL && root->right == NULL)
		return 1;
	return TreeleafSize(root->left) + TreeleafSize(root->right);
}

这里root->left == NULL && root->right == NULL,就是判断是否是叶子节点的函数,同时要满足root不能为空。

3.3 求二叉树高度

cpp 复制代码
int TreeHight(Btnode* root)
{
	if (root == NULL)
		return 0;
	int ret1 = TreeHight(root->left);
	int ret2 = TreeHight(root->right);
	return ret1 > ret2 ? ret1 +1: ret2+1;//这里不要忘记加上第一层
	//return TreeHighr(root->left)>TreeHighr(root->right)?
	// TreeHighr(root->left):TreeHighr(root->right)
	//这里如果用这种,当递归层数很深时,如果要进行多次比较,就需要重新再运行函数,且运行多次
	//时间复杂度大大增加了,所以这里直接保存大小
}

注意事项在代码注释中,请读者仔细观看。

3.4 求第K层节点个数

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

3.5 查找值为X的节点

cpp 复制代码
Btnode* FindTreenode(Btnode* root, BtDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;
	Btnode* ret1 = FindTreenode(root->left, x);
	if (ret1)
		return ret1;
	Btnode* ret2 = FindTreenode(root->right, x);
	return ret2;
}

记得将节点保存,否则节点会丢失。

以上就是本文的全部内容,后续将要进行相关OJ题目练习,以及对二叉树的创建与删除。

相关推荐
I_LPL2 小时前
day54 代码随想录算法训练营 图论专题8
数据结构·图论·拓扑排序·dijkstra算法
tankeven2 小时前
HJ131 数独数组
c++·算法
liuyao_xianhui2 小时前
优选算法_丢失的数字_位运算_C++
linux·数据结构·c++·算法·动态规划·哈希算法·散列表
woniu_buhui_fei2 小时前
Java 服务最常见的线上性能故障
java·jvm·算法
sali-tec2 小时前
C# 基于OpenCv的视觉工作流-章37-区域截图
图像处理·人工智能·opencv·算法·计算机视觉
DeepModel2 小时前
【概率分布】正态分布(高斯分布)原理、可视化与机器学习实战
python·算法·概率论
啊哦呃咦唔鱼2 小时前
LeetCode hot100-239 滑动窗口最大值
数据结构·算法·leetcode
m0_743297422 小时前
嵌入式LinuxC++开发
开发语言·c++·算法
2301_800895102 小时前
求最小生成树kruskal还是prim--备战蓝桥杯版h
算法