既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
📌导航小助手📌
💡本章重点
- 二叉树的层序遍历
- 二叉树重要面试OJ题
- 🔥算法思想
**🍞一.**广度优先遍历
**🥐Ⅰ.**层序遍历
💡广度优先遍历: 对于二叉树来说又称为层序遍历
- 即访问顺序不同与
先序
、中序
、后序
遍历【这三种遍历统称为:深度优先遍历
】要递归访问完一个分支后才返回再递归访问剩下的分支 - 而
层序遍历
就是一层一层的遍历树的结点,遍历完一层后,才遍历下一层,直至遍历完整棵树
❗特别注意:
- 对于
广度优先遍历
,我们一般借助队列
的数据结构去实现
【对于>队列<的知识有遗忘的,可以点击跳转食用哟~】 - 在遍历完后,切记对
队列
所申请的空间进行释放,以防止内存泄露
的情况
➡️实现方式:
- 1️⃣先将第一层的树的结点入队列
- 2️⃣当队列不为
NULL
时,可以借助队列FIFO
(先进先出)原则,进行对已经入队列的树的结点依次读取(达到访问结点
的效果)并删除在队列中已经访问过的结点 - 3️⃣在上述删除某个结点的同时,将此结点的孩子结点插入队列中(即相当于同时对下一层进行处理,以达到访问完这一层后,可以继续访问孩子节点所在的层)
- 4️⃣重复上述步骤,直至
队列
为NULL
,代表整棵树已完全遍历
✊动图示例:
👉代码实现:
1️⃣实现队列
的数据结构:
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode\* next;
QDataType data;
}QNode;
typedef struct Queue
{
//int size;
QNode\* head;
QNode\* tail;
}Queue;
void QueueInit(Queue\* pq);
void QueueDestroy(Queue\* pq);
void QueuePush(Queue\* pq, QDataType x);
void QueuePop(Queue\* pq);
QDataType QueueFront(Queue\* pq);
QDataType QueueBack(Queue\* pq);
bool QueueEmpty(Queue\* pq);
int QueueSize(Queue\* pq);
void QueueInit(Queue\* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
}
void QueueDestroy(Queue\* pq)
{
assert(pq);
QNode\* cur = pq->head;
while (cur)
{
QNode\* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
void QueuePush(Queue\* pq, QDataType x)
{
assert(pq);
QNode\* newnode = (QNode\*)malloc(sizeof(QNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
if (pq->tail == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
}
void QueuePop(Queue\* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode\* next = pq->head->next;
free(pq->head);
pq->head = next;
}
}
QDataType QueueFront(Queue\* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
QDataType QueueBack(Queue\* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
bool QueueEmpty(Queue\* pq)
{
assert(pq);
return pq->head == NULL;
}
int QueueSize(Queue\* pq)
{
assert(pq);
QNode\* cur = pq->head;
int size = 0;
while (cur)
{
++size;
cur = cur->next;
}
return size;
}
2️⃣实现层序遍历:
void TreeLevelOrder(BTNode\* root)
{
Queue q;
QueueInit(&q);
if (root)
{
QueuePush(&q, root);
}
while (!QueueEmpty(&q))
{
BTNode\* front = QueueFront(&q);
QueuePop(&q);
printf("%d ", front->data);
if (front->left != NULL)
{
QueuePush(&q, front->left);
}
if (front->right != NULL)
{
QueuePush(&q, front->right);
}
}
QueueDestroy(&q);
}
**🥯Ⅱ.**总结
✨综上:就是层序遍历
啦~
➡️相信大家对新的遍历方式有不一样的看法了吧🧡
**🍞二.**二叉树重要面试OJ题
🔥秒杀模板
❗ 秒杀口诀:
- 左右子树之间的
逻辑关系
➕树的遍历方式
❓忘记的同学可以>点击<前往回顾呀
✊让我们用题目来实际运用分析吧~
🏷️ 二叉树的前序遍历【难度:简单】
🔍题目传送门:
牛客网:BM23. 二叉树的前序遍历 |
---|
🌐更多同类题型,不同算法思想学习,可点击>网站跳转<呀😉
给你二叉树的根节点 root
,返回它节点值的 前序
遍历。
- 示例 1:
输入:root = [1,null,2,3]
输出:[1,2,3]
- 示例 2:
输入:root = []
输出:[]
- 示例 3:
输入:root = [1]
输出:[1]
💡解题关键:
- 我们需要知道
前序遍历
的遍历方式 - 本题就可以运用我们的秒杀技巧
❗特别注意:
- 本题中我们需要将
前序遍历
得到的结点存入数组
中,我们便需要提前得知此数组需要开辟多大的空间【即需要知道树的结点个数
】
👉秒杀分析:
- 计算树的结点个数时,整棵树(分为
根节点
、左子树
、右子树
)来看就是:
左子树
总的结点个数 + 右子树
总的结点个数 + 1
(根节点)
- 所以
逻辑关系
为:+
👆综上:
- 秒杀口诀为:
+
➕后序遍历
- 本质:利用递归的性质,先计算左子树总的结点个数,再计算右子树总的结点个数,最终返回的是
左子树
与右子树
总的结点个数的和 +1
(根节点自身个数)
✊动图示例:
👉代码实现:
int treeSize(struct TreeNode\* root)
{
return root == NULL ? 0 : treeSize(root->left) + treeSize(root->right) + 1;
}
void preorder(struct TreeNode\* root, int\*arr, int\* i)
{
//前序遍历
if (root == NULL)
{
return;
}
arr[(\*i)++] = root->val;
preorder(root->left, arr, i);
preorder(root->right, arr, i);
}
int\* preorderTraversal(struct TreeNode\* root, int\* returnSize)
{
\*returnSize = treeSize(root);
int\* arr = (int\*)malloc(sizeof(int)\*(\*returnSize));
int i = 0;
preorder(root, arr, &i);
return arr;
}
➡️补充:
- 我们需要带着自己开辟的
数组
和数组下标
进行前序遍历,因为需要将遍历得到的结点存入数组中 - 所以每一次
下标
的改变都需要让不同的递归栈帧
知道,所以下标
需要传的是地址
(否则如果传的是下标的临时拷贝,那数组内的结点之间就会造成覆盖)
🏷️ 另一棵树的子树【难度:简单】
🔍题目传送门:
Leetcode:572. 另一棵树的子树 |
---|
给你两棵二叉树 root
和 subRoot
。检验 root
中是否包含和 subRoot
具有相同结构和节点值的子树。如果存在,返回 true
;否则,返回 false
二叉树 tree
的一棵子树包括 tree
的某个节点和这个节点的所有后代节点。tree
也可以看做它自身的一棵子树
示例 1:
输入:root = [3,4,5,1,2], subRoot = [4,1,2]
输出:true
示例 2:
输入:root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
输出:false
💡解题关键:
- 遍历
主树
的每一个结点,让每一个结点当作根节点
时,去判断此时的根节点的树是否与子树
相同 - 此时我们便可以复用
检查两棵树是否相同
的代码进行判断
👉秒杀分析:
- 因为需要遍历
主树
的每一个结点,让其每一个结点当根节点
时的树去与子树
判断是否相同 - 所以我们对
主树
采取前序遍历
【即这样遍历下,我们可以快速且从上往下全面的判断是否为子树】,若为其余遍历方式,则可能一开始就错过导致程序做了一些无用功 - 又因为只要
主树
里一旦找到为子树
的情况,就无需继续找子树
了,所以逻辑关系为||
【即主树的某个结点为树时是子树
的情况,返回true
,逻辑关系||
遇上true
就可以直接停止寻找】
➡️做题思路:
- 用
前序遍历
遍历主树每一个结点,让每一个结点当作根节点
去作树,与需要判断的子树
判断两棵树是否相同
- 一旦找到,就返回
true
,停止寻找
✊动图示例:
👉代码实现:
bool isSameTree(struct TreeNode\* p, struct TreeNode\* q)
{
//1.树都为NULL的时候 -- 相等
//2.比较比到 NULL 的时候 == 前面都比完了,那就相等
if (p == NULL && q == NULL)
{
return true;
}
//判断p树和q树结构是否相同
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)
{
//遍历root这棵树的每个结点,每个结点做子树根 ,去跟subRoot比较
if (root == NULL)
{
return false;
}
if (isSameTree(root, subRoot))
{
return true;
}
return isSubtree(root->left, subRoot)|| isSubtree(root->right, subRoot);
}
🏷️ 平衡二叉树【难度:简单】
🔍题目传送门:
Leetcode:110. 平衡二叉树 |
---|
给定一个二叉树,判断它是否是高度平衡的二叉树
本题中,一棵高度平衡二叉树定义为:
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
ubRoot)|| isSubtree(root->right, subRoot);
}
---
### 🏷️ 平衡二叉树【难度:简单】
🔍**题目传送门:**
| [Leetcode:110. 平衡二叉树](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb) |
| --- |
给定一个二叉树,判断它是否是高度平衡的二叉树
本题中,一棵高度平衡二叉树定义为:
>
[外链图片转存中...(img-WKmeR3dm-1715501834839)]
[外链图片转存中...(img-1UPjg9Xg-1715501834839)]
[外链图片转存中...(img-Pp7Kd8Na-1715501834839)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**