数据结构手册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;
}
};
总结与展望
树结构以其层次化的特性,在计算机科学中扮演着不可替代的角色:
- 二叉树的简洁性 - 最基础的树形结构,理解递归的绝佳范例
- BST的有序性 - 结合了快速查找和动态更新的优势
- 堆的高效性 - 优先级处理的理想选择
- 遍历的多样性 - 不同遍历方式解决不同问题
树结构的真正力量在于它的层次抽象能力,能够自然地建模现实世界中的许多关系。
在接下来的章节中,我们将探索:
- 更复杂的平衡树(AVL、红黑树)
- 多路搜索树(B树、B+树)
- 空间划分树(KD树、四叉树)
- 专门化树结构(Trie、后缀树)
下一章预告:《数据结构手册006:映射关系 - map与unordered_map的深度解析》
我们将深入探索键值对存储的两种经典实现:基于红黑树的有序映射和基于哈希表的无序映射,理解它们在不同场景下的性能特点和适用性。