嵌入式数据结构—学习笔记 二叉树

二叉树的层次遍历_哔哩哔哩_bilibiliicon-default.png?t=N7T8https://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 优点

  1. 易于实现:二叉树的数据结构相对简单,便于理解和实现。
  2. 搜索效率:在平衡的二叉树中,如二叉搜索树或AVL树,搜索、插入和删除操作的时间复杂度接近O(log n),其中n是树中的节点数。
  3. 动态数据结构:二叉树可以方便地进行插入和删除操作,适合动态变化的数据集。
  4. 多种变体:二叉树有许多变种,如二叉搜索树、红黑树、AVL树等,它们针对不同的场景优化了性能。

1.3 缺点

  1. 不平衡问题:如果二叉树不是平衡的,比如退化成链式结构,那么搜索、插入和删除操作的效率会降低到O(n)。
  2. 空间开销:二叉树可能需要额外的空间来存储指针,尤其是当树的深度很大时。
  3. 维护成本:为了保持树的平衡状态,需要在插入和删除操作时进行额外的调整,这增加了实现的复杂性和成本。

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
#include

/**
 * 创建并返回一个二叉树的根节点。
 * 
 * 该函数通过递归方式构建二叉树。从输入读取每个节点的值,如果值为'#',则表示当前节点为空节点。
 * 否则,创建一个新节点,并将其左子树和右子树分别递归调用本函数来构建。
 * 
 * @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);
}

你可能感兴趣的:(嵌入式数据结构,数据结构,学习,笔记)