首页 > 其他分享 >链式二叉树

链式二叉树

时间:2024-12-01 13:04:02浏览次数:9  
标签:遍历 que 二叉树 链式 NULL root 节点

引言

在探讨数据结构时,我们不难发现,虽然普通的链式二叉树在存储数据上可能不如前面用数组模拟二叉树直观,但其独特的结构为后续的复杂数据结构奠定了基础。特别是当我们谈及搜索问题时,搜索二叉树以其高效的搜索性能脱颖而出,与二分查找法有着异曲同工之妙。但是,二分查找法在实际应用中受限颇多,它要求数据必须有序且存储在数组中,这一条件往往难以满足。更何况,数组在增删元素时,需要移动大量数据,效率较低。

但是,搜索二叉树虽然也面临一些挑战,如不平衡时可能导致搜索效率下降,但其灵活的节点插入和删除操作,以及基于节点值自动排序的特性,使其在许多应用场景中仍然具有显著优势。为了进一步优化搜索性能,我们引入了平衡二叉树、哈希表、B树等高级数据结构。特别是B树,作为数据库系统的底层核心之一,其高效的多路搜索特性极大地提升了数据处理的效率。

其实,在实际应用中,由于普通二叉树在数据存储和检索方面存在局限性,我们并不常直接用它来存储数据。但是,在学习数据结构的道路上,普通二叉树扮演着举足轻重的角色。我们之所以要深入研究它,主要有两大原因:首先,它为后续学习更复杂的树形数据结构奠定了坚实的基础;其次,在各类考试中,普通二叉树往往是不可或缺的考点。

因此,我们将把重点放在对普通二叉树结构的理解上,而不是过多地纠结于其增删查改的具体操作。这是因为,掌握二叉树的结构特点,对于我们后续学习其遍历算法,以及理解更复杂的树形数据结构具有至关重要的意义。

在学习链式二叉树之前,我们需要这样去看待一棵二叉树:一棵二叉树或者就是由根和它的左子树和右子树组成;而单独看它的左子树和右子树,又是由根和对应的左子树和右子树组成。

fa9085f68d4842168227a49132d8bb19.png

 例如上图中的树,1是根,它的左子树由2和3组成,右子树由4、5、6组成;在它的左子树中,树根是2,左子树由3组成,右子树为空;在它的右子树中,树根是4,左子树由5组成,右子树由6组成。

创建链式二叉树(含代码)

数据结构

typedef char dataType;
typedef struct BinaryTreeNode {
	dataType data;
	struct BinaryTreeNode* left;//左孩子
	struct BinaryTreeNode* right;//右孩子
}BTNode;

创建节点

BTNode* createBTNode(dataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	node->data = x;
	node->left = node->right = NULL;
	return node;
}

生成树(测试案例)

BTNode* createBinaryTree()
{
	BTNode* node0 = createBTNode(5);
	BTNode* node1 = createBTNode(3);
	BTNode* node2 = createBTNode(7);
	BTNode* node3 = createBTNode(1);
	BTNode* node4 = createBTNode(4);
	BTNode* node5 = createBTNode(9);

	node0->left = node1;
	node0->right = node2;
	node1->left = node3;
	node1->right = node4;
	node2->right = node5;

	return node0;
}

5e157449be8e4ab19ed344706df5b79f.png

遍历二叉树

二叉树的遍历分前序遍历、中序遍历、后序遍历和层次遍历。

二叉树的前中后序遍历常用递归实现,因为递归能自然地匹配二叉树的递归结构。递归的好处包括代码简洁、逻辑清晰,以及易于理解和维护。

在写实现他们的代码之前,我们需要知道他们对应的遍历顺序和规则,还要能根据给出的链式二叉树图片能写出它对应的遍历。

层次遍历,顾名思义,就是按照每层去遍历,后面我们会详细讲解。

前序遍历

遍历顺序

根→左子树→右子树

测试案例中的树的前序遍历顺序:5→3→1→NULL(1的左孩子)→NULL(1的右孩子)→4→NULL(4的左孩子)→NULL(4的右孩子)→7→NULL(7的左孩子)→9→NULL(9的左孩子)→NULL(9的右孩子)

代码

void preOrder(BTNode* root)
{
	if (root == NULL) {
		printf("NULL ");
		return;
	}
	printf("%d ", root->data);
	preOrder(root->left);
	preOrder(root->right);
}

递归展开图

6e263f0a2dfb4a4388f6fe3726625e79.png

 

中序遍历

遍历顺序

左子树→根→右子树

测试案例中的中序遍历顺序:NULL(1的左孩子)→1→NULL(1的右孩子)→3→NULL(4的左孩子)→4→NULL(4的右孩子)→5→NULL(7的左孩子)→7→NULL(9的左孩子)→9→NULL(9的右孩子)

代码

void inOrder(BTNode* root)
{
	if (root == NULL) {
		printf("NULL ");
		return;
	}
	inOrder(root->left);
	printf("%d ", root->data);
	inOrder(root->right);
}

递归展开图

660cc3cd22a84b53aa4ddca8eb679989.png

后序遍历

遍历顺序

左子树→右子树→根

测试案例中的后序遍历顺序:NULL(1的左孩子)→NULL(1的右孩子)→1→NULL(4的左孩子)→NULL(4的右孩子)→4→3→NULL (7的左孩子)→NULL(9的左孩子)→NULL(9的右孩子)→9→7→5

代码

void postOrder(BTNode* root)
{
	if (root == NULL) {
		printf("NULL ");
		return;
	}
	postOrder(root->left);
	postOrder(root->right);
	printf("%d ", root->data);
}

(这里就不放递归展开图了)

层次遍历

思路

层序遍历也称为广度优先遍历,我们需要借助队列来实现它。我们先通过一张图来理解它的实现:

df4dd5c609284bf1a60bd7a57f790491.png

ed75704482ba4ad598fe17ab56a8bf87.png

 

具体步骤如下:

  1. 检查根节点:首先确保树的根节点不为空。如果为空,则无法进行层序遍历。

  2. 初始化队列:创建一个空的队列,用于存储将要访问的节点。

  3. 加入根节点:将根节点加入队列中。这是遍历的起点。

  4. 遍历队列:开始一个循环,条件是队列不为空。这个循环会一直进行,直到队列中的所有节点都被访问过。

    • 访问节点:将队首元素取出,访问(通常是打印)该节点的数据。

    • 加入子节点:检查当前节点的左子节点和右子节点是否存在。如果存在,将它们依次加入队列的末尾。这样,下一轮循环时会访问这些子节点,从而实现了按层访问。

  5. 队列清理:当队列为空,即所有节点都被访问过后,结束循环。此时,层序遍历完成。最后,销毁队列。

代码

这里的队列实现,我们用前面文章中队列的代码:

 

但是注意,入队的元素是二叉树的节点的指针,随意头文件部分需要稍加修改:

typedef struct BinaryTreeNode* QdataType;
void levelOrder(BTNode* root)
{
	assert(root);
	Queue que;
	initQueue(&que);
	pushQueue(&que, root);

	while (!emptyQueue(&que))
	{
		BTNode* front = queueFront(&que);
		popQueue(&que);
		printf("%d ", front->data);
		if(front->left)
			pushQueue(&que, front->left);
		if (front->right)
			pushQueue(&que, front->right);
	}
	queueDestroy(&que);
}

判断一棵树是不是完全二叉树

分析

 

4bc6c8914a004321ba22af073571098f.png

 我们以这棵完全二叉树为例:

我们可以从图片看出,这棵树是一颗完全二叉树。如果按层序去遍历它(包括空节点也遍历上),我们会发现它的遍历顺序如下:

d701a7405ad44bdea7e6edc4e1cacce7.png

 

 

be9f5f53d7fe482bbbd1b72e6f8b5389.png

 我们再来看看这颗非完全二叉树:

如果按层序去遍历它(包括空节点也遍历上),我们会发现它的遍历顺序如下:

3d2e95f56bdc4af8a7464dabb4b498c7.png

 我们发现,完全二叉树一旦遍历到了空节点,后面一直都会是空节点;而非完全二叉树一旦遍历到了空节点,后面不会一直都是遍历的空节点。

思路

所以,要想判断一棵树是不是完全二叉树,我们只用按层序遍历让里面的节点入队,叶子节点的空节点也要入队上。我们每次遍历其实是访问队头元素,一旦当前访问的队头元素是空节点,我们就判断一下队列中剩下的元素有没有非空节点,有的话说明不是完全二叉树,没有的话说明就是完全二叉树。

代码

bool isCompleteBinaryTree(BTNode* root)
{
	assert(root);
	Queue que;
	initQueue(&que);
	pushQueue(&que, root);

	while (!emptyQueue(&que))
	{
		BTNode* front = queueFront(&que);
		popQueue(&que);
		if (front)
		{
			pushQueue(&que, front->left);
			pushQueue(&que, front->right);
		}
		if (front == NULL) {
			break;//当前访问的队头元素是空节点就退出
		}
	}
	while (!emptyQueue(&que)) //判断一下队列中剩下的元素有没有非空节点
    {
		BTNode* front = queueFront(&que);
		popQueue(&que);
		if (front) {
			queueDestroy(&que);
			return false;
		}
	}
	queueDestroy(&que);
	return true;
}

销毁二叉树

分析

销毁二叉树,其实也是一种遍历的过程,每遍历到一个节点就去释放它。我们前面讲过前中后序遍历和层序遍历,那用哪个比较好呢?

当然是后序遍历。

为什么呢?我们一起来看一下:

如果采用先序遍历、中序遍历或层序遍历来销毁二叉树,可能会在销毁根节点后无法再访问其左右子树,导致子树无法被正确销毁。而后序遍历则可以确保在销毁根节点之前,其左右子树都已经被完整销毁。

代码

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

关于DFS和BFS

DFS就是深度优先搜索,BFS就是广度优先搜索。

不能将广度优先搜索和层序遍历、深度优先搜索和前序遍历混为一谈。二叉树的层序遍历只是在二叉树中的广度优先搜索,而前序遍历只是在二叉树中的深度优先搜索。

标签:遍历,que,二叉树,链式,NULL,root,节点
From: https://blog.csdn.net/2403_82706967/article/details/144126100

相关文章

  • 二叉树的层序遍历
    给你二叉树的根节点root,返回其节点值的层序遍历。(即逐层地,从左到右访问所有节点)。示例输入:root=[3,9,20,null,null,15,7]输出:[[3],[9,20],[15,7]]思路:求解该问题需要使用到队列进行层序遍历,即从根节点开始一层一层的开始遍历,每一次遍历都先确定队列中结点的个数n,用......
  • #Js篇: 链式判断运算符 ?.和Null判断运算符 ??和逻辑赋值运算符||= &&= ??=
    链式判断运算符?.?.运算符,直接在链式调用的时候判断,左侧的对象是否为null或undefined。如果是的,就不再往下运算,而是返回undefined。链判断运算符?.有三种写法。obj?.prop//对象属性是否存在obj?.[expr]//同上func?.(…args)//函数或对象方法是否存在下面是obj?......
  • 【初阶数据结构和算法】初识树与二叉树的概念以及堆和完全二叉树之间的关系
    文章目录一、树的概念与结构1.树的概念2.树的相关术语3.树的表示4.树形结构实际运用举例二、二叉树的概念及特殊二叉树1.二叉树的概念2.特殊的二叉树满二叉树完全二叉树二叉树的性质(由满二叉树特点推导)三、二叉树的存储结构1.二叉树的顺序结构2.二叉树的链式结构......
  • 判断正则二叉树———概念 + 实现模板 + 例题详解(简单易懂)
    判断正则二叉树递归判断概念正则二叉树(RegularBinaryTree):但每个节点要么有两个子节点,要么是叶子节点。实现思路1.设置递归终止条件(1)空节点(node==nullptr)————>returntrue;(2)只有一个子树(!root->left&&root->rig......
  • [数据结构]建立二叉树的中序线索二叉树
     输入二叉树的扩展的先序遍历序列,建立一棵二叉树,然后建立该二叉树的中序线索二叉树,在中序线索二叉树基础上中序遍历,输出中序遍历序列。二叉树结点类型为char,特殊字符为@。输入格式:输入先序遍历序列:ABD@F@@@CE@@@输出格式:输出二叉树的中序遍历序列为:DFBAEC输入样例:......
  • C++关于二叉树的具体实现
    目录1.二叉树的结构2.创建一棵二叉树3.二叉树的先序遍历1.借助栈的先序遍历2.利用递归的先序遍历4.二叉树的中序遍历5.二叉树的后序遍历1.借助栈的后序遍历2.利用递归的后序遍历6.二叉树的层序遍历7.tree.h8.tree.cpp9.main.cpp1.二叉树的结构对于二叉树来说......
  • 解锁【二叉树】的奥秘:方法、策略与实战(104,144,543)
    ......
  • 代码随想录算法训练营第十四天(统一迭代;LeetCode226.翻转二叉树;LeetCode101.对称二叉树
    统一迭代LeetCode144.二叉树的前序遍历题目链接:二叉树的前序遍历题目链接代码/***Definitionforabinarytreenode.*publicclassTreeNode{*intval;*TreeNodeleft;*TreeNoderight;*TreeNode(){}*TreeNode(intval)......
  • 构建与计算:使用递归实现表达式的二叉树解析器
    ✅作者简介:2022年博客新星第八。热爱国学的Java后端开发者,修心和技术同步精进。......
  • [Python手撕]二叉树的锯齿形层序遍历
    二叉树的锯齿形层序遍历给你二叉树的根节点root,返回其节点值的锯齿形层序遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。示例1:输入:root=[3,9,20,null,null,15,7]输出:[[3],[20,9],[15,7]]示例2:输入:root=[1]输出:[[1]]示例3:输入:root......