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. 递归销毁树时,需遵循"先子树后结点"的顺序,避免内存泄露。
相关推荐
AZ996ZA12 小时前
自学linux的二十天【DNS 服务从入门到实战】
linux·运维·服务器
wdfk_prog12 小时前
[Linux]学习笔记系列 -- [drivers][mmc]mmc_sd
linux·笔记·学习
qinyia12 小时前
**使用AI助手在智慧运维中快速定位并修复服务异常:以Nginx配置错误导致502错误为例**
linux·运维·服务器·数据库·mysql·nginx·自动化
❀͜͡傀儡师12 小时前
CentOS 7部署FTP服务
linux·运维·centos·ftp
我真会写代码12 小时前
WebSocket:告别轮询,实现Web实时通信 WebRTC:无需插件,实现浏览器端实时音视频通信
网络·websocket·网络协议·webrtc·实时音视频
闲人不梦卿12 小时前
数据结构之排序方法
数据结构·算法·排序算法
TracyCoder12312 小时前
LeetCode Hot100(24/100)——21. 合并两个有序链表
算法·leetcode·链表
数智工坊12 小时前
【数据结构-栈、队列、数组】3.4栈在表达式求值下-递归中的应用
数据结构
济61712 小时前
ARM Linux 驱动开发篇----字符设备驱动开发(2)--字符设备驱动开发步骤---- Ubuntu20.04
linux·运维·服务器
power 雀儿12 小时前
前馈网络+层归一化
人工智能·算法