文章目录
前言
上篇博客我们学习了链式结构的二叉树,从递归角度 实现了二叉树的前中后序遍历 以及各种有关二叉树的结点个数和高度 等函数,今天我们来学习一个不使用递归的二叉树的层序遍历 以及一些二叉树有关的算法题,发车咯
一、二叉树的层序遍历
二叉树的层序遍历就是从所在二叉树的根结点出发 ,首先访问第一层的树根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历
层序遍历还要借助我们的数据结构:队列
对队列不熟悉的可以看看我的这篇博客嗷
链接:栈和队列
有图才能有真相
其实就是当前一层的结点入队列 ,然后一个一个出队列,出队列时把该节点的左右孩子入队列,这样下一个层级的结点就跟着上一层结点之后啦,重复这个操作,层序遍历就完成啦。话不多说,上代码
这是队列的结构以及一些函数的声明
这里把之前的 int 改成 BTNode 就好啦 队列里面的元素是二叉树的结点类型*
cpp
typedef BTNode* QDataType; // int 改成 BTNode*
typedef struct QueueNode // 队列内每个元素用链式结构 一个一个结点连接
{
QDataType data;
struct QueueNode* next;
}QueueNode;
typedef struct Queue // 队列
{
QueueNode* phead; // 队头
QueueNode* ptail; // 队尾
int size;
}Queue;
void QueueInit(Queue* pq); // 队列初始化
void QueuePush(Queue* pq, QDataType x); // 入队
void QueuePop(Queue* pq); // 出队
bool QueueEmpty(Queue* pq); // 判空函数
QDataType QueueFront(Queue* pq); // 获取对头元素
QDataType QueueBack(Queue* pq); // 获取队尾元素
int QueueSize(Queue* pq); // 队内有效元素
void QueueDestory(Queue* pq); // 销毁队列
void QueuePrint(Queue* pq); // 打印 方便测试代码
借助队列实现层序遍历
cpp
// 层序遍历
void LevelOrder(BTNode* root)
{
//借助数据结构------队列
Queue q;
QueueInit(&q);
QueuePush(&q, root); // 初始第一层的根结点入队列
while (!QueueEmpty(&q))
{
//取队头,出队头
BTNode* top = QueueFront(&q);
QueuePop(&q);
cout<< top->data;
//将队头左右孩子入队列(不为空)
if (top->left)
{
QueuePush(&q, top->left);
}
if (top->right)
{
QueuePush(&q, top->right);
}
}
QueueDestroy(&q);
}
学习了层序遍历之后,我们可以用它来判断二叉树是否为完全二叉树 ,因为完全二叉树是一层一层去填满每一个结点的,不能跳着填。
思路:正常层序遍历二叉树,当遇到空结点时,跳出循环,此时如果是完全二叉树,那它之后的结点除了空结点是没有有效结点的,如果有,那就不是完全二叉树。
cpp
// 判断⼆叉树是否是完全⼆叉树
bool BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
//取队头,出队头
BTNode* top = QueueFront(&q);
QueuePop(&q);
if (top == NULL)
{
break;
}
//把不为空的队头结点的左右孩子入队列
QueuePush(&q, top->left);
QueuePush(&q, top->right);
}
// 第一个循环之后,来到第二个循环,没有遇到有效元素就是完全二叉树
//队列不一定为空,继续取队头出队头
while (!QueueEmpty(&q))
{
BTNode* top = QueueFront(&q);
QueuePop(&q);
if (top != NULL)
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}
层序遍历就到这里啦
二、二叉树的有关习题
2.1 单值二叉树
习题链接:单值二叉树
题目
思路
思路:本题给定一个二叉树,判断所有的结点的值是否相同
简简单单的一个题,就是递归判断每一个左右孩子是否与根结点相同 就好啦
对于认真了解二叉树的遍历的我们,洒洒水啦
代码
cpp
class Solution
{
public:
bool isUnivalTree(TreeNode* root)
{
if(root == NULL)
return true;
if(root->left && root->val != root->left->val)
return false;
if(root->right && root->val != root->right->val)
return false;
return isUnivalTree(root->left) && isUnivalTree(root->right);
}
};
轻松拿下
2.2 相同的树
习题链接:相同的树
题目
思路
思路:本题和上一题有些类似,算是上一个题的拓展学习吧,需要判断是不是两颗相同的树
我们就同时递归两个树就好啦,一边递归一边判断结点的值是否相等
另外再处理一下不是相同的两颗树的判断就好啦
代码
cpp
class Solution
{
public:
bool isSameTree(TreeNode* p, TreeNode* q)
{
if(q == NULL && p == NULL) // 如果两颗树的当前结点都为空 返回true
return true;
if(q == NULL || p == NULL) // 如果其中有一个不为空 证明两颗树的结点个数不同
return false;
if(p->val != q->val) // 值不相同
return false;
return isSameTree(p->left,q->left) && isSameTree(p->right,q->right);
// 递归到每个根节点的左右子树
}
};
拿下拿下
2.3 对称的树
习题链接:对称的树
题目
思路
思路:本题要判断一颗树是否为对称二叉树
一开始没什么头绪,但是想到对称,就从对称入手了,除了第一层的根节点,如果符合对称条件,左右子树是两颗左右孩子相反的树 ,这好像与判断两颗相同的树有些许差别 ,但处理一下就好啦,递归第一颗树的左节点和第二颗树的右结点匹配,右结点与左结点匹配。
代码
cpp
class Solution
{
public:
bool check(TreeNode* p, TreeNode* q)
{
if(p == nullptr && q == nullptr)
{
return true;
}
if(p == nullptr || q == nullptr)
{
return false;
} // 当前结点是否相等 以及 左右孩子反过来匹配判断
return p->val == q->val && check(p->left,q->right) && check(p->right,q->left);
}
bool isSymmetric(TreeNode* root)
{ // 递归第一个根结点的左右子树 分成两棵树来看
return check(root->left, root->right);
}
};
也是稳稳的过啦
2.4 二叉树的遍历
习题链接:二叉树的遍历
题目
思路
思路:本题看起来就有难度了,就像上篇博客留下来的一个小问题,构建二叉树
本题根据读入的先序遍历,根据字符串创建二叉树,然后再进行中序遍历
这颗树建起来还不算麻烦,毕竟已经给了先序遍历,我们就构建链式结构,然后递归字符串的下标就好啦,走一遍先序遍历,然后把值赋给结点
代码
cpp
#include <functional>
#include <iostream>
using namespace std;
typedef struct BinaryTreeNode // 正常创建二叉树链式结构
{
char data;
struct BinaryTreeNode* left;
struct BinaryTreeNode*right;
}BTNode;
BTNode* BuyNode(char ch) // 获取新结点函数
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
node->data = ch;
node->left = node->right =NULL;
return node;
}
BTNode* createTree(string arr,int* pi) // 建树过程
{
if(arr[*pi] == '#') // 这里传了一个pi 表示字符串的下标
{
(*pi)++; // 如果当前字符是 # pi++ 返回
return NULL;
}
BTNode* root = BuyNode(arr[*pi]); // 根据先序遍历 获取值为arr[pi]的结点 pi++
(*pi)++;
root->left = createTree(arr, pi);
root->right = createTree(arr, pi);
return root; // 类似于先序遍历的过程建树
}
void InOrder(BTNode* root) // 再更具中序遍历输出就好了
{
if(root == NULL)
return ;
InOrder(root->left);
cout<<root->data<<' ';
InOrder(root->right);
}
int main()
{
string arr;
cin>>arr;
int i = 0;
BTNode* root = createTree(arr,&i);
InOrder(root);
return 0;
}
拿下
2.5 二叉树的遍历
本题就没有习题链接啦,是实验课的习题
题目
思路
思路:本题和上一题不同的是,上一题是给定先序遍历,然后再建树输出中序遍历。
但本题是给定后序遍历和中序遍历 ,再输出层序遍历 。听起来有点不知道怎么处理了,只给定先序遍历建树还好,这里是给定后序和中序遍历。
**想到后序遍历(左右根)和中序遍历(左根右 )的具体过程,可以根据后序遍历找根节点,然后再根据中序遍历找左右子树,**如此反复这个过程就好啦'
假设我们有以下后序遍历和中序遍历的结果:
后序遍历:3 2 5 6 4 1
中序遍历:3 2 1 5 4 6
确定根节点:
在后序遍历中,最后一个元素 1 是根节点。
分割中序遍历:
在中序遍历中找到根节点 1 的位置,它将中序遍历分为左子树 3 2 和右子树 5 4 6。
递归构建子树:
对于左子树 3 2 和右子树 5 4 6,分别在后序遍历中找到对应的部分(去掉已处理的根节点)。
左子树的后序遍历是 3 2 ,右子树的后序遍历是 5 6 4。
对左子树和右子树重复上述过程,直到构建完整棵树。'
左子树:
右子树:
就是不断找当前根节点的左右子树,把一个大问题化成很多重复相同的子问题。
代码
这里小编就用c++来实现这个题目了嗷
cpp
#include<bits/stdc++.h>
using namespace std;
struct TNode // 二叉树的结点结构 数据域 左右指针域
{
int data;
TNode* left;
TNode* right;
};
void LevelOrder(TNode* root) // 层序遍历
{
queue<TNode*> q; // 这里小编就直接使用 stl库的 queue 函数啦
q.push(root);
while (!q.empty())
{
TNode* top = q.front();
q.pop();
if (top == root)
cout << top->data;
else
cout << ' ' << top->data; // 这里的输出需要注意一下格式 因为题目的最后没有空格
if (top->left)
q.push(top->left);
if (top->right)
q.push(top->right); // 这些步骤就和前面讲的层序遍历一样
}
}
TNode* BuildTree(int* inorder,int * postorder,int n) // 至关重要的环节 建树
{ // 根据后序遍历和中序遍历建树
if (n == 0)
return nullptr;
TNode* node = new TNode;
node->data = postorder[n - 1]; // 这里直接找到根节点 为后序遍历的最后一个数据
node->left = node->right = nullptr;
int pos = 0;
for (pos; pos < n; pos++)
{
if (inorder[pos] == postorder[n - 1]) // 然后再在中序遍历找到根结点 分出左右子树
break; // pos左边的就是左子树 pos 右边的就是右子树
}
node->left = BuildTree(inorder, postorder, pos); // 当前根节点的左子树
node->right = BuildTree(inorder + pos + 1, postorder + pos, n - pos - 1); // 右子树
return node; // 这里中序遍历要在pos结点之后 因为pos是根结点
} // 后序遍历在pos开始 因为根节点在结尾
int main() // 然后就是反复循环左右子树递归就好啦 就像前面举例一样
{
int inorder[50], postorder[50]; // 创建中序遍历和后序遍历的数组
int n;
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> postorder[i];
}
for (int i = 0; i < n; i++)
{
cin >> inorder[i];
}
TNode* root = BuildTree(inorder, postorder, n); // 建树
LevelOrder(root); // 层序遍历
return 0;
}
也是稳稳拿下了
2.6 二叉树的有关选择题
先来了解一些新的二叉树的性质:
对任何一棵二叉树, 如果度为 0 的叶结点个数为 n0 , 度为2 的分支结点个数为 n2 ,则有 n0 = n 2 + 1
假设一个二叉树有a 个度为2的节点, b 个度为1的节点, c 个叶节点,则这个二叉树的边数是2a+b
另一方面,由于共有a+b+c 个节点,所以边数等于a+b+c-1
结合上面两个公式:
2a+b = a+b+c-1 ,即: a = c-1
话不多说,上题
- 某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( )
A 不存在这样的二叉树
B 200
C 198
D 199
由前面推导出来的 a = c-1 叶子结点个数有200个
2.在具有 2n 个结点的完全二叉树中,叶子结点个数为( )
A n
B n+1
C n-1
D n/2
已知是完全二叉树则有 b = 1或0 2a+b+1 = 2n 若要符合要求 b只能=1 所以 a = n-1 、c = n;
3.一棵完全二叉树的结点数为531个,那么这棵树的高度为( )
A 11
B 10
C 8
D 12
完全二叉树最后一层之前都是满的,所以可以根据2的次幂来求层数,前面在最开始讲到满二叉树时有结点个数的公式:结点个数 = 2^k-1 为层数
2的9次幂是512 所以这颗树的高度为10
4.一个具有767个结点的完全二叉树,其叶子结点个数为()
A 383
B 384
C 385
D 386
完全二叉树,还是回到 b只能取1或0 则2a+b+1 = 767 b只能取0 推出 a = 383 c=384;
来一些链式二叉树的遍历题
1.某完全二叉树按层次输出(同一层从左到右)的序列为 ABCDEFGH 。该完全二叉树的前序序列为
A ABDHECFG
B ABCDEFGH
C HDBEAFCG
D HDEBFGCA
越来越觉得完全二叉树是个好东西
左子树 b d e h 右子树 c f h
2.二叉树的先序遍历和中序遍历如下:先序遍历:EFHIGJK;中序遍历:HFIEJKG.则二叉树根结点为
A E
B F
C G
D H
找根节点 给了先序遍历 那不就是E嘛
3.设一课二叉树的中序遍历序列:badce,后序遍历序列:bdeca,则二叉树前序遍历序列为____。
A adbce
B decab
C debac
D abcde
和我们前面根据中序遍历和后序遍历建树的题相似 , 这不是手拿把掐吗
4.某二叉树的后序遍历序列与中序遍历序列相同,均为 ABCDEF ,则按层次输出(同一层从左到右)的序列为
A FEDCBA
B CBAFED
C DEFCBA
D ABCDEF
这个题更有意思,中序遍历和后序遍历相同,那不是只有左子树
总结
简单的一些二叉树的学习就到这里啦,目前小编的石粒还没有涉及到更深层次的二叉树
回顾前面两篇文章,从二叉树的基本概念到顺序结构二叉树(堆)的实现,再到链式结构二叉树的实现,然后是二叉树的遍历方式,最后到习题部分的巩固与深入,对二叉树的遍历越来越深刻,特别是根据中序遍历和后序遍历建树再层序遍历输出,对递归又有了进一步理解,小编持续进步中~~~~
下一篇我们将开启新的数据结构------排序,小编持续更新中,不要走开~~~~