4.树:一对多的层次数据结构

一、什么是树?

树(Tree)是一种一对多 的非线性数据结构,它通过节点组成层次关系,就像一棵倒过来的真实树木:根在最顶端,叶子在最末端,每个节点可以有多个子节点。

简单来说:

  • 数组、链表是一对一的线性结构;
  • 树是一对多的层次结构,每个节点可以有多个子节点。

树在实际开发中应用非常广泛:

  • 文件系统的目录结构;
  • DOM 文档对象模型;
  • 数据库的索引结构;
  • 路由表的前缀树;
  • 表达式的语法树等。

二、树的核心术语

1. 根节点(Root)

树的最顶层节点,没有父节点,一棵树只有一个根节点。

2. 叶子节点(Leaf)

没有子节点的节点,位于树的末端。

3. 父节点与子节点

  • 父节点:有边直接相连的上层节点;
  • 子节点:有边直接相连的下层节点;
  • 一个父节点可以有多个子节点,但一个子节点只能有一个父节点。

4. 兄弟节点(Sibling)

共享同一个父节点的节点,它们之间没有直接相连的边。

5. 子树(Subtree)

树中任意一个节点及其所有后代组成的树,子树本身也是一棵完整的树,体现了树的递归特性

6. 深度(Depth)

从根节点到该节点的路径长度,根节点的深度为 0。

7. 高度(Height)

从该节点到最远叶子节点的路径长度,叶子节点的高度为 0。

三、二叉树的节点定义

最常用的树是二叉树,每个节点最多有两个子节点:左子节点和右子节点。

对应的 C 语言代码定义如下:

复制代码
// 二叉树节点结构体
typedef struct TreeNode {
    int val;               // 数据域:存储节点数据
    struct TreeNode* left;  // 左子节点指针
    struct TreeNode* right; // 右子节点指针
} TreeNode;
  • 叶子节点的 leftright 都指向 NULL
  • 树就是由多个 TreeNode 通过指针串联起来的层次结构。

四、二叉树的基本操作

1. 创建节点

创建一个新的二叉树节点,初始化数据和子节点指针:

复制代码
// 创建新节点
TreeNode* createNode(int val) {
    TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
    node->val = val;
    node->left = NULL;
    node->right = NULL;
    return node;
}

2. 构建二叉树

通过手动指定节点的父子关系,构建一棵二叉树:

复制代码
// 构建示例二叉树
TreeNode* buildTree() {
    // 创建节点
    TreeNode* root = createNode(1);
    TreeNode* node2 = createNode(2);
    TreeNode* node3 = createNode(3);
    TreeNode* node4 = createNode(4);
    TreeNode* node5 = createNode(5);
    TreeNode* node6 = createNode(6);

    // 建立父子关系
    root->left = node2;
    root->right = node3;
    node2->left = node4;
    node2->right = node5;
    node3->right = node6;

    return root;
}

3. 前序遍历

前序遍历的顺序是:根节点 → 左子树 → 右子树,代码实现:

复制代码
// 前序遍历
void preorderTraversal(TreeNode* root) {
    if (root == NULL) return;
    printf("%d ", root->val);       // 先访问根节点
    preorderTraversal(root->left);  // 再遍历左子树
    preorderTraversal(root->right); // 最后遍历右子树
}

4. 中序遍历

中序遍历的顺序是:左子树 → 根节点 → 右子树,代码实现:

复制代码
// 中序遍历
void inorderTraversal(TreeNode* root) {
    if (root == NULL) return;
    inorderTraversal(root->left);   // 先遍历左子树
    printf("%d ", root->val);       // 再访问根节点
    inorderTraversal(root->right);  // 最后遍历右子树
}

5. 后序遍历

后序遍历的顺序是:左子树 → 右子树 → 根节点,代码实现:

复制代码
// 后序遍历
void postorderTraversal(TreeNode* root) {
    if (root == NULL) return;
    postorderTraversal(root->left);  // 先遍历左子树
    postorderTraversal(root->right); // 再遍历右子树
    printf("%d ", root->val);        // 最后访问根节点
}

6. 计算树的高度

树的高度是从根节点到最远叶子节点的路径长度,代码实现:

复制代码
// 计算树的高度
int getHeight(TreeNode* root) {
    if (root == NULL) return 0;
    int leftHeight = getHeight(root->left);
    int rightHeight = getHeight(root->right);
    return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
}

7. 计算树的节点总数

递归计算树的节点总数,代码实现:

复制代码
// 计算树的节点总数
int getNodeCount(TreeNode* root) {
    if (root == NULL) return 0;
    return getNodeCount(root->left) + getNodeCount(root->right) + 1;
}

8. 释放树的内存

递归释放树的所有节点内存,避免内存泄漏:

复制代码
// 释放树的内存
void freeTree(TreeNode* root) {
    if (root == NULL) return;
    freeTree(root->left);
    freeTree(root->right);
    free(root);
}

五、完整代码示例

复制代码
#include <stdio.h>
#include <stdlib.h>

// 二叉树节点结构体
typedef struct TreeNode {
    int val;
    struct TreeNode* left;
    struct TreeNode* right;
} TreeNode;

// 创建新节点
TreeNode* createNode(int val) {
    TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
    node->val = val;
    node->left = NULL;
    node->right = NULL;
    return node;
}

// 构建示例二叉树
TreeNode* buildTree() {
    TreeNode* root = createNode(1);
    TreeNode* node2 = createNode(2);
    TreeNode* node3 = createNode(3);
    TreeNode* node4 = createNode(4);
    TreeNode* node5 = createNode(5);
    TreeNode* node6 = createNode(6);

    root->left = node2;
    root->right = node3;
    node2->left = node4;
    node2->right = node5;
    node3->right = node6;

    return root;
}

// 前序遍历
void preorderTraversal(TreeNode* root) {
    if (root == NULL) return;
    printf("%d ", root->val);
    preorderTraversal(root->left);
    preorderTraversal(root->right);
}

// 中序遍历
void inorderTraversal(TreeNode* root) {
    if (root == NULL) return;
    inorderTraversal(root->left);
    printf("%d ", root->val);
    inorderTraversal(root->right);
}

// 后序遍历
void postorderTraversal(TreeNode* root) {
    if (root == NULL) return;
    postorderTraversal(root->left);
    postorderTraversal(root->right);
    printf("%d ", root->val);
}

// 计算树的高度
int getHeight(TreeNode* root) {
    if (root == NULL) return 0;
    int leftHeight = getHeight(root->left);
    int rightHeight = getHeight(root->right);
    return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
}

// 计算树的节点总数
int getNodeCount(TreeNode* root) {
    if (root == NULL) return 0;
    return getNodeCount(root->left) + getNodeCount(root->right) + 1;
}

// 释放树的内存
void freeTree(TreeNode* root) {
    if (root == NULL) return;
    freeTree(root->left);
    freeTree(root->right);
    free(root);
}

int main() {
    // 构建二叉树
    TreeNode* root = buildTree();

    // 测试前序遍历
    printf("前序遍历:");
    preorderTraversal(root); // 输出:1 2 4 5 3 6
    printf("\n");

    // 测试中序遍历
    printf("中序遍历:");
    inorderTraversal(root); // 输出:4 2 5 1 3 6
    printf("\n");

    // 测试后序遍历
    printf("后序遍历:");
    postorderTraversal(root); // 输出:4 5 2 6 3 1
    printf("\n");

    // 测试树的高度
    printf("树的高度:%d\n", getHeight(root)); // 输出:3

    // 测试节点总数
    printf("节点总数:%d\n", getNodeCount(root)); // 输出:6

    // 释放内存
    freeTree(root);
    printf("内存已释放\n");

    return 0;
}

六、树的实际应用场景

树在实际开发中应用非常广泛,常见场景包括:

  1. 文件系统:操作系统的目录结构就是一棵树,根目录是根节点,子目录和文件是子节点;
  2. DOM 树 :HTML 文档的结构就是一棵树,根节点是 <html>,子节点是各种标签;
  3. 数据库索引:MySQL 的 InnoDB 引擎使用 B+ 树作为索引结构,提高查询效率;
  4. 路由表:路由器的路由表使用前缀树(Trie)存储路由信息;
  5. 表达式求值:算术表达式可以表示为一棵二叉树,通过后序遍历计算结果;
  6. 哈夫曼树:用于数据压缩,根据字符出现频率构建最优编码。

七、总结

树是一种一对多的层次数据结构,它通过节点和边组成层次关系,体现了递归特性。

最常用的树是二叉树,每个节点最多有两个子节点,支持前序、中序、后序三种遍历方式,以及计算高度、节点总数等基本操作。

在实际开发中,树的应用非常广泛,是算法和开发中不可或缺的基础数据结构。

希望这篇文章能帮助你深入理解树的原理和实现!

相关推荐
luj_17682 小时前
桥牌思维启示:系统设计的模块化架构
c语言·开发语言·c++·经验分享·算法
天空'之城3 小时前
单片机基础核心知识点汇总(五)
单片机·嵌入式硬件
caimouse3 小时前
ReactOS 图形系统分析(16):字符串对象 — STROBJ(string.c)
c语言·开发语言
Aphelios3804 小时前
一次锁内网络IO引发的Tomcat线程池“饿死”事故
java·开发语言·spring boot·elasticsearch·tomcat·网络io阻塞·线程池耗尽
不可求~4 小时前
C++ std::string_view 不是字符串:从悬空引用到安全用法
java·开发语言·c++
en.en..5 小时前
数据结构考点解析:逻辑闭环与存储循环辨析
数据结构
90后小陈老师5 小时前
PHP 配置默认值失效排查实录:isset 兜底遇上表单空串回填,首页文案是怎么集体消失的
开发语言·php
JJJennie7776 小时前
Gemini 3.7 Flash 上线,Google 想让 AI 多干活
开发语言·javascript·ecmascript