文章目录
- [1. 二叉树链式结构及实现](#1. 二叉树链式结构及实现)
- [2. 二叉树的遍历](#2. 二叉树的遍历)
-
- [2.1 二叉树的前序遍历](#2.1 二叉树的前序遍历)
- [2.2 二叉树的中序遍历](#2.2 二叉树的中序遍历)
- [2.3 二叉树的后序遍历](#2.3 二叉树的后序遍历)
- [3. 求树的节点个数](#3. 求树的节点个数)
- [4. 求树的高度(深度)](#4. 求树的高度(深度))
- [5. 求树的叶子节点的个数](#5. 求树的叶子节点的个数)
- [6. 求二叉树第k层节点的个数](#6. 求二叉树第k层节点的个数)
- [7. 二叉树查找值为x的结点](#7. 二叉树查找值为x的结点)
- [8. 二叉树经典 oj 题目解析](#8. 二叉树经典 oj 题目解析)
-
- [8.1 验证二叉搜索树(https://leetcode.cn/problems/validate-binary-search-tree/description/?envType=problem-list-v2\&envId=tree)](#8.1 验证二叉搜索树)
- [8.2 相同的树(https://leetcode.cn/problems/same-tree/description/)](#8.2 相同的树)
- [8.3 另一颗树的子树(https://leetcode.cn/problems/subtree-of-another-tree/)](#8.3 另一颗树的子树)
- [8.4 二叉树遍历(https://www.nowcoder.com/practice/4b91205483694f449f94c179883c1fef)](#8.4 二叉树遍历)
- [8.5 从前序与中序遍历序列构造二叉树(https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/description/?envType=problem-list-v2\&envId=tree)](#8.5 从前序与中序遍历序列构造二叉树)
- [9. 408经典题目解析](#9. 408经典题目解析)
-
- [9.1 2011年408选择题05](#9.1 2011年408选择题05)
- [9.2 2012年408选择题03](#9.2 2012年408选择题03)
- [9.3 2017年408选择题04](#9.3 2017年408选择题04)
- [10. 二叉树层序遍历](#10. 二叉树层序遍历)

1. 二叉树链式结构及实现
c
#include <stdio.h>
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;
}
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);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解。
再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:
1. 空树
2. 非空:根结点,根结点的左子树、根结点的右子树组成的。


2. 二叉树的遍历
学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的结点进行相应的操作,并且每个结点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
按照规则,二叉树的遍历有:前序 / 中序 / 后序的递归结构遍历:
-
前序遍历(PreorderTraversal亦称先序遍历)一一访问根结点的操作发生在遍历其左右子树之前。
-
中序遍历(InorderTraversal)一一访问根结点的操作发生在遍历其左右子树之中。
-
后序遍历(PostorderTraversal)一一访问根结点的操作发生在遍历其左右子树之后。
由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。
2.1 二叉树的前序遍历
前序遍历:根 左子树 右子树
逻辑图 :


前序遍历代码:
c
//前序遍历
void PrevOrder(BTNode* root)
{
if (root == NULL)
{
//空树
printf("N ");// 空结点用 N 表示
return;
}
printf("%d ", root->data);// 先访问根
PrevOrder(root->left);// 再左子树
PrevOrder(root->right); // 最后右子树
}
前序遍历递归代码展开图:


2.2 二叉树的中序遍历
中序遍历: 左子树 根 右子树
逻辑图:


中序遍历代码:
c
//中序遍历
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
中序遍历递归代码展开图:

2.3 二叉树的后序遍历
后序遍历:左子树 右子树 根
逻辑图:


后序遍历代码:
c
//后序遍历
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->data);
}
后序遍历递归代码展开图:

总结:递归的本质是把一个大问题拆解为当前问题和子问题直到问题被分解到不能在被分解时,这也就是递归的返回条件(最小规模的子问题)

3. 求树的节点个数
c
//求树的节点个数
int TreeSize(BTNode* root)
{
return root == NULL ? 0 :
TreeSize(root->left) + TreeSize(root->right) + 1;
}
条件:root == NULL,到达空节点,返回 0,停止递归;
递归过程:递归计算左子树总节点:TreeSize(root->left) ,递归计算右子树总节点:TreeSize(root->right) 。 +1:加上当前这一个根节点
4. 求树的高度(深度)
思想:
- 空树高度 = 0
- 非空树的高度 = max(左子树高度,右子树高度)+1

用一道题来巩固 :二叉树的最大深度
代码:
c
int maxDepth(struct TreeNode* root)
{
if (root == NULL)//空树
return 0;
int leftHeight = maxDepth(root->left);
int rightHeight = maxDepth(root->right);
//用两个变量来记录防止重复计算
return leftHeight > rightHeight ?
leftHeight + 1 : rightHeight + 1;
}
核心逻辑拆解:
1. 递归终止条件: root == NULL 代表走到叶子节点下方空指针,没有这一层,返回高度 0。
2. 递归分解: 一棵树的高度 = 左右子树中更高的那棵子树高度 + 1(+1 代表当前根节点这一层),先算出左子树高度,再算出右子树高度两者对比取大值 +1 返回。
3. 和「节点个数」函数的区别:节点数:左右相加 + 1,统计数量,高度:左右取最大值 + 1,统计层数
5. 求树的叶子节点的个数
c
int TreeleafSize(BTNode* root)
{
if (root == NULL) //空树直接返回 0,没有叶子。
return 0;
if (root->left == NULL && root->right == NULL)//叶子结点:左右孩子都为空,返回 1。
return 1;
return TreeleafSize(root->left) + TreeleafSize(root->right);
//非叶子结点:不是叶子,不能直接加 1,而是分别递归到左子树和右子树去统计叶子数量,然后把结果相加。
}
核心逻辑拆解:
1. 空节点 root==NULL:返回 0,无叶子;
2. 叶子节点(左右都空):返回 1,找到 1 个叶子;
3. 分支节点(有左 / 右孩子):自身不算叶子,只累加左右子树的叶子数量。
6. 求二叉树第k层节点的个数
思路:
-
空树第k层的节点个数:0
-
非空树且k==1的节点个数:1
-
非空树且k>1的节点个数:左子树的第k-1层+右子树的k-1层

代码:
c
int TreeLevelkSize(BTNode* root, int k)
{
if (root == NULL)//空树
return 0;
if (k == 1) //第一层
return 1;
//非空树且k>1
return TreeLevelkSize(root->left, k - 1)
+ TreeLevelkSize(root->right, k - 1);
}
7. 二叉树查找值为x的结点
访问顺序:当前根节点 → 递归左子树 → 递归右子树
1. 先判断自己是不是目标,是就直接返回;
2. 优先搜左子树,左子树找到立刻返回,不会再搜右子树(剪枝,提升效率);
3. 左子树完全遍历完没找到,再去搜右子树;
4. 整条路径遍历结束无匹配,返回NULL。
c
BTNode* TreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)// 空树:返回NULL
return NULL;
if (root->data == x)// 当前结点就是目标:直接返回
return root;
//前序遍历:
BTNode* ret1 = TreeFind(root->left, x);//找左子树
if (ret1)
return ret1;//找到了直接返回不要继续找右边
BTNode* ret2 = TreeFind(root->right, x);//左子树没找到找右子树
if (ret2)
return ret2;
return NULL;//都没找到就返回空
}
8. 二叉树经典 oj 题目解析
8.1 验证二叉搜索树
二叉搜索树(Binary Search Tree, BST)是一种特殊的二叉树,它满足以下递归定义
对于树中的任意一个节点(不仅限于根节点):
1. 左子树中所有节点的值都小于该节点的值。
2. 右子树中所有节点的值都大于该节点的值。
3. 其左子树和右子树也分别都是二叉搜索树。
示例:

首先我们发现二叉搜索树的特点是左边所有的数(左子树)都比它小;右边所有的数(右子树)都比它大,我们就可以联想到使用中序遍历,中序遍历的顺序是 "左 → 根 → 右"。这意味着遍历过程是:先处理完所有比"我"小的数(左边) → 再处理"我"本身 → 最后处理所有比"我"大的数(右边)当你对整棵树递归执行这个操作时,就相当于把整组数据按照"从小到大"的顺序挨个"过了一遍",自然就得到了升序序列。
反过来也是对的:如果一棵二叉树的中序遍历是升序的,那么它一定是一棵合法的二叉搜索树(前提是节点值不重复)。因此,中序遍历升序 常常被用作验证一棵树是否为 BST 的"金标准"算法(只需在遍历时检查当前节点是否大于前一个节点即可)。
代码:
c
//使用全局变量prey记录前一个节点以便做比较
struct TreeNode* prev = NULL;
bool _isValidBST(struct TreeNode* root)
{
if(root == NULL)
return true;
//递归函数的返回值是给上一层"报信"的唯一渠道。你不拦截 false,
//它就石沉大海,上层根本不知道底下出错了上层接收不到信号,
//会继续执行后面的比较逻辑,错误无法提前终止,遍历会走完整棵树,效率低且判断失效。
//所以这里必须做判断
if(_isValidBST(root->left) == false)
return false;
if(prev && prev->val >= root->val)
return false;
//当前节点对于下一次中序遍历到节点,他就是前一个
prev = root;
//这里与上面同理也需要判断
if(_isValidBST(root->right) == false)
return false;
return true;
}
//这里需要使用子函数调用的方法来解决平台测试多组数据时
//全局变量 prev 会保存上一次遍历的节点,OJ 多组测试用例连续调用时会数据污染
//单函数无法区分"我是第一次被调用"还是"我是递归进来的";而拆成两个函数,强行把"初始化"和"递归"隔离了。
bool isValidBST(struct TreeNode* root)
{
prev = NULL;
return _isValidBST(root);
}
总结:本题在思想上比较简单但是实际写代码的过程中有很多的坑点值得我们注意
坑 1:递归发现不合法时,不向上 return false
错误写法:只调用子递归,不判断返回值:_isValidBST(root->left);后果:下层返回 false 后直接作废,上层继续执行判断,无法提前终止,判定逻辑失效。正确操作:子树递归返回 false 就立刻向上 return false,层层短路退出。
坑 2: 使用全局变量 prev,不在入口函数重置
错误做法:只用一个递归函数,每次调用不把 prev 置 NULL,后果:OJ 多组测试用例连续运行时,prev 保存上一棵树的节点,数据污染,判断出错。正确操作:拆分内外双函数,外层 isValidBST 每次调用先执行prev = NULL再进递归。
8.2 相同的树
本题目跟前面的分治子问题和返回值合并的问题类似,我们检查两棵树是否相同,可以先检查根结点,再用递归子问题分别检查左子树是否相同,右子树是否相同,跟上一个题类似要注意一定要处理的是子树的检查结果。
c
bool isSameTree(struct TreeNode* p, struct TreeNode* q)
{
//两个节点都为空,表示当前分支同时到达末尾,这部分结构一致,视为相同,返回真。
if(p == NULL && q == NULL)
return true;
/*
执行到这里,说明不是两个都为空。只要其中一个为空另一个不为空,
就表示结构不同(一棵树在该位置有节点,另一棵没有),直接返回假。
*/
if(p == NULL || q == NULL)
return false;
//两个节点都存在,但值不相等,内容不同,返回假。
if(p->val != q->val)
return false;
/*
当前节点的值和结构都匹配,需要继续检查子树。
只有当左子树完全相同且右子树完全相同时,
两棵树才完全相同。这里用逻辑与 && 连接左右递归结果。
*/
return isSameTree(p->left,q->left) && isSameTree(p->right,q->right);
}
8.3 另一颗树的子树
本题目为前⼀个相同的树问题再加码变形所以我们可以复用上题的代码思路也很简单只需要:
1. 将root中的所有子树找出来
2. 把找出来的子树跟 subroot 进行比较,到这一步就把问题转变为了上一题的判断相同的树
c
bool isSameTree(struct TreeNode* p, struct TreeNode* q)
{
//两个节点都为空,表示当前分支同时到达末尾,这部分结构一致,视为相同,返回真。
if(p == NULL && q == NULL)
return true;
/*
执行到这里,说明不是两个都为空。只要其中一个为空另一个不为空,
就表示结构不同(一棵树在该位置有节点,另一棵没有),直接返回假。
*/
if(p == NULL || q == NULL)
return false;
//两个节点都存在,但值不相等,内容不同,返回假。
if(p->val != q->val)
return false;
/*
当前节点的值和结构都匹配,需要继续检查子树。
只有当左子树完全相同且右子树完全相同时,
两棵树才完全相同。这里用逻辑与 && 连接左右递归结果。
*/
return isSameTree(p->left,q->left)
&& isSameTree(p->right,q->right);
}
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot)
{
if(root == NULL)// 主树为空,不可能包含任何非空子树
return false;
// 当前节点值与子树根值相同,并且从当前节点开始整棵树与子树完全相同
if(root->val == subRoot->val && isSameTree(root,subRoot))
return true;
// 否则,去左子树或右子树中继续寻找匹配的起点
return isSubtree(root->left,subRoot) || isSubtree(root->right,subRoot);
}
总结: 通过 DFS 前序遍历,将 root 中的节点逐一作为子树根节点找出来; 一旦找到一个候选根节点,立刻就地利用 isSameTree 进行比较;若匹配则立即终止,无需找出全部子树。这种"边找边比,找到就停"的策略,正是这段代码既简洁又高效的精髓所在
8.4 二叉树遍历
二叉树的前序序列是无法确定一棵二叉树的,但是题目中的前序序列中用#表示空树,那么我们直接走一个前序遍历过程构造二叉树即可
由于字符串是先序遍历的且#字符代表空树那我们就可以按照根左右的顺序来建立二叉树
示例:ABC##DE#G##F###

代码:
c
#include <stdio.h>
#include <stdlib.h>
//定义一颗二叉树的结构
typedef struct BinTreeNode
{
struct BinTreeNode* left;//左子树
struct BinTreeNode* right;//右子树
char val;
}BTNode;//用 typedef 起别名 BTNode,方便后续使用。
//前序构建树
BTNode* createTree(char* a,int* pi)
//指向当前读取位置下标的指针。
//必须传指针,因为递归过程中需要不断更新下标,保证每个字符只被读取一次。
{
//空树返回空链接到上一层
if(a[*pi] == '#')
{
(*pi)++;
return NULL;
}
//不是空就malloc一个节点
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
root->val = 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->val);
InOrder(root->right);
}
int main()
{
char a[100];
scanf("%s",a);
int i = 0;
BTNode* root = createTree(a,&i);
InOrder(root);
return 0;
}
递归展开图:

8.5 从前序与中序遍历序列构造二叉树
思路:
无重复元素的前序序列和中序序列可以确定⼀棵唯一的二叉树,前序序列为根->左子树->右子树,中序序列为根->左子树->根->右子树。第⼀个核心点在前序序列第一个值 x 可以确定当前树的根,再找到x在中序序列的位置,可以确定把中序序列分割为左子树中序序列 x 右子树中序序列
利用 x 构造好根结点以后,就可以使用前序序列后面的值和左右子树的中序序列区间分别递归构造左右子树。

代码:
c
// 递归函数:把一颗树拆成无数个小树来构建
// preorder: 先序数组(发牌机)
// pi: 指向"当前发到哪张牌"的指针(遥控器)
// inorder: 中序数组(地图)
// inbegin: 当前这棵小树在地图上的左边界
// inend: 当前这棵小树在地图上的右边界
struct TreeNode* _buildTree(int* preorder, int* pi , int* inorder, int inbegin, int inend)
{
/* ★ 情况1:没地盘了 ★
如果左边界大于右边界,说明这块地是空的(没有节点),
就像让你在地图第5格到第3格之间找东西,根本不存在,直接返回空 */
if(inbegin > inend)
return NULL;
/* ★ 情况2:有地盘,开始建根 ★
先序数组的"遥控器"指着谁,谁就是当前这棵树的根。
赶紧把这块地盘的老大建出来 */
struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
root->val = preorder[*pi]; // 取遥控器指向的那张牌(根的值)
(*pi)++; /* 关键一步:牌打出去了!遥控器自动往后挪一位,
下一层递归来取牌时,拿的就是下一张先序牌了 */
/* ★ 在地图(中序)里找根的位置 ★
根在中序里面左边是左子树,右边是右子树,得先找到它在哪 */
int rooti; // 用来记录根在中序数组中的下标
for(rooti = inbegin; rooti <= inend; ++rooti)
{
if(inorder[rooti] == root->val)
break; // 找到了!根就在这个位置
}
/* ★ 开始"划地盘"分给小弟们 ★
现在已知根在中序的 rooti 位置:
左边的 [inbegin, rooti-1] 全都归左子树管
右边的 [rooti+1, inend] 全都归右子树管 */
// 先建左子树:把左边的地盘和"遥控器"传给左子树递归
// 注意:这里传的还是同一个遥控器 pi,因为先序的顺序是"根->左->右",
// 左子树会自己把属于它的先序牌全部消耗掉,然后遥控器刚好指到右子树的牌
root->left = _buildTree(preorder, pi, inorder, inbegin, rooti-1);
// 再建右子树:等左子树建完,遥控器已经自动移到右边了,直接建右边
root->right = _buildTree(preorder, pi, inorder, rooti+1, inend);
return root; // 把建好的这棵小树(或整棵树)交上去
}
// 主函数:题目给的入口
struct TreeNode* buildTree(int* preorder, int preorderSize, int* inorder, int inorderSize)
{
int i = 0; // 这就是那个"遥控器"实体,一开始指向先序数组的第 0 个位置
/* 注意这里传进去的是 &i(遥控器的地址),而不是 i(遥控器的值)
因为递归函数需要修改遥控器的指向,必须传地址才能把修改带出来 */
return _buildTree(preorder, &i, inorder, 0, inorderSize-1);
}
补充说明:
1. 为什么递归要先建 left 再建 right,顺序绝对不能换?
因为先序遍历的顺序就是 根 -> 左 -> 右。先序数组的牌是连续发出来的,必须先把左子树的所有牌消耗完(递归左子树),遥控器才会正好停在右子树的第一张牌上。如果你先递归右子树,拿到的牌就全乱套了。
2. 为什么递归函数参数里没有 preEnd(先序终点)?
因为用不着!我们不需要管先序到哪里结束,遥控器 pi 在中序区间 inbegin, inend 的约束下,天然就能保证拿到的牌正好属于当前这棵小树。只要中序区间划分对了,先序的牌绝对不会拿多或拿少。
9. 408经典题目解析
9.1 2011年408选择题05
若一棵二叉树的前序遍历序列和后序遍历序列分别为1,2,3,4和4,3,2,1,则该二叉树的中序遍历序列不会是
A.1,2,3,4 B.2,3,4,1 C.3,2,4,1 D.4,3,2,1

观察发现:如果左右子树均存在的话,前序序列中,2在1的左子树,后序序列中2在1的右子树,互相矛盾由此我们可以得到:左子树和右子树当中必须有一个是空树

现在需要确定的是3和4的位置在哪里我们继续观察当3和4是2的左右孩子时

在前序序列中如果2有左子树的话3就是左子树的根,在后序序列中2有右子树的话3就是右子树的根,两者互相矛盾。
由此我们得出结论3和4不可能是兄弟关系只能是父子关系
所有中序序列共有8种排列可能,由此我们就可以验证出答案C是不可能的

9.2 2012年408选择题03
若一棵二叉树的前序遍历序列a,e,b,d,c,后序遍历序列为b,c,d,e,a,则根结点的孩子结点
A. 只有e B.有e、b C.有e、c D.无法确定
本题跟上一题相似,a为根,a的左右子树必有一空,因为如果a左右子树均不为空,那么前序序列中e在左子树,而后序序列中e在右子树,矛盾。这样也就得到答案了,a的孩子只有e
9.3 2017年408选择题04
要使一棵非空二叉树的先序序列与中序序列相同,其所有非叶结点须满足的条件是
A.只有左子树 B.只有右子树
C.结点的度均为1 D.结点的度均为2
观察发现:如果非叶结点均没有左子树,则他们的遍历顺序是一样的。

这样我们就轻松得出正确答案是B
总结:画好图尝试去分析才能很好的去观察二叉树的结构进而得出答案
10. 二叉树层序遍历
层序遍历是按照二叉树的层次从上到下,从左到右对二叉树各个结点进行访问。如下图所示,该二叉树的遍历顺序为:ABCDEFGHI

深度优先遍历(DepthFirstSearch,DFS):
一条路走到黑,再回头。你选择一条分支,尽可能深地走下去,直到尽头,然后回溯到上一个岔路口,尝试另一条没走过的路。DFS一般用递归或者用栈辅助实现,二叉树的前序/中序/后序本质就是一种深度优先遍历。
广度优先遍历(BreadthFirstSearch,BFS):
层层推进,由近及远。你从起点开始,先探索所有直接相连的地方,然后再探索这些地方的下一个邻居,像水波纹一样扩散开。BFS一般用队列辅助实现,二叉树的层序遍历本质就是一种广度优先遍历。
层序遍历的思路:
层序遍历,我们需要借助一个队列来现,大思路是当前层结点依次入队列,当前层结点出队列时,带下一层结点入队列。
1. 将二叉树根结点入队列
2. 队列不为空,出队头结frontNode,并将frontNode的下一层(不为空的孩子)入队列
3. 循环重复2,直到队列为空,则遍历结束

层序遍历代码实现:
因为队列是先进先出的先天符合层序遍历的逻辑所以我们模拟队列来实现
c
void Levelorder1(BTNode* root)
{
//指针数组:存节点的地址才能找到根节点的孩子
BTNode* queueArr[100];
int front = 0, rear = 0;
// front指向队⾸,rear指向队尾的下⼀个位置
//root节点不为空把root入队列
if(root)
queueArr[rear++] = root;
while(front != rear)
{
//front = rear时队列为空
BTNode* frontNode = queueArr[front];//取队头的数据
++front;//删除队头数据(出队)
printf("%d ", frontNode->data);
//左右孩子不为空就入队列
if(frontNode->left)
queueArr[rear++] = frontNode->left;
if(frontNode->right)
queueArr[rear++] = frontNode->right;
}
printf("\n");
}
也可使用动态开辟队列的方法:
c
void Levelorder1(BTNode* root) {
// 这⾥使⽤简单的数组实现队列
// 指针数组
BTNode** queueArr = (BTNode**)malloc(sizeof(BTNode*) * 100);
if (queueArr == NULL) {
printf("malloc fail");
return;
}
// front指向队⾸,rear指向队尾的下⼀个位置
int front = 0, rear = 0;
if (root != NULL)
queueArr[rear++] = root; // 根⼊队
while (rear - front != 0) { // rear- front是队列中数据个数
// 取队头结点,并出队这个结点
BTNode* frontNode = queueArr[front++];
printf("%c ", frontNode->data);
// 对头结点的下⼀层不为空的结点⼊队
if (frontNode->left)
queueArr[rear++] = frontNode->left;
if (frontNode->right)
queueArr[rear++] = frontNode->right;
}
printf("\n");
free(queueArr);
}
层序遍历变形:一些考察题目中常常要求分别获取每层的数据,如:依次打印每层数据,一层一行。
使用一个队列(可以是数组或链表),按层序顺序存储节点。
在每一层开始前,记录当前队列长度,这个长度就是该层的节点数。
然后循环处理恰好这么多节点,每处理一个节点就将其左右孩子入队。
处理完一层后打印换行,继续下一层。

代码:
c
void Levelorder1(BTNode* root)
{
//指针数组:存节点的地址才能找到根节点的孩子
BTNode* queueArr[100];
int front = 0, rear = 0;
// front指向队⾸,rear指向队尾的下⼀个位置
int levelSize = 0;
//root节点不为空把root入队列
if(root)
{
queueArr[rear++] = root;
levelSize = 1;//第一层一个节点
}
while(front != rear)
{
//控制让一层全部出完
while(levelSize--)
{
//front = rear时队列为空
BTNode* frontNode = queueArr[front];//取队头的数据
++front;//删除队头数据(出队)
printf("%d ", frontNode->data);
//左右孩子不为空就入队列
if(frontNode->left)
queueArr[rear++] = frontNode->left;
if(frontNode->right)
queueArr[rear++] = frontNode->right;
}
//队列当前层出完后,下一层都进队列,
//队列数据个数就是下一层的节点数
levelSize = rear - front;
printf("\n");
}
printf("\n");
}
至此树与二叉树章节学习告一段落。

下一章将系统学习各类经典排序算法,分析原理、复杂度与适用场景。