【Super数据结构】二叉树的概念、操作大集合!(含深度/广度优先遍历/求深度/前序+中序构建二叉树/后序+中序构建二叉树等)

在这里插入图片描述

🏠关于此专栏:Super数据结构专栏将使用C/C++语言介绍顺序表、链表、栈、队列等数据结构,每篇博文会使用尽可能多的代码片段+图片的方式。
🚪归属专栏:Super数据结构
🎯每日努力一点点,技术累计看得见


树概念

树的概念

在开始介绍树的概念前,我们来聊聊日常生活中的树结构。

其实我们的电脑上的目录本质上就是一种树的结构。以学习资料文件夹为例,它这个位置拆分出3个分支(C语言、C++和数据结构三个文件夹),而这3个分支继续拆分。
在这里插入图片描述
像这种由一个结点向外扩展出多个结点的结构,我们将它称之为树结构。因为它就像是我们生活中倒挂的树,树根在整个树结构的顶端,叶子枝干在树根的下面。
在这里插入图片描述
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

有一个特殊的结点,称为根结点,根节点没有前驱结点。
除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。
因此,树是递归定义的。

例如下图最上面的树中,A是根节点,它可以分为T1、T2、T3三颗子树,而T1可以划分为T1.1、T1.2两颗子树,T2可以划分为T2.2这一颗子树,T3可划分为T3.1和T3.2这两颗子树…(下图只展示了部分划分,T1.1、T2.2两棵树都还可以划分)
在这里插入图片描述
注意:树形结构中,子树之间不能有交集,否则就不是树形结构。

如下图所示,如果没有加上红色箭头,则这是一颗树;如果加上红色箭头,则K结点既是F的子树的根节点,也是G的子树的根节点,使得F和G的子树存在交际,因而加上红色箭头后不是树,而是图。

也可以通过当前结点是否唯一的前驱结点来判断是否是一颗树。在树中,每个结点都有唯一的前驱结点。
在这里插入图片描述

树的相关概念

在这里插入图片描述
节点的度:一个节点含有的子树的个数称为该节点的度;如上图:A的度为3
叶节点或终端节点:度为0的节点称为叶节点;如上图的J、F、K、L、H、I
非终端节点或分支节点:度不为0的节点;如上图的A、B、C、D、E、G
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;如上图:A是B、C、D的父节点
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;如上图:E和F是B的子节点
兄弟节点:具有相同父节点的节点互称为兄弟节点;如上图中H和I是兄弟节点
树的度:一棵树中,最大的节点的度称为树的度;上图的树的度为3
节点的层次:从根开始定义起,根为第1层,节点的子节点为第2层,以此类推
树的高度或深度:节点中节点的最大层数;上图树的高度为4
堂兄弟节点:双亲在同一层的节点互为堂兄弟;上图中J、K互为堂兄弟节点
节点的祖先:从根到该节点所经分支上的所有结点;如上图中,L的祖先有A、C、G
子孙:以某结点为根的子树中,任意一个结点都成为该节点的子孙;上图中所有节点都是A的子孙
森林:由m(m>0)颗互不相交的树的集合称为森林

树的表示

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存值域,也要保存结点和结点之间的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的孩子兄弟表示法

孩子兄弟表示法中,每个节点只需要保存自己下一个新兄弟,和自己的第一个孩子。下方给出每个节点的定义形式,和该表示法的示意图。

typedef int TreeDataType;
struct Node
{
	struct Node* _firstchild;	//指向第一个孩子
	struct Node* _nextBrother;	//指向下一个兄弟
	TreeDataType _data;			//保存节点数据
}

在这里插入图片描述

二叉树的概念和结构

概念

一棵二叉树是结点的一个有限集合,该集合:
1.或者为空
2.由一个根节点加上两棵别称为左子树和右子树的二叉树组成
在这里插入图片描述
从上图可以看出:
①二叉树不存在度大于2的节点
②二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

注意:对于任意的二叉树都是由以下几种复合而成

在这里插入图片描述

特殊的二叉树

满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 2 k − 1 2^k-1 2k1,则它就是满二叉树。

完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。(完全二叉树的高度-1的各层均是满的,最后一层从左往右按顺序连续排列,但最后一层不一定满)

在这里插入图片描述

二叉树的性质

1.若规定根节点的层数为1,则一颗非空二叉树的第i层最多有 2 i − 1 2^{i-1} 2i1个结点。

证明:根结点有一个结点,每个结点最多可以分出2个分支,因而深度每增加1,层结点数就会×2。故可以得到规律:第i层最多有 2 i − 1 2^{i-1} 2i1个结点。
在这里插入图片描述

2.若规定根结点的层数为1,则深度为h的二叉树的最大结点数是 2 h − 1 2^h-1 2h1

证明:由上一个性质可以知道:第i层最多有 2 i − 1 2^{i-1} 2i1个结点。也就是说,二叉树的总结点是首项为1,公比为2的等比数列,由等比数列可知 ( a 1 ( 1 − q n ) ) / ( 1 − q ) (a_{1}(1-q^n))/(1-q) (a1(1qn))/(1q)= ( 1 × ( 1 − 2 h ) ) / ( 1 − 2 ) (1×(1-2^{h}))/(1-2) (1×(12h))/(12)= 2 h − 1 2^{h}-1 2h1

3.对任何一颗二叉树,如果度为0的叶结点个数为 n 0 n_{0} n0,度为2的分支结点个数为 n 2 n_{2} n2,则有 n 0 = n 2 + 1 n_{0}=n_{2}+1 n0=n2+1

4.若规定根结点的层数为1,具有n个结点的满二叉树的深度为 l o g 2 ( n + 1 ) log_{2}(n+1) log2(n+1)

证明:由上面的结论可知深度为h的二叉树的最大结点数是 2 h − 1 2^h-1 2h1,可以得到 n = 2 h − 1 n=2^h-1 n=2h1,两边取对数得 h = l o g 2 ( n + 1 ) h=log_{2}(n+1) h=log2(n+1)

5.对于具有n个基点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对于序号为i的结点有:
①若i>0,i位置结点的双亲结点序号为 ( i − 1 ) / 2 (i-1)/2 (i1)/2;i=0,i为根节点编号,无双亲结点
②若2i+1<n,有孩子序号为2i+1,2i+1>=n则没有左孩子
③若2i+2<n,有孩子序号为2i+2,2i+2>=n则没有右孩子

二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

1.顺序存数
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆会在下一篇文章专门讲解。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

可以通过2i+1找到结点的左孩子,2i+2找到结点的有孩子。下图中,右侧不是完全二叉树,但为了B和C能找到自己的孩子,3和5下标位置必须空出来。如果是一个全部结点只有右子树的树,则会有大量的空间浪费。
在这里插入图片描述
2.链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链。

typedef int TreeDataType;

struct Node2
{
	struct Node2* leftChild;	//保存左孩子
	struct Node2* rightChild;	//保存右孩子
	TreeDataType val;			//值域
}

struct Node3
{
	struct Node3* parent;		//指向父节点
	struct Node3* leftChild;	//指向左孩子结点
	struct Node4* rightChild;	//指向右孩子结点
	TreeDataType val;			//值域
}

二叉树的顺序结构及实现

关于二叉树的顺序存储实现,将在专栏下一篇文章中介绍。下一篇文章还将介绍顺序结构二叉树的应用——堆、堆排序及Top-K问题。

二叉树的链式结构及实现

学习二叉树的各种操作前,我们需要先创建一颗二叉树,但二叉树的创建相对繁琐。我们这里先使用简易的方式创建一颗二叉树。

typedef BTDataType;
typedef struct BinaryTreeNode;
{
	BTDataType _data;
	struct BinaryTreeNode* _left;
	struct BinaryTreeNode* _right;
}BTNode;

BTNode* BuyNode(BTDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	node->_data = x;
	node->left = NULL;
	node->right = NULL;
}

BTNode* CreateTree()
{
	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;
	node1->right = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;
	return node1;
}

上面代码创建的二叉树结构如下图所示。注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式文章最后详解讲解。
在这里插入图片描述

二叉树的遍历

前序/中序/后序遍历(深度优先)

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

1.前序遍历——访问结点的操作发生在遍历完其左右子树之前。

下面的代码使用递归的方式前序遍历二叉树。

//前序遍历
void PreOrder(BTNode* root)
{
	if(root == NULL) return;
	printf("%d ", root->_data);
	PreOrder(root->_left);
	PreOrder(root->_right);
}

对于上面代码的解析如下:
1.当进入PreOrder函数后,先打印根节点的数据1;
2.调用PreOrder(root->_left),进入数据1所在结点的左子树;
3.打印数据1左子树根节点的数据,即打印2;
4.调用PreOrder(root->_left),进入数据2所在结点的左子树;
5.打印数据2左子树根节点的数据,即打印3;
6.调用PreOrder(root->_left),进入数据3所在结点的左子树;
7.由于数据3所在结点的左子树为空,故返回到数据3所在结点;
8.调用PreOrder(root->_right),进入数据3所在结点的右子树;
9.由于数据3所在结点的右子树为空,故返回到数据3所在结点;
10.结点3的左右子树均访问完毕,则返回到数据2所在结点;
11.调用PreOrder(root->_right),进入数据2所在结点的右子树;
12.由于数据2所在结点的右子树为空,故返回到数据2所在结点;
13.结点2的左右子树均访问完毕,则返回到数据1所在结点;
14.根节点右子树的递归调用情况与左子树相同,这里不再画出
在这里插入图片描述

  1. 中序遍历——访问结点的操作发生在遍历完其左子树之后,遍历其右子树之前。

下面的代码使用递归的方式中序遍历二叉树。

//中序遍历
void InOrder(BTNode* root)
{
	if(root == NULL) return;
	InOrder(root->_left);
	printf("%d ", root->_data);
	InPrder(root->_right);
}

对于上面代码的解析如下:
1.当进入InOrder函数后,不打印根节点的值(值为1),而是调用InOrder(root->_left);
2.进入节点2后,不打印当前节点的值,而是调用InOrder(root->_left);
3.进入节点3后,不打印当前节点的值,而是调用InOrder(root->_left);
4.发现节点3的左子树为空后返回;
5.由于节点3的左子树已经遍历完毕,此时可以打印节点3的值;
6.调用InOrder(root->_right),进入节点3的右子树;
7.发现节点3的右子树为空后返回;
8.节点3及节点3的左右子树遍历完毕,返回上一级调用;
9.节点2的左子树遍历完毕,此时打印节点2;
10.调用InOrder(root->_right),进入节点2的右子树;
11.发现节点2的右子树为空后返回;
12.节点2及节点2的左右子树遍历完毕,返回上一级调用;
13.根节点的左子树遍历完毕,此时可以访问并打印根节点的值;
14.进入根节点的右子树进行中序遍历,右子树遍历与上述操作相同,这里不再画出。

在这里插入图片描述

  1. 后序遍历——访问结点的操作发生在遍历完其左右子树之后。

下面的代码使用递归的方式中序遍历二叉树。

//后序遍历
void PostOrder(BTNode* root)
{
	if(root == NULL) return;
	PostOrder(root->_left);
	PostPrder(root->_right);
	printf("%d ", root->_data);
}

对于上面代码的解析如下:
1.调用PostOrder,进入根节点,由于后序需要将左右子树遍历完毕后才能访问当前节点,故需要调用PostOrder(root->_left)进入根节点的左子树;
2.由于节点2的左右子树也未遍历完毕,故调用PostOrder(root->_left)进入节点2的左子树;
3.由于节点3的左右子树也未遍历完毕,故调用PostOrder(root->_left)进入节点3的左子树;
4.节点3的左子树为空,返回上一级调用;
5.节点3的左子树遍历完毕,但右子树尚未遍历,故调用PostOrder(root->_right)进入节点3的右子树;
6.节点3的右子树为空,返回上一级调用;
7.节点3的左右子树遍历完毕,故此时可以访问节点3;
8.节点3及节点3的左右子树已经遍历访问完毕,返回上一级调用;
9.节点2的左子树遍历完毕,但它的右子树尚未遍历,故调用PostOrder(root->_right)进入节点2的右子树;
10.节点2的右子树为空,返回上一级调用;
11.节点2的左右子树遍历完毕,故此时可以访问节点2;
12.节点2及节点2的左右子树已经遍历完毕,返回上一级调用;
13.节点1(根节点)的左子树遍历完毕,但右子树尚未遍历完毕,故根节点需要等右子树遍历完毕才能访问;右子树的遍历与上述步骤相似,这里不再描述。
在这里插入图片描述

对于这三种遍历操作来说,它们在遍历的结点的顺序是相同的,只是访问结点的时机不一样。前序遍历是在遇到节点时就访问该节点;中序遍历是在访问完该节点的左子树后,再访问当前节点;后序遍历是在访问完该节点的左右子树,再访问当前节点。

对于前序前序遍历来说,它遍历上面创建的树得到的序列为123456;中序遍历得到的是321546;后序遍历得到的是325641。

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

层次遍历(广度优先)

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

对于下方的树,我们的遍历顺序步骤是:先遍历第1层,从左到右依次遍历结果为1;再遍历第2层,从左到右依次遍历结果为2、4;再遍历最后一层(第3层),从左到右依次遍历结果为:3、5、6。故这颗树的层次遍历结果为1、2、4、3、5、6。
在这里插入图片描述
我们可以借助队列来实现二叉树的层次遍历,下面以上面这颗树为例,借助队列模拟层次遍历的代码实现过程↓↓↓
step1:在开始开始遍历第1层前,将整颗树根节点入队。
在这里插入图片描述
step2:计算队列节点中的元素个数sz,此时sz为1;取出队列中的元素时,如果它的左右子树不为空,则将左子树、右子树的根节点入队,再访问出队的节点;出队一个元素后,则sz–;当sz为0时,表示当前层遍历完毕。

在这里插入图片描述
step3:再计算队列节点中的元素个数sz,此时sz为1;取出队列中的元素时,如果它的左右子树不为空,则将左子树、右子树的根节点入队,再访问出队的节点;出队一个元素后,则sz–;当sz为0时,表示当前层遍历完毕。
在这里插入图片描述
step4:计算队列节点中的元素个数sz,此时sz为1;取出队列中的元素时,如果它的左右子树不为空,则将左子树、右子树的根节点入队,再访问出队的节点;出队一个元素后,则sz–;当sz为0时,表示当前层遍历完毕。此时队列为空,则层次遍历结束。
在这里插入图片描述
下面给出队列的实现代码(下面实现的层次遍历会使用到该队列)↓↓↓

//queue.h
#pragma once
#include "BinaryTree.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef BTNode* QDataType;

typedef struct QueueNode
{
	struct QueueNode* m_next;
	QDataType m_data;
}QueueNode;

typedef struct Queue
{
	QueueNode* m_head;
	QueueNode* m_tail;
}Queue;

void QueueInit(Queue* pq);
void QueueDestory(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
int QueueEmpty(Queue* pq);
int QueueSize(Queue* pq);

//queue.c
#include "queue.h"

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->m_head = pq->m_tail = NULL;
}

void QueueDestory(Queue* pq)
{
	QueueNode* cur = pq->m_head;
	while (cur)
	{
		QueueNode* next = cur->m_next;
		free(cur);
		cur = next;
	}
	pq->m_head = pq->m_tail = NULL;
}

void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	newnode->m_data = x;
	newnode->m_next = NULL;
	if (pq->m_head == NULL)
	{
		pq->m_head = pq->m_tail = newnode;
	}
	else
	{
		pq->m_tail->m_next = newnode;
		pq->m_tail = newnode;
	}
}

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->m_head);

	QueueNode* next = pq->m_head->m_next;
	free(pq->m_head);
	pq->m_head = next;
	if (pq->m_head == NULL)
		pq->m_tail = NULL;
}

QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->m_head);

	return pq->m_head->m_data;
}

QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->m_head);

	return pq->m_tail->m_data;
}

int QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->m_head == NULL;
}

int QueueSize(Queue* pq)
{
	assert(pq);

	QueueNode* cur = pq->m_head;
	int size = 0;
	while (cur)
	{
		size++;
		cur = cur->m_next;
	}

	return size;
}

层次遍历实现代码如下↓↓↓

void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if(root != NULL)
		QueuePush(&q, root);

	int sz = 0;

	while (!QueueEmpty(&q))
	{
		sz = QueueSize(&q);
		while (sz)
		{
			BTNode* node = QueueFront(&q);
			printf("%d ", node->val);
			if (node->left)
				QueuePush(&q, node->left);
			if (node->right)
				QueuePush(&q, node->right);
			QueuePop(&q);
			sz--;
		}
		printf("\n");
	}
}

二叉树节点个数以及高度等计算

计算二叉树结点个数

一颗二叉树的结点个数等于{左子树结点个数+右子树结点个数+1}

int BTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return BTreeSize(root->left) + BTreeSize(root->left) + 1;
}

计算二叉树叶子结点个数

叶子结点的特点是,它的左右子树均为空。我们可以借助前序、中序、后序遍历的思想(层次遍历也可以),通过判断当前结点的左右子树是否均为空,如果均为空,则叶子结点数量+1;否则叶子结点个数不变。↓↓↓

int BTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);
}

计算二叉树第k层结点个数

这里有两种实现思路,第一种:通过层次遍历的方式,当遍历到第k层时,计算当前层的结点个数;第二种:通过前中后序遍历,并在递归过程中,将层数k传入递归函数中,每向下递归一层,则对k做-1操作,当k==1时,则表示它为第k层结点。

第一种思路实现代码↓↓↓

int BTreeLevelKSize(BTNode* root, int k)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	int level = 1;
	while (!QueueEmpty(&q))
	{
		int sz = QueueSize(&q);
		if (level == k) return sz;
		while (sz)
		{
			BTNode* node = QueueFront(&q);
			QueuePop(&q);
			if (node->left)
				QueuePush(&q, node->left);
			if (node->right)
				QueuePush(&q, node->right);
			sz--;
		}
		level++;
	}
	return -1;
}

第二种思路实现代码↓↓↓

int BTreeLevelKSize(BTNode* root, int k)
{
	assert(k > 0);
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	else
	{
		return BTreeLevelKSize(root->left, k - 1) + BTreeLevelKSize(root->right, k - 1);
	}
}

查找值为x的结点

在寻找值为x的结点时,使用的遍历方式不同,可能导致返回的并不是同一个结点,而只是值相同的结点中的一个。下面代码选择的是前序遍历的思路实现的,你可选择中序、后序或者层次遍历中的任意一种来实现。

BTNode* BTreeFind(BTNode* root, int val)
{
	if (root == NULL)
		return NULL;
	if (root->val == val)
	{
		return root;
	}
	else
	{
		BTNode* left = BTreeFind(root->left, val);
		BTNode* right = BTreeFind(root->right, val);
		return left != NULL ? left : right;
	}
}

二叉树高度计算

树的高度等于max{Height(root->left), Height(root->right)} + 1,即二叉树的高度等于左右子树中较高的那颗子树的高度+1。

int BTreeHeight(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	int left = BTreeHeight(root->left);
	int right = BTreeHeight(root->right);
	return 1 + (left > right ? left : right);
}

二叉树的构建与销毁

二叉树的构建

除了上面介绍的,通过手动构建二叉树外,我们还可以使用前序遍历结果+中序遍历结果,或是后序遍历结果+中序遍历结果来构建二叉树。

先介绍前序遍历结果+中序遍历结果来构建二叉树。由于前序遍历时,总是会先遍历根结点;而中序遍历时,总是先遍历完左子树才遍历当前结点,再遍历右子树。因此,前序遍历可以知道哪个结点是根节点,即下图的数字3;得到的根节点3将中序遍历划分为左右子树,左子树3、2,右子树5、4、6。接下来我们可以处理左子树,由于前序遍历1后面跟着的是2,说明2是这棵树的左子树的根节点;到中序遍历中找到2所在位置,将3、2划分左右子树,左子树为3,右子树为空。以此类推…

在这里插入图片描述
因而,我们可以得到前序+中序遍历序列构造二叉树的代码↓↓↓LeetCode-从前序与中序遍历序列构造二叉树

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
struct TreeNode* _buildTree(int* preorder, int* pi, int preorderSize, int* inorder, int left, int right)
{
    if(*pi >= preorderSize || left > right) return NULL;
    int i = left;
    for(; i <= right; i++)
    {
        if(preorder[*pi] == inorder[i]) break;
    }
    struct TreeNode* node = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    node->val = preorder[*pi];
    ++*pi;
    node->left = _buildTree(preorder, pi, preorderSize, inorder, left, i - 1);
    node->right = _buildTree(preorder, pi, preorderSize, inorder, i + 1, right);
    return node;
}

struct TreeNode* buildTree(int* preorder, int preorderSize, int* inorder, int inorderSize) {
    int pi = 0;
    return _buildTree(preorder, &pi, preorderSize, inorder,0, inorderSize - 1);
}

后序+中序同理↓↓↓LeetCode-从中序与后序遍历序列构造二叉树

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
struct TreeNode* _buildTree(int* inorder, int left, int right, int* postorder, int* ps)
{
    if(*ps < 0 || left > right)return NULL;
    int i = left;
    for(; i <= right; i++)
    {
        if(inorder[i] == postorder[*ps]) break;
    }
    struct TreeNode* node = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    node->val = postorder[*ps];
    --*ps;
    node->right = _buildTree(inorder, i + 1, right, postorder, ps);
    node->left = _buildTree(inorder, left, i - 1, postorder, ps);
    return node;
}

struct TreeNode* buildTree(int* inorder, int inorderSize, int* postorder, int postorderSize) {
    int ps = postorderSize - 1;
    return _buildTree(inorder, 0, inorderSize - 1, postorder, &ps);
}

二叉树的销毁

销毁二叉树时,要销毁当前结点前要么先销毁它的左右子树;要么将它的左右子树的根节点先保存下来。这里选择先销毁它的左右子树,再销毁当前结点。(这是一种后序遍历的思想)↓↓↓

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

判断是否为完全二叉树

完全二叉树是前h-1层均为满,最后一层不一定满,但最后一层如果有结点,则是从左到右连续存在,两节点之间没有任何空位置。这里我们可以使用层次遍历的思想,但不同的是,遇到空结点是,需要在队列之中保存空结点。如果一个树不是完全二叉树,则它的某一层的遍历过程中,会出现非空结点与空结点相间的情况↓↓↓
在这里插入图片描述
如果一颗树是完全二叉树,则它的最后一层遍历时,队列中剩下的结点将均为空结点
在这里插入图片描述

bool IsCompleteTree(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root != NULL) QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		int sz = QueueSize(&q);
		int empty = 0;
		while (sz)
		{
			BTNode* node = QueueFront(&q);
			QueuePop(&q);
			if (node != NULL)
			{
				QueuePush(&q, node->left);
				QueuePush(&q, node->right);
			}
			else
			{
				empty = 1;
				break;
			}
			sz--;
		}
		if (empty)break;
	}
	while (!QueueEmpty(&q))
	{
		if (QueueFront(&q) != NULL) return false;
		QueuePop(&q);
	}
	return true;
}

🎈欢迎进入Super数据结构专栏,查看更多文章。
如果上述内容有任何问题,欢迎在下方留言区指正b( ̄▽ ̄)d

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值