【数据结构手册005】树结构入门 - 从二叉树到层次智慧

数据结构手册005:树结构入门 - 从二叉树到层次智慧

树:自然界与计算机科学的完美相遇

当我们观察自然界时,树木的结构总是令人惊叹:主干分出枝干,枝干再分出更小的枝条,最终形成复杂的层次系统。计算机科学中的树结构正是受此启发,成为组织层次化数据的理想选择。

cpp 复制代码
// 现实世界中的树结构类比
// 文件系统:根目录 → 子目录 → 文件
// 组织架构:CEO → 部门经理 → 员工  
// 家谱:祖先 → 父母 → 子女
// 决策过程:问题 → 选择 → 结果

树的基本概念:建立术语体系

树的定义与特性

树是由n(n≥0)个节点组成的有限集合:

  • 当n=0时,称为空树
  • 当n>0时,有一个特定的称为根的节点
  • 其余节点可分为m(m≥0)个互不相交的有限集合,每个集合本身又是一棵树
cpp 复制代码
// 树的节点基本结构
template<typename T>
struct TreeNode {
    T data;                    // 节点数据
    std::vector<TreeNode*> children;  // 子节点指针集合
    
    TreeNode(const T& val) : data(val) {}
};

核心术语解析

cpp 复制代码
class TreeTerminology {
    /*
          A        ← 根节点 (Root)
         /|\
        B C D      ← A的子节点 (Children), 兄弟节点 (Siblings)
       / \   \
      E   F   G    ← 叶节点 (Leaves)
      
    层级: 
    第1层: A (深度0)
    第2层: B,C,D (深度1)  
    第3层: E,F,G (深度2)
    
    路径: A→B→E (长度为2)
    高度: 2 (从根到最远叶节点的路径长度)
    */
};

二叉树:最简单的树形结构

二叉树的定义与特性

二叉树是每个节点最多有两个子节点的树结构,分别称为左子节点和右子节点。

cpp 复制代码
template<typename T>
struct BinaryTreeNode {
    T data;
    BinaryTreeNode* left;   // 左子节点
    BinaryTreeNode* right;  // 右子节点
    
    BinaryTreeNode(const T& val) : data(val), left(nullptr), right(nullptr) {}
};

特殊类型的二叉树

cpp 复制代码
void binaryTreeTypes() {
    // 1. 满二叉树 (Full Binary Tree)
    //    每个节点都有0个或2个子节点
    /*
            A
           / \
          B   C
         / \ 
        D   E
    */
    
    // 2. 完全二叉树 (Complete Binary Tree)
    //    除最后一层外,所有层都是满的,且最后一层节点靠左排列
    /*
            A
           / \
          B   C
         / \  /
        D   E F
    */
    
    // 3. 完美二叉树 (Perfect Binary Tree)
    //    所有叶节点都在同一层,且所有非叶节点都有两个子节点
    /*
            A
           / \
          B   C
         / \ / \
        D  E F  G
    */
    
    // 4. 平衡二叉树 (Balanced Binary Tree)
    //    任意节点的左右子树高度差不超过1
}

二叉树的遍历:四种经典方式

遍历是二叉树最重要的操作之一,体现了递归思想的精髓。

1. 前序遍历:根→左→右

cpp 复制代码
void preorderTraversal(BinaryTreeNode<int>* root) {
    if (root == nullptr) return;
    
    std::cout << root->data << " ";  // 先访问根节点
    preorderTraversal(root->left);   // 再遍历左子树
    preorderTraversal(root->right);  // 最后遍历右子树
}

// 应用场景:复制树结构、前缀表达式

2. 中序遍历:左→根→右

cpp 复制代码
void inorderTraversal(BinaryTreeNode<int>* root) {
    if (root == nullptr) return;
    
    inorderTraversal(root->left);    // 先遍历左子树
    std::cout << root->data << " ";  // 再访问根节点
    inorderTraversal(root->right);   // 最后遍历右子树
}

// 应用场景:二叉搜索树的有序输出

3. 后序遍历:左→右→根

cpp 复制代码
void postorderTraversal(BinaryTreeNode<int>* root) {
    if (root == nullptr) return;
    
    postorderTraversal(root->left);   // 先遍历左子树
    postorderTraversal(root->right);  // 再遍历右子树
    std::cout << root->data << " ";   // 最后访问根节点
}

// 应用场景:删除树、计算目录大小

4. 层序遍历:按层次遍历

cpp 复制代码
#include <queue>

void levelOrderTraversal(BinaryTreeNode<int>* root) {
    if (root == nullptr) return;
    
    std::queue<BinaryTreeNode<int>*> q;
    q.push(root);
    
    while (!q.empty()) {
        BinaryTreeNode<int>* current = q.front();
        q.pop();
        
        std::cout << current->data << " ";
        
        if (current->left) q.push(current->left);
        if (current->right) q.push(current->right);
    }
}

// 应用场景:查找最短路径、按层次处理数据

遍历实战:表达式树求值

cpp 复制代码
class ExpressionTree {
public:
    double evaluate(BinaryTreeNode<std::string>* root) {
        if (root == nullptr) return 0;
        
        // 叶节点是操作数
        if (!root->left && !root->right) {
            return std::stod(root->data);
        }
        
        // 递归计算左右子树
        double leftVal = evaluate(root->left);
        double rightVal = evaluate(root->right);
        
        // 根据运算符计算结果
        if (root->data == "+") return leftVal + rightVal;
        if (root->data == "-") return leftVal - rightVal;
        if (root->data == "*") return leftVal * rightVal;
        if (root->data == "/") return leftVal / rightVal;
        
        throw std::invalid_argument("未知运算符");
    }
};

void expressionTreeDemo() {
    // 表达式: (2 * (3 + 4)) / 5
    /*
            [/]
           /   \
         [*]   [5]
        /   \
      [2]   [+]
           /   \
         [3]   [4]
    */
    
    auto root = new BinaryTreeNode<std::string>("/");
    root->left = new BinaryTreeNode<std::string>("*");
    root->right = new BinaryTreeNode<std::string>("5");
    root->left->left = new BinaryTreeNode<std::string>("2");
    root->left->right = new BinaryTreeNode<std::string>("+");
    root->left->right->left = new BinaryTreeNode<std::string>("3");
    root->left->right->right = new BinaryTreeNode<std::string>("4");
    
    ExpressionTree et;
    double result = et.evaluate(root);
    std::cout << "表达式结果: " << result << std::endl;  // (2*(3+4))/5 = 2.8
}

二叉搜索树:有序的二叉树

BST的定义与特性

二叉搜索树是一种特殊的二叉树,满足:

  • 左子树所有节点的值 < 根节点的值
  • 右子树所有节点的值 > 根节点的值
  • 左右子树也都是二叉搜索树
cpp 复制代码
template<typename T>
class BinarySearchTree {
private:
    struct Node {
        T data;
        Node* left;
        Node* right;
        Node(const T& val) : data(val), left(nullptr), right(nullptr) {}
    };
    
    Node* root;
    
public:
    BinarySearchTree() : root(nullptr) {}
    
    // 插入操作
    void insert(const T& value) {
        root = insertRecursive(root, value);
    }
    
private:
    Node* insertRecursive(Node* node, const T& value) {
        if (node == nullptr) {
            return new Node(value);
        }
        
        if (value < node->data) {
            node->left = insertRecursive(node->left, value);
        } else if (value > node->data) {
            node->right = insertRecursive(node->right, value);
        }
        // 如果值相等,根据需求决定(这里不插入重复值)
        
        return node;
    }
};

BST的查找操作

cpp 复制代码
class BinarySearchTree {
public:
    bool search(const T& value) const {
        return searchRecursive(root, value);
    }
    
    // 查找最小值
    T findMin() const {
        if (root == nullptr) throw std::runtime_error("树为空");
        return findMinRecursive(root)->data;
    }
    
    // 查找最大值  
    T findMax() const {
        if (root == nullptr) throw std::runtime_error("树为空");
        return findMaxRecursive(root)->data;
    }
    
private:
    bool searchRecursive(Node* node, const T& value) const {
        if (node == nullptr) return false;
        
        if (value == node->data) return true;
        else if (value < node->data) return searchRecursive(node->left, value);
        else return searchRecursive(node->right, value);
    }
    
    Node* findMinRecursive(Node* node) const {
        while (node->left != nullptr) {
            node = node->left;
        }
        return node;
    }
    
    Node* findMaxRecursive(Node* node) const {
        while (node->right != nullptr) {
            node = node->right;
        }
        return node;
    }
};

BST的删除操作

cpp 复制代码
class BinarySearchTree {
public:
    void remove(const T& value) {
        root = removeRecursive(root, value);
    }
    
private:
    Node* removeRecursive(Node* node, const T& value) {
        if (node == nullptr) return nullptr;
        
        if (value < node->data) {
            node->left = removeRecursive(node->left, value);
        } else if (value > node->data) {
            node->right = removeRecursive(node->right, value);
        } else {
            // 找到要删除的节点
            if (node->left == nullptr) {
                //  case 1: 没有左子节点
                Node* rightChild = node->right;
                delete node;
                return rightChild;
            } else if (node->right == nullptr) {
                // case 2: 没有右子节点
                Node* leftChild = node->left;
                delete node;
                return leftChild;
            } else {
                // case 3: 有两个子节点
                // 找到右子树的最小值节点
                Node* minNode = findMinRecursive(node->right);
                // 用最小值替换当前节点
                node->data = minNode->data;
                // 删除右子树中的最小值节点
                node->right = removeRecursive(node->right, minNode->data);
            }
        }
        return node;
    }
};

堆:完全二叉树的应用

堆的定义与特性

堆是一种特殊的完全二叉树,满足堆属性:

  • 最大堆:父节点的值 ≥ 子节点的值
  • 最小堆:父节点的值 ≤ 子节点的值
cpp 复制代码
#include <vector>
#include <algorithm>

template<typename T>
class MaxHeap {
private:
    std::vector<T> data;
    
    // 上浮操作
    void siftUp(int index) {
        while (index > 0) {
            int parent = (index - 1) / 2;
            if (data[index] <= data[parent]) break;
            std::swap(data[index], data[parent]);
            index = parent;
        }
    }
    
    // 下沉操作
    void siftDown(int index) {
        int size = data.size();
        while (index * 2 + 1 < size) {
            int leftChild = index * 2 + 1;
            int rightChild = index * 2 + 2;
            int largerChild = leftChild;
            
            if (rightChild < size && data[rightChild] > data[leftChild]) {
                largerChild = rightChild;
            }
            
            if (data[index] >= data[largerChild]) break;
            
            std::swap(data[index], data[largerChild]);
            index = largerChild;
        }
    }
    
public:
    void insert(const T& value) {
        data.push_back(value);
        siftUp(data.size() - 1);
    }
    
    T extractMax() {
        if (data.empty()) throw std::runtime_error("堆为空");
        
        T maxValue = data[0];
        data[0] = data.back();
        data.pop_back();
        
        if (!data.empty()) {
            siftDown(0);
        }
        
        return maxValue;
    }
    
    const T& peek() const {
        if (data.empty()) throw std::runtime_error("堆为空");
        return data[0];
    }
    
    bool empty() const { return data.empty(); }
    size_t size() const { return data.size(); }
};

堆排序算法

cpp 复制代码
class HeapSort {
public:
    template<typename T>
    static void sort(std::vector<T>& arr) {
        // 构建最大堆
        int n = arr.size();
        for (int i = n / 2 - 1; i >= 0; --i) {
            heapify(arr, n, i);
        }
        
        // 逐个提取元素
        for (int i = n - 1; i > 0; --i) {
            std::swap(arr[0], arr[i]);  // 将当前最大值移到末尾
            heapify(arr, i, 0);         // 对剩余元素重新堆化
        }
    }
    
private:
    template<typename T>
    static void heapify(std::vector<T>& arr, int n, int i) {
        int largest = i;        // 初始化最大值为根
        int left = 2 * i + 1;   // 左子节点
        int right = 2 * i + 2;  // 右子节点
        
        // 如果左子节点大于根
        if (left < n && arr[left] > arr[largest]) {
            largest = left;
        }
        
        // 如果右子节点大于当前最大值
        if (right < n && arr[right] > arr[largest]) {
            largest = right;
        }
        
        // 如果最大值不是根
        if (largest != i) {
            std::swap(arr[i], arr[largest]);
            heapify(arr, n, largest);  // 递归地堆化受影响的子树
        }
    }
};

void heapSortDemo() {
    std::vector<int> arr = {12, 11, 13, 5, 6, 7};
    std::cout << "排序前: ";
    for (int val : arr) std::cout << val << " ";
    
    HeapSort::sort(arr);
    
    std::cout << "\n排序后: ";
    for (int val : arr) std::cout << val << " ";
    std::cout << std::endl;
}

实战应用:树结构的经典场景

场景1:文件系统模拟

cpp 复制代码
class FileSystem {
private:
    struct FileNode {
        std::string name;
        bool isDirectory;
        std::vector<FileNode*> children;
        std::string content;  // 文件内容
        
        FileNode(const std::string& name, bool isDir) 
            : name(name), isDirectory(isDir) {}
    };
    
    FileNode* root;
    
public:
    FileSystem() {
        root = new FileNode("root", true);
    }
    
    void createDirectory(const std::string& path) {
        // 实现创建目录逻辑
    }
    
    void createFile(const std::string& path, const std::string& content) {
        // 实现创建文件逻辑
    }
    
    void listDirectory(const std::string& path) {
        // 实现列出目录内容
    }
    
    // 前序遍历显示文件系统结构
    void displayStructure() {
        displayRecursive(root, 0);
    }
    
private:
    void displayRecursive(FileNode* node, int depth) {
        std::string indent(depth * 2, ' ');
        std::cout << indent << (node->isDirectory ? "[D] " : "[F] ") 
                  << node->name << std::endl;
        
        for (FileNode* child : node->children) {
            displayRecursive(child, depth + 1);
        }
    }
};

场景2:决策树分类器

cpp 复制代码
class DecisionTree {
private:
    struct DecisionNode {
        std::string question;      // 问题或特征
        DecisionNode* trueBranch;  // 条件为真时的分支
        DecisionNode* falseBranch; // 条件为假时的分支
        std::string result;        // 叶节点的分类结果
        
        DecisionNode(const std::string& q) : question(q), trueBranch(nullptr), 
                                            falseBranch(nullptr) {}
        DecisionNode(const std::string& res) : question(""), trueBranch(nullptr),
                                              falseBranch(nullptr), result(res) {}
        
        bool isLeaf() const { return result != ""; }
    };
    
    DecisionNode* root;
    
public:
    DecisionTree() : root(nullptr) {}
    
    std::string classify(const std::map<std::string, bool>& features) {
        return classifyRecursive(root, features);
    }
    
    void displayTree() {
        displayRecursive(root, 0);
    }
    
private:
    std::string classifyRecursive(DecisionNode* node, 
                                 const std::map<std::string, bool>& features) {
        if (node->isLeaf()) {
            return node->result;
        }
        
        auto it = features.find(node->question);
        if (it != features.end() && it->second) {
            return classifyRecursive(node->trueBranch, features);
        } else {
            return classifyRecursive(node->falseBranch, features);
        }
    }
    
    void displayRecursive(DecisionNode* node, int depth) {
        std::string indent(depth * 2, ' ');
        
        if (node->isLeaf()) {
            std::cout << indent << "结果: " << node->result << std::endl;
        } else {
            std::cout << indent << "问题: " << node->question << std::endl;
            std::cout << indent << "是 →" << std::endl;
            displayRecursive(node->trueBranch, depth + 1);
            std::cout << indent << "否 →" << std::endl;
            displayRecursive(node->falseBranch, depth + 1);
        }
    }
};

void decisionTreeDemo() {
    DecisionTree tree;
    // 构建一个简单的动物分类决策树
    /*
            [有毛发?]
             是 → [喵喵叫?] → 是 → 猫
                    否 → [有蹄子?] → 是 → 马
             否 → [有羽毛?] → 是 → 鸟
    */
    
    std::map<std::string, bool> animal1 = {{"有毛发", true}, {"喵喵叫", true}};
    std::cout << "分类结果: " << tree.classify(animal1) << std::endl;  // 猫
}

树的存储与序列化

二叉树的序列化与反序列化

cpp 复制代码
class BinaryTreeCodec {
public:
    // 序列化:将二叉树转换为字符串
    std::string serialize(BinaryTreeNode<int>* root) {
        if (root == nullptr) return "null,";
        
        std::string result = std::to_string(root->data) + ",";
        result += serialize(root->left);
        result += serialize(root->right);
        return result;
    }
    
    // 反序列化:从字符串重建二叉树
    BinaryTreeNode<int>* deserialize(const std::string& data) {
        std::queue<std::string> nodes;
        std::stringstream ss(data);
        std::string item;
        
        while (std::getline(ss, item, ',')) {
            if (!item.empty()) {
                nodes.push(item);
            }
        }
        
        return deserializeHelper(nodes);
    }
    
private:
    BinaryTreeNode<int>* deserializeHelper(std::queue<std::string>& nodes) {
        if (nodes.empty()) return nullptr;
        
        std::string val = nodes.front();
        nodes.pop();
        
        if (val == "null") return nullptr;
        
        BinaryTreeNode<int>* root = new BinaryTreeNode<int>(std::stoi(val));
        root->left = deserializeHelper(nodes);
        root->right = deserializeHelper(nodes);
        
        return root;
    }
};

性能分析与优化

树操作的复杂度分析

操作 普通二叉树 平衡BST 堆
查找 O(n) O(log n) O(1) peek
插入 O(n) O(log n) O(log n)
删除 O(n) O(log n) O(log n)
遍历 O(n) O(n) O(n)

平衡二叉树的重要性

cpp 复制代码
void balanceImportance() {
    // 不平衡的BST会退化为链表
    /*
        1
         \
          2
           \
            3
             \
              4    // 查找时间复杂度退化为O(n)
    */
    
    // 平衡的BST保持高效
    /*
          2
         / \
        1   3
             \
              4    // 查找时间复杂度保持O(log n)
    */
}

现代C++中的树结构

使用智能指针管理内存

cpp 复制代码
template<typename T>
class ModernBinaryTree {
private:
    struct Node {
        T data;
        std::unique_ptr<Node> left;
        std::unique_ptr<Node> right;
        
        Node(const T& val) : data(val) {}
    };
    
    std::unique_ptr<Node> root;
    
public:
    void insert(const T& value) {
        root = insertRecursive(std::move(root), value);
    }
    
private:
    std::unique_ptr<Node> insertRecursive(std::unique_ptr<Node> node, const T& value) {
        if (!node) {
            return std::make_unique<Node>(value);
        }
        
        if (value < node->data) {
            node->left = insertRecursive(std::move(node->left), value);
        } else if (value > node->data) {
            node->right = insertRecursive(std::move(node->right), value);
        }
        
        return node;
    }
};

总结与展望

树结构以其层次化的特性,在计算机科学中扮演着不可替代的角色:

  1. 二叉树的简洁性 - 最基础的树形结构,理解递归的绝佳范例
  2. BST的有序性 - 结合了快速查找和动态更新的优势
  3. 堆的高效性 - 优先级处理的理想选择
  4. 遍历的多样性 - 不同遍历方式解决不同问题

树结构的真正力量在于它的层次抽象能力,能够自然地建模现实世界中的许多关系。

在接下来的章节中,我们将探索:

  • 更复杂的平衡树(AVL、红黑树)
  • 多路搜索树(B树、B+树)
  • 空间划分树(KD树、四叉树)
  • 专门化树结构(Trie、后缀树)

下一章预告:《数据结构手册006:映射关系 - map与unordered_map的深度解析》

我们将深入探索键值对存储的两种经典实现:基于红黑树的有序映射和基于哈希表的无序映射,理解它们在不同场景下的性能特点和适用性。

相关推荐
汉克老师11 小时前
GESP2026年9月认证C++七级( 第三部分编程题(2、括号序列))精讲
c++·gesp·小学生·学c++编程
w_zero_one11 小时前
链表(4)
java·数据结构·算法
2603_9695792812 小时前
内网数据自动备份工具:隔离网络下的数据保护方案
网络
逗脑IDE12 小时前
零基础学ESP32:RFID无线射频卡——让ESP32拥有“刷卡“能力!
开发语言·网络·人工智能·python·esp32·硬件开发
cws20040112 小时前
监控摄像头选型指南
运维·网络·监控
和裕13 小时前
定制纸箱刀模费全解析:费用定义与可减免合作场景
大数据·运维·网络·人工智能·算法
白山编程大哥13 小时前
C语言篇:语法进阶
c语言·数据结构·算法
troy12813 小时前
智能体沙箱:如何为 Agent 模型搭建安全试验场,降低幻觉带来的损失
网络·安全
jimy113 小时前
虚函数vtable,链接时看到构造函数,linker把符号地址写入vtable(三)
开发语言·c++
LHR_friendship13 小时前
HCIP课程全部内容
网络·笔记·学习·hcip·华为ensp