25、数据结构:树与二叉树的概念、特性及递归实现

数据结构:树与二叉树的概念、特性及递归实现

一、树的基础概念

树是由 n(n≥0)n(n\geq0)n(n≥0) 个结点组成的有限集合,核心定义与术语如下:

  • 空树 :n=0n=0n=0 时的特殊树;
  • 非空树 :有且仅有一个根结点 ,其余结点可分为 mmm 个互不相交的子树(子树本身也是树);
  • 结点的度:结点拥有的子树数量;
  • 叶结点/分支结点:度为0的结点称为叶结点(终端结点),度不为0的称为分支结点(非终端结点);
  • 树的度数:树中所有结点的最大度数;
  • 树的深度/高度:从根开始计数,根为第1层,其孩子为第2层,以此类推,最深结点的层数即为树的深度。

树的存储方式分为两种:

  1. 顺序结构:用数组存储(适合完全二叉树);
  2. 链式结构:用指针存储结点间的父子关系(通用方式)。

二、二叉树:特殊的树结构

二叉树是树的特殊形式,定义为:由 nnn 个结点组成的有限集合,要么是空树,要么由一个根结点和两棵互不相交的左子树、右子树组成(左、右子树也为二叉树)。

2.1 二叉树的核心特点

  1. 每个结点最多有2棵子树(左、右子树);
  2. 左、右子树是有顺序的,次序不能颠倒(即使只有一棵子树,也要区分左/右)。

2.2 特殊类型的二叉树

  • 斜树:所有结点只有左子树(左斜树)或只有右子树(右斜树),本质是退化的线性结构;
  • 满二叉树 :所有分支结点都有左、右子树,且所有叶结点在同一层(深度为 kkk 的满二叉树有 2k−12^k-12k−1 个结点);
  • 完全二叉树:按层序编号后,每个结点的编号与同深度的满二叉树对应位置编号一致(叶结点只出现在最后两层,且最后一层的结点靠左排列)。

2.3 二叉树的核心特性

  1. 第 iii 层最多有 2i−12^{i-1}2i−1 个结点(i≥1i\geq1i≥1);
  2. 深度为 kkk 的二叉树,最多有 2k−12^k-12k−1 个结点(k≥1k\geq1k≥1);
  3. 任意二叉树中,叶结点数 n0n_0n0 与度为2的结点数 n2n_2n2 满足:n0=n2+1n_0 = n_2 + 1n0=n2+1;
  4. 有 nnn 个结点的完全二叉树,深度为 ⌊log⁡2n⌋+1\lfloor \log_2 n \rfloor + 1⌊log2n⌋+1。

三、二叉树的递归实现:从创建到遍历

以下代码基于链式结构 实现二叉树,通过前序字符串递归创建树,并实现前序、中序、后序遍历,最后递归销毁树避免内存泄露。

3.1 代码结构说明

二叉树结点结构体
c 复制代码
typedef char DATATYPE;

// 二叉树结点:存储数据+左/右子树指针
typedef struct _treenode_
{
    DATATYPE data;
    struct _treenode_ *left;  // 左子树指针
    struct _treenode_ *right; // 右子树指针
} TREENODE;

3.2 递归创建二叉树(前序字符串法)

通过前序遍历序列 (用 # 表示空结点)递归构建二叉树,示例序列:"abd##e##c#fh###"(对应树结构见下文)。

c 复制代码
char data[] = "abd##e##c#fh###"; // 前序序列(#表示空结点)
int inx = 0; // 全局变量:记录当前处理到序列的第几个字符

// 递归创建二叉树(传入根结点的指针的指针)
void CreateRoot(TREENODE **root)
{
    char c = data[inx++]; // 取当前字符并移动下标
    if ('#' == c)
    {
        *root = NULL; // #表示空结点
        return;
    }
    // 分配结点内存并初始化
    *root = (TREENODE *)malloc(sizeof(TREENODE));
    if (NULL == *root)
    {
        printf("CreateRoot malloc failed\n");
        return;
    }
    (*root)->data = c;
    CreateRoot(&(*root)->left);  // 递归创建左子树
    CreateRoot(&(*root)->right); // 递归创建右子树
}

序列对应树结构

根结点为 a,左子树是 bb 的左是 d、右是 e),右子树是 cc 的右是 ff 的左是 h),空结点用 # 填充。

3.3 深度优先遍历(前序、中序、后序)

深度优先遍历通过递归实现,核心是根结点的访问时机

  • 前序遍历:根 → 左 → 右
  • 中序遍历:左 → 根 → 右
  • 后序遍历:左 → 右 → 根
前序遍历
c 复制代码
void PreOrderTraverse(TREENODE *T)
{
    if (NULL == T) return;
    printf("%c", T->data); // 先访问根
    PreOrderTraverse(T->left);  // 遍历左子树
    PreOrderTraverse(T->right); // 遍历右子树
}
中序遍历
c 复制代码
void InOrderTraverse(TREENODE *T)
{
    if (NULL == T) return;
    InOrderTraverse(T->left);  // 先遍历左子树
    printf("%c", T->data);     // 再访问根
    InOrderTraverse(T->right); // 最后遍历右子树
}
后序遍历
c 复制代码
void PostOrderTraverse(TREENODE *T)
{
    if (NULL == T) return;
    PostOrderTraverse(T->left);  // 先遍历左子树
    PostOrderTraverse(T->right); // 再遍历右子树
    printf("%c", T->data);       // 最后访问根
}

3.4 二叉树的销毁(递归释放内存)

递归遍历所有结点,先释放左、右子树,再释放当前结点(避免野指针与内存泄露):

c 复制代码
void DestroyTree(TREENODE *root)
{
    if (NULL == root) return;
    DestroyTree(root->left);  // 销毁左子树
    DestroyTree(root->right); // 销毁右子树
    free(root);               // 释放当前结点
}

3.5 主函数与运行结果

c 复制代码
int main(int argc, char **argv)
{
    TREENODE *root = NULL;
    CreateRoot(&root); // 创建二叉树

    printf("前序遍历:");
    PreOrderTraverse(root);
    printf("\n中序遍历:");
    InOrderTraverse(root);
    printf("\n后序遍历:");
    PostOrderTraverse(root);
    printf("\n");

    DestroyTree(root); // 销毁树
    return 0;
}
运行输出
复制代码
前序遍历:abdecfh
中序遍历:dbeahfc
后序遍历:debhfca

四、总结

  1. 二叉树的核心是左/右子树的有序性,基于递归的创建与遍历是最直观的实现方式;
  2. 前序字符串法创建二叉树时,# 是关键的空结点标识,确保递归能正确终止;
  3. 遍历的本质是根结点的访问时机,前/中/后序的区别仅在于根结点的打印顺序;
  4. 递归销毁树时,需遵循"先子树后结点"的顺序,避免内存泄露。
相关推荐
莫物1 小时前
Java后端请求不同环境下的同一接口,有的环境会出现乱码问题
java·开发语言
MM_MS1 小时前
SQL Server数据库和Visual Studio (C#)联合编程
开发语言·数据库·sqlserver·c#·visual studio
Han.miracle1 小时前
数据结构--初始数据结构
算法·集合·大o表示法
List<String> error_P1 小时前
C语言联合体:内存共享的妙用
算法·联合体
runfarther1 小时前
mysql_mcp_server部署及应用案例
linux·mysql·centos·mcp
little~钰1 小时前
可持久化线段树和标记永久化
算法
爱吃番茄鼠骗1 小时前
Linux操作系统———信号量
linux
惺忪97981 小时前
Qt C++11/14/17 新特性大全详解
开发语言·c++
saber_andlibert1 小时前
【docker】网络基础和容器编排
网络·docker·php