【数据结构手册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的深度解析》

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

相关推荐
youngee111 小时前
hot100-39二叉树层序遍历
数据结构·算法
Evan芙1 小时前
Rocky Linux 9 网卡地址定制
linux·服务器·网络
Mr_WangAndy1 小时前
C++17 新特性_第一章 C++17 语言特性_if constexpr,类模板参数推导 (CTAD)
c++·c++40周年·if constexpr·类模板参数推导 ctad·c++17新特性
繁华似锦respect1 小时前
C++ 设计模式之单例模式详细介绍
服务器·开发语言·c++·windows·visualstudio·单例模式·设计模式
小年糕是糕手1 小时前
【C++】类和对象(三) -- 拷贝构造函数、赋值运算符重载
开发语言·c++·程序人生·考研·github·个人开发·改行学it
杰克尼1 小时前
蓝桥云课-小蓝做题
java·数据结构·算法
艾莉丝努力练剑1 小时前
【C++:C++11收尾】解构C++可调用对象:从入门到精通,掌握function包装器与bind适配器包装器详解
java·开发语言·c++·人工智能·c++11·右值引用
卿雪1 小时前
MySQL【索引】篇:索引的分类、B+树、创建索引的原则、索引失效的情况...
java·开发语言·数据结构·数据库·b树·mysql·golang
bing_feilong1 小时前
ubuntu20.04没有图形界面怎么办?
linux·网络