二叉树的层次遍历_哔哩哔哩_bilibili
https://www.bilibili.com/video/BV1ee4y1q77b/?p=25&spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=01c0a0b4e215da5cc9a422b60e2ca405
一. 二叉树的原理及优缺点
二叉树是计算机科学中的一种重要的数据结构,它具有以下特点和原理:
1.1 原理
- 定义:二叉树是一种树形结构,其中每个节点最多有两个子节点,通常称为左子节点和右子节点。
- 空树:一个没有节点的二叉树称为空树。
- 非空树:至少有一个根节点,每个节点可以有零个、一个或两个子节点。
- 左子树与右子树:在二叉树中,左子树和右子树是有区别的,不能互换位置。
- 遍历:常见的遍历方式包括前序遍历(根-左-右)、中序遍历(左-根-右)和后序遍历(左-右-根)。
1.2 优点
- 易于实现:二叉树的数据结构相对简单,便于理解和实现。
- 搜索效率:在平衡的二叉树中,如二叉搜索树或AVL树,搜索、插入和删除操作的时间复杂度接近O(log n),其中n是树中的节点数。
- 动态数据结构:二叉树可以方便地进行插入和删除操作,适合动态变化的数据集。
- 多种变体:二叉树有许多变种,如二叉搜索树、红黑树、AVL树等,它们针对不同的场景优化了性能。
1.3 缺点
- 不平衡问题:如果二叉树不是平衡的,比如退化成链式结构,那么搜索、插入和删除操作的效率会降低到O(n)。
- 空间开销:二叉树可能需要额外的空间来存储指针,尤其是当树的深度很大时。
- 维护成本:为了保持树的平衡状态,需要在插入和删除操作时进行额外的调整,这增加了实现的复杂性和成本。
1.4 应用
- 文件系统:用于组织文件和目录的层次结构。
- 编译器:语法树和符号表常常采用二叉树的形式。
- 数据库索引:B树和B+树是基于二叉树的扩展,用于高效的数据检索。
- 排序算法:例如堆排序可以看作是特殊的二叉树的应用。
总之,二叉树因其灵活性和效率,在计算机科学的多个领域都有广泛的应用。然而,为了克服其固有的不平衡问题,衍生出了多种平衡二叉树的结构,如AVL树、红黑树等,这些结构在实际应用中更为常见。
二.二叉树相关函数
2.1 定义二叉树
typedef char data_t;
typedef struct node_t {
data_t data;
struct node_t *lchild, *rchild;
} bitree;
2.2 创建二叉树
/**
* 创建并返回一个二叉树的根节点。
*
* 该函数通过递归方式构建二叉树。从输入读取每个节点的值,如果值为'#',则表示当前节点为空节点。
* 否则,创建一个新节点,并将其左子树和右子树分别递归调用本函数来构建。
*
* @return 返回新建的二叉树的根节点,如果内存分配失败或输入结束标志,则返回NULL。
*/
bitree *tree_create(){
data_t val; // 用于存储当前节点的值
bitree *root; // 将要创建的节点指针
scanf("%d", &val); // 从输入读取节点的值
if(val == '#') // 如果值为'#',表示当前节点为空节点,直接返回NULL
return NULL;
if((root = (bitree *)malloc(sizeof(bitree))) == NULL) // 动态分配内存给新节点,检查是否分配成功
{
printf("malloc error\n"); // 如果内存分配失败,打印错误信息
return NULL;
}
root->data = val; // 将读取的值赋给新节点
root->lchild = tree_create(); // 递归调用本函数构建左子树
root->rchild = tree_create(); // 递归调用本函数构建右子树
return root; // 返回新创建的节点
}
该函数用于创建一个二叉树。它通过递归调用自身来构建树的左子树和右子树。函数首先从输入读取节点的值,如果值为'#',表示当前节点为空节点,直接返回NULL。然后,函数动态分配内存给新节点,并检查是否分配成功。如果内存分配失败,打印错误信息并返回NULL。否则,将读取的值赋给新节点,并递归调用本函数构建左子树和右子树。最后,返回新创建的节点。
2.3 先序遍历二叉树
/**
* 预序遍历二叉树
*
* 该函数对二叉树进行预序遍历,即先访问根节点,然后递归遍历左子树,最后递归遍历右子树。
* 这种遍历方式适用于需要先处理根节点,然后再处理子树的情况。
*
* @param root 二叉树的根节点指针。如果根节点为空,则函数直接返回,不进行任何操作。
*/
void preorder(bitree *root){
/* 如果当前节点为空,则返回,不进行任何操作 */
if(root == NULL)
return;
/* 访问当前节点,打印节点数据 */
printf("%d ", root->data);
/* 递归遍历左子树 */
preorder(root->lchild);
/* 递归遍历右子树 */
preorder(root->rchild);
}
2.4 中序遍历二叉树
/**
* 中序遍历二叉树
*
* @param root 二叉树的根节点指针
*
* 中序遍历的顺序为:先遍历左子树,然后访问根节点,最后遍历右子树。
* 该函数实现了这一遍历逻辑,对于每个节点,先递归遍历其左子树,然后打印该节点的数据,
* 最后递归遍历其右子树。如果根节点为空,则直接返回,结束遍历。
*/
void inorder(bitree *root){
/* 如果根节点为空,则返回,结束递归 */
if(root == NULL)
return;
/* 递归遍历根节点的左子树 */
inorder(root->lchild);
/* 打印当前节点的数据 */
printf("%d ", root->data);
/* 递归遍历根节点的右子树 */
inorder(root->rchild);
}
2.5 后序遍历二叉树
/**
* 以后序遍历方式遍历二叉树
*
* 后序遍历的顺序为:先遍历左子树,再遍历右子树,最后访问根节点。
* 这种遍历方式常用于计算表达式树、复制二叉树等场景。
*
* @param root 二叉树的根节点指针。如果根节点为空,则无需进行遍历。
*/
void postorder(bitree *root){
/* 如果当前节点为空,则返回,避免空指针解引用 */
if(root == NULL)
return;
/* 递归遍历左子树 */
postorder(root->lchild);
/* 递归遍历右子树 */
postorder(root->rchild);
/* 输出当前根节点的数据 */
printf("%d ",root->data);
}
2.6 层级遍历二叉树
该函数实现了二叉树的层次遍历(层序遍历)功能。使用一个链式队列来辅助遍历,首先将根节点入队,然后循环从队列中出队一个节点,打印该节点的值,并将其左右子节点依次入队,直到队列为空。最后销毁队列,释放内存。
#ifndef _LINKQUEUE_H_
#define _LINKQUEUE_H_
#include "tree.h"
typedef bitree* datatype;
typedef struct node {
datatype data;
struct node *next;
} listnode, *linklist;
typedef struct {
linklist front;
linklist rear;
} linkqueue;
linkqueue * queue_create(void);
int enqueue(linkqueue *q, datatype x);
datatype dequeue(linkqueue *q);
int queue_empty(linkqueue *q);
int queue_clear(linkqueue *q);
linkqueue *queue_free(linkqueue *q);
#endif
/**
* 层次遍历二叉树
* @param root 二叉树的根节点指针
* @note 本函数通过队列实现二叉树的层次遍历,逐层打印树的节点值
*/
void layerorder(bitree *root){
/* 定义一个链式队列指针 */
linkqueue *lq;
/* 创建队列,如果创建失败则直接返回 */
if((lq = queue_create()) == NULL)
return;
/* 如果根节点为空,则直接返回 */
if(root == NULL)
return;
/* 打印根节点的值 */
printf("%c ",root->data);
/* 将根节点入队 */
enqueue(lq,root);
/* 当队列不为空时,进行循环 */
while(!queue_empty(lq)){
/* 出队一个节点,并赋值给root */
root = dequeue(lq);
/* 如果当前节点有左子节点,则打印左子节点的值并将其入队 */
if(root->lchild != NULL){
printf("%c ",root->lchild->data);
enqueue(lq,root->lchild);
}
/* 如果当前节点有右子节点,则打印右子节点的值并将其入队 */
if(root->rchild != NULL){
printf("%c ",root->rchild->data);
enqueue(lq,root->rchild);
}
}
/* 销毁队列,释放内存 */
queue_free(lq);
}
三. tree.h tree.c
tree.h
#ifndef _TREE_H
#define _TREE_H
typedef char data_t;
typedef struct node_t {
data_t data;
struct node_t *lchild, *rchild;
} bitree;
bitree *tree_create();
void preorder(bitree *root);
void inorder(bitree *root);
void postorder(bitree *root);
void layerorder(bitree *root);
#endif
tree.c
#include"tree.h"
#include"linkqueue.h"
#include<stdio.h>
#include<stdlib.h>
/**
* 创建并返回一个二叉树的根节点。
*
* 该函数通过递归方式构建二叉树。从输入读取每个节点的值,如果值为'#',则表示当前节点为空节点。
* 否则,创建一个新节点,并将其左子树和右子树分别递归调用本函数来构建。
*
* @return 返回新建的二叉树的根节点,如果内存分配失败或输入结束标志,则返回NULL。
*/
bitree *tree_create(){
data_t val; // 用于存储当前节点的值
bitree *root; // 将要创建的节点指针
scanf("%d", &val); // 从输入读取节点的值
if(val == '#') // 如果值为'#',表示当前节点为空节点,直接返回NULL
return NULL;
if((root = (bitree *)malloc(sizeof(bitree))) == NULL) // 动态分配内存给新节点,检查是否分配成功
{
printf("malloc error\n"); // 如果内存分配失败,打印错误信息
return NULL;
}
root->data = val; // 将读取的值赋给新节点
root->lchild = tree_create(); // 递归调用本函数构建左子树
root->rchild = tree_create(); // 递归调用本函数构建右子树
return root; // 返回新创建的节点
}
/**
* 预序遍历二叉树
*
* 该函数对二叉树进行预序遍历,即先访问根节点,然后递归遍历左子树,最后递归遍历右子树。
* 这种遍历方式适用于需要先处理根节点,然后再处理子树的情况。
*
* @param root 二叉树的根节点指针。如果根节点为空,则函数直接返回,不进行任何操作。
*/
void preorder(bitree *root){
/* 如果当前节点为空,则返回,不进行任何操作 */
if(root == NULL)
return;
/* 访问当前节点,打印节点数据 */
printf("%d ", root->data);
/* 递归遍历左子树 */
preorder(root->lchild);
/* 递归遍历右子树 */
preorder(root->rchild);
}
/**
* 中序遍历二叉树
*
* @param root 二叉树的根节点指针
*
* 中序遍历的顺序为:先遍历左子树,然后访问根节点,最后遍历右子树。
* 该函数实现了这一遍历逻辑,对于每个节点,先递归遍历其左子树,然后打印该节点的数据,
* 最后递归遍历其右子树。如果根节点为空,则直接返回,结束遍历。
*/
void inorder(bitree *root){
/* 如果根节点为空,则返回,结束递归 */
if(root == NULL)
return;
/* 递归遍历根节点的左子树 */
inorder(root->lchild);
/* 打印当前节点的数据 */
printf("%d ", root->data);
/* 递归遍历根节点的右子树 */
inorder(root->rchild);
}
/**
* 以后序遍历方式遍历二叉树
*
* 后序遍历的顺序为:先遍历左子树,再遍历右子树,最后访问根节点。
* 这种遍历方式常用于计算表达式树、复制二叉树等场景。
*
* @param root 二叉树的根节点指针。如果根节点为空,则无需进行遍历。
*/
void postorder(bitree *root){
/* 如果当前节点为空,则返回,避免空指针解引用 */
if(root == NULL)
return;
/* 递归遍历左子树 */
postorder(root->lchild);
/* 递归遍历右子树 */
postorder(root->rchild);
/* 输出当前根节点的数据 */
printf("%d ",root->data);
}
/**
* 层次遍历二叉树
* @param root 二叉树的根节点指针
* @note 本函数通过队列实现二叉树的层次遍历,逐层打印树的节点值
*/
void layerorder(bitree *root){
/* 定义一个链式队列指针 */
linkqueue *lq;
/* 创建队列,如果创建失败则直接返回 */
if((lq = queue_create()) == NULL)
return;
/* 如果根节点为空,则直接返回 */
if(root == NULL)
return;
/* 打印根节点的值 */
printf("%c ",root->data);
/* 将根节点入队 */
enqueue(lq,root);
/* 当队列不为空时,进行循环 */
while(!queue_empty(lq)){
/* 出队一个节点,并赋值给root */
root = dequeue(lq);
/* 如果当前节点有左子节点,则打印左子节点的值并将其入队 */
if(root->lchild != NULL){
printf("%c ",root->lchild->data);
enqueue(lq,root->lchild);
}
/* 如果当前节点有右子节点,则打印右子节点的值并将其入队 */
if(root->rchild != NULL){
printf("%c ",root->rchild->data);
enqueue(lq,root->rchild);
}
}
/* 销毁队列,释放内存 */
queue_free(lq);
}