c语言二叉树的层次遍历(超详细)学习笔记
前言:
我学习数据结构的方式是看书加看视频,视频看的是哔哩哔哩up主的 二叉树的层次遍历 我总结并补充他所讲的内容,他的视频适合有c语言基础的看。本文章继我的上一篇文章 c语言二叉树的创建与前序、中序、后序遍历(超详细)学习笔记 ,在二叉树的前序、中序、后序的三种遍历上,继续详细解释二叉树的层次遍历。看完此片文章,一定会有所收获的!
思路:
若想实现层次遍历,那么应该是先遍历根节点A,然后遍历它的左子树B,再遍历它的右子树C,再遍历B的左子树,再遍历B的右子树...
按照这个逻辑关系,我们可以运用树+队的形式,树的结点作为队的元素。
每次入队一个树结点,进行遍历,出队,然后再在将这个树结点的左孩子和右孩子进行入队,依次进行...最终得到层次遍历:A B C D E F G
一、创建树结构体
//创建树结构体 typedef struct TreeNode { char data; struct TreeNode* lChild; struct TreeNode* rChild; }treeNode;
二、创建循环队列结构体(链式存储方式)
循环队列的解释:
队的特点是先进先出,循环队列适应两种存储结构,顺序存储有队满情况,链式存储不存在队满情况。因为是链式存储方式,所以不能通过顺序存储中的real和front来判断队空,通过链式结构中的指针域来联系队中的元素彼此。
那么就有两种循环方式:单循环队列和双循环队列方式。所以我会展示两种方式。当然两种方法各有所爱了,总体上是差不多的。
(1)创建单循环队列
//创建循环队列结构体 typedef struct QueueNode { treeNode* data;树的结点作为元素 struct QueueNode* next; }QueueNode;
(2)创建双循环队列
//创建双循环队列结构体 typedef struct QueueNode { treeNode* data; //树的结点作为元素 struct QueueNode* next; struct QueueNode* pre; }QueueNode;
三、初始化二叉树
仍是使用先序方式进行传入数据 并运用递归。如果想了解递归详细的推导,可以看看我的这篇文章 c语言二叉树的创建与前序、中序、后序遍历(超详细)学习笔记
//初始化二叉树 TreeNode* creatTree() { TreeNode* T; char ch; scanf_s("%c", &ch);//通过输入的ch是否是‘#’来判断该节点是否有孩子节点 if (ch == '#') //'#'代表传入的是空结点 { //此时为空结点 return NULL; } else { //不为空 T = (TreeNode*)malloc(sizeof(TreeNode)); T->data = ch; //创建左子树,逻辑一致,进行递归 T->lChild = creatTree(); //创建右子树,逻辑一致,进行递归 T->rChild = creatTree(); return T; } }
四、创建空队
(1)单循环队列:
//创建空队 QueueNode* initQueue() { QueueNode* Q = (QueueNode*)malloc(sizeof(QueueNode)); Q->data = NULL; Q->next = Q; return Q; }
(2)双循环队列:
//创建空队 QueueNode* initQueue() { QueueNode* Q = (QueueNode*)malloc(sizeof(QueueNode)); Q->data = NULL; Q->next = Q; Q->pre = Q; return Q; }
五、入队 (尾插法入队)
尾插法入队的解释:先进先出,方便出队时快速找到当前元素,Q->next == 出队的元素
如:按照1,2,3,4,5的顺序入队,那么出队的顺序为1,2,3,4,5
头插法入队:Q->1->2->3->4->5,若想出队1,需找到元素1 即:Q->nest == 1,非常快速。
(1)单循环队列:
//入队 尾插法 void enQueue(QueueNode* Q, treeNode* data) { QueueNode* node = (QueueNode*)malloc(sizeof(QueueNode)); QueueNode* q = Q->next; while(q->next != Q) { q = q->next; } node->data = data; q->next = node; node->next = Q; }
(2)双循环队列:
//入队 尾插法 void enQueue(QueueNode* Q, treeNode* data) { QueueNode* node = (QueueNode*)malloc(sizeof(QueueNode)); node->data = data; node->pre = Q->pre; node->next = Q; Q->pre->next = node; Q->pre = node; }
六、判断队是否为空
//判断队是否为空 int isEmpty(QueueNode* Q) { if (Q->next == Q) { return 1; } else { return 0; } }
七、出队 (头插法出队)
头插法出队,非常快速的找到需要出队的元素,Q->next == 出队的元素
找到出队元素,进行出队操作并返回其元素。
(1)单循环队列:
//出队 头插法 treeNode* deQueue(QueueNode* Q) { if (isEmpty(Q)) { return NULL; } else { QueueNode* node = Q->next; treeNode* T = node->data; Q->next = node->next; free(node); return T; } }
(2)双循环队列:
//出队 头插法 treeNode* deQueue(QueueNode* Q) { if (isEmpty(Q)) { return NULL; } else { QueueNode* node = Q->next; treeNode* T = node->data; Q->next = node->next; node->next->pre = Q; free(node); return T; } }
八、层次遍历
//层次遍历 void levelTraverse(QueueNode* Q, treeNode* T) { //先进队 enQueue(Q, T); while (!isEmpty(Q)) { treeNode* node = deQueue(Q); //将node 进行遍历 printf("%c ", node->data); //如果有左孩子 if (node->lChild) { enQueue(Q, node->lChild); } //如果有右孩子 if (node->rChild) { enQueue(Q, node->rChild); } } }
思路:每次入队一个树结点,出队并取出其元素,进行遍历,然后再在将这个树结点的左孩子和右孩子进行入队,依次进行...最终得到层次遍历:A B C D E F G
九、前序遍历
//前序遍历 根 左 右 void preOrder(treeNode* T) { if (T == NULL) { return; } else { //先办事 printf("%c ", T->data); //左孩子 preOrder(T->lChild); //右孩子 preOrder(T->rChild); } }
十、完整代码
(1)单循环队列完整代码:
#include #include //创建树结构体 typedef struct TreeNode { char data; struct TreeNode* lChild; struct TreeNode* rChild; }treeNode; //创建循环队列结构体 typedef struct QueueNode { treeNode* data;树的结点作为元素 struct QueueNode* next; }QueueNode; //初始化二叉树 TreeNode* creatTree() { TreeNode* T; char ch; scanf_s("%c", &ch);//通过输入的ch是否是‘#’来判断该节点是否有孩子节点 if (ch == '#') //'#'代表传入的是空结点 { //此时为空结点 return NULL; } else { //不为空 T = (TreeNode*)malloc(sizeof(TreeNode)); T->data = ch; //创建左子树,逻辑一致,进行递归 T->lChild = creatTree(); //创建右子树,逻辑一致,进行递归 T->rChild = creatTree(); return T; } } //创建空队 QueueNode* initQueue() { QueueNode* Q = (QueueNode*)malloc(sizeof(QueueNode)); Q->data = NULL; Q->next = Q; return Q; } //入队 尾插法 void enQueue(QueueNode* Q, treeNode* data) { QueueNode* node = (QueueNode*)malloc(sizeof(QueueNode)); QueueNode* q = Q->next; while(q->next != Q) { q = q->next; } node->data = data; q->next = node; node->next = Q; } //判断队是否为空 int isEmpty(QueueNode* Q) { if (Q->next == Q) { return 1; } else { return 0; } } //出队 头插法 treeNode* deQueue(QueueNode* Q) { if (isEmpty(Q)) { return NULL; } else { QueueNode* node = Q->next; treeNode* T = node->data; Q->next = node->next; free(node); return T; } } //层次遍历 void levelTraverse(QueueNode* Q, treeNode* T) { //先进队 enQueue(Q, T); while (!isEmpty(Q)) { treeNode* node = deQueue(Q); //将node 进行打印 printf("%c ", node->data); //如果有左孩子 if (node->lChild) { enQueue(Q, node->lChild); } //如果有右孩子 if (node->rChild) { enQueue(Q, node->rChild); } } } //前序遍历 根 左 右 void preOrder(treeNode* T) { if (T == NULL) { return; } else { //先办事 printf("%c ", T->data); //左孩子 preOrder(T->lChild); //右孩子 preOrder(T->rChild); } } int main() { QueueNode* Q = initQueue(); treeNode* T = creatTree(); printf("前序遍历:\n"); preOrder(T); printf("\n"); printf("层次遍历:\n"); levelTraverse(Q, T); printf("\n"); return 0; }
(2)双循环队列完整代码:
#include #include //创建树结构体 typedef struct TreeNode { char data; struct TreeNode* lChild; struct TreeNode* rChild; }treeNode; //创建双循环队列结构体 typedef struct QueueNode { treeNode* data; //树的结点作为元素 struct QueueNode* next; struct QueueNode* pre; }QueueNode; //初始化二叉树 TreeNode* creatTree() { TreeNode* T; char ch; scanf_s("%c", &ch);//通过输入的ch是否是‘#’来判断该节点是否有孩子节点 if (ch == '#') //'#'代表传入的是空结点 { //此时为空结点 return NULL; } else { //不为空 T = (TreeNode*)malloc(sizeof(TreeNode)); T->data = ch; //创建左子树,逻辑一致,进行递归 T->lChild = creatTree(); //创建右子树,逻辑一致,进行递归 T->rChild = creatTree(); return T; } } //创建空队 QueueNode* initQueue() { QueueNode* Q = (QueueNode*)malloc(sizeof(QueueNode)); Q->data = NULL; Q->next = Q; Q->pre = Q; return Q; } //入队 尾插法 void enQueue(QueueNode* Q, treeNode* data) { QueueNode* node = (QueueNode*)malloc(sizeof(QueueNode)); node->data = data; node->pre = Q->pre; node->next = Q; Q->pre->next = node; Q->pre = node; } //判断队是否为空 int isEmpty(QueueNode* Q) { if (Q->next == Q) { return 1; } else { return 0; } } //出队 头插法 treeNode* deQueue(QueueNode* Q) { if (isEmpty(Q)) { return NULL; } else { QueueNode* node = Q->next; treeNode* T = node->data; Q->next = node->next; node->next->pre = Q; free(node); return T; } } //层次遍历 void levelTraverse(QueueNode* Q, treeNode* T) { //先进队 enQueue(Q, T); while (!isEmpty(Q)) { treeNode* node = deQueue(Q); //将node 进行遍历 printf("%c ", node->data); //如果有左孩子 if (node->lChild) { enQueue(Q, node->lChild); } //如果有右孩子 if (node->rChild) { enQueue(Q, node->rChild); } } } //前序遍历 根 左 右 void preOrder(treeNode* T) { if (T == NULL) { return; } else { //先办事 printf("%c ", T->data); //左孩子 preOrder(T->lChild); //右孩子 preOrder(T->rChild); } } int main() { int index = 0; QueueNode* Q = initQueue(); treeNode* T = creatTree(); printf("前序遍历:"); preOrder(T); printf("\n"); printf("层次遍历:"); levelTraverse(Q, T); printf("\n"); return 0; }
十一、运行结果
输入:ABD##E##CF##G##
总结:
二叉树层次遍历的好处包括:
1.按照层级顺序访问节点:层次遍历可以按照树的层级顺序访问节点,从根节点开始,逐层向下遍历,这样可以更加直观地观察和理解整个二叉树的结构。
2.方便实现广度优先搜索:层次遍历是广度优先搜索(BFS)的一种实现方式。BFS是一种重要的图搜索算法,广泛应用于图的遍历、路径搜索、连通性判断等问题中。层次遍历的结果正好可以按照广度优先搜索的顺序得到。
3.快速查找某一层的节点:对于二叉树层次遍历的结果,每一层的节点都是按照从左到右的顺序排列的。因此,可以直接通过索引或下标的方式快速查找到某一层的节点。这种特性在某些问题中非常有用,比如在二叉树的每一层中查找特定的节点或值。
4.方便处理与层次相关的问题:有些问题与二叉树的层次相关,比如计算二叉树的最大深度、最小深度,判断二叉树是否是完全二叉树等等。通过层次遍历,可以轻松地处理与层次相关的问题,因为遍历的结果已经按照层级顺序排列好了。
总之,二叉树层次遍历可以提供对二叉树结构的直观理解,方便实现广度优先搜索,快速查找某一层的节点,并处理与层次相关的问题。
实现二叉树的层次遍历需要树+队的结合,队是循环队列,那么有单循环队列和双循环队列两种方式进行实现。制作不易,真心想让你懂,还是有不足的地方,望见谅嘞。