二叉树定义
二叉树(Binary Tree)是一种树形结构,每个节点最多有两个子节点。通常,二叉树的节点包括以下几个部分:
- 值(Value):节点的内容。
- 左子树(Left Child):指向左子节点的指针。
- 右子树(Right Child):指向右子节点的指针。
二叉树的基本结构在C++中通常通过结构体来表示:
struct TreeNode {
int val; // 节点值
TreeNode* left; // 左子节点
TreeNode* right; // 右子节点
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} // 构造函数
};
二叉树的类型
满二叉树(Full Binary Tree)
- 每个节点要么没有子节点,要么恰好有两个子节点。
- 也就是说,除了叶子节点,其他节点的左右子树都有节点。
完全二叉树(Complete Binary Tree)
- 除了最底层外,其他层的节点数都达到最大,并且最底层的节点都集中在最左边。
- 完全二叉树的特点是:第
i
层最多有2^(i-1)
个节点,第n
层最多有2^(n-1)
个节点。
二叉搜索树(Binary Search Tree, BST)
- 二叉搜索树是一种特殊的二叉树,满足:对于树中的每个节点,左子树的节点值小于该节点,右子树的节点值大于该节点。
平衡二叉树(Balanced Binary Tree)
- 平衡二叉树的定义是,任意节点的左右子树的高度差不超过1。AVL树是平衡二叉树的一种。
满二叉树与完全二叉树的区别
- 满二叉树要求每个节点都有0或2个子节点,而完全二叉树只要求除了最后一层,其他层都满。
- 完全二叉树的叶子节点必须从左到右排列。
二叉树的操作
前序遍历(Preorder Traversal)
遍历顺序:根 -> 左 -> 右
- 递归实现
void preorder(TreeNode* root) {
if (!root) return;
cout << root->val << " ";
preorder(root->left);
preorder(root->right);
}
- 非递归实现
void preorderNonRecursive(TreeNode* root) {
if (root == NULL) return;
stack<TreeNode*> s;
s.push(root);
while (!s.empty()) {
TreeNode* node = s.top();
s.pop();
cout << node->val << " "; // 访问节点
if (node->right) s.push(node->right); // 右子树先入栈
if (node->left) s.push(node->left); // 左子树后入栈
}
}
中序遍历(Inorder Traversal)
遍历顺序:左 -> 根 -> 右
- 递归实现
void inorder(TreeNode* root) {
if (!root) return;
inorder(root->left);
cout << root->val << " ";
inorder(root->right);
}
- 非递归实现
void inorderNonRecursive(TreeNode* root) {
stack<TreeNode*> s;
TreeNode* node = root;
while (node != NULL || !s.empty()) {
while (node != NULL) {
s.push(node);
node = node->left;
}
node = s.top();
s.pop();
cout << node->val << " "; // 访问节点
node = node->right;
}
}
后序遍历(Postorder Traversal)
左 -> 右 -> 根。
- 递归实现
void postorder(TreeNode* root) {
if (!root) return;
postorder(root->left);
postorder(root->right);
cout << root->val << " ";
}
- 非递归实现
void postorderNonRecursive(TreeNode* root) {
if (root == NULL) return;
stack<TreeNode*> s1, s2;
s1.push(root);
while (!s1.empty()) {
TreeNode* node = s1.top();
s1.pop();
s2.push(node);
if (node->left) s1.push(node->left);
if (node->right) s1.push(node->right);
}
while (!s2.empty()) {
cout << s2.top()->val << " "; // 访问节点
s2.pop();
}
}
层序遍历(Level-order Traversal)
层序遍历是广度优先遍历,即按层次访问二叉树的节点。
void levelOrder(TreeNode* root) {
if (!root) return;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
TreeNode* node = q.front();
q.pop();
cout << node->val << " ";
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
}
二叉搜索树(BST)的插入操作
二叉搜索树(BST)具有以下特性:左子树节点值小于根节点值,右子树节点值大于根节点值。
TreeNode* insert(TreeNode* root, int val) {
if (!root) return new TreeNode(val);
if (val < root->val) {
root->left = insert(root->left, val);
} else {
root->right = insert(root->right, val);
}
return root;
}
判断二叉树是否是平衡二叉树
bool isBalanced(TreeNode* root) {
return height(root) != -1;
}
int height(TreeNode* root) {
if (root == NULL) return 0;
int leftHeight = height(root->left);
if (leftHeight == -1) return -1; // 如果左子树不平衡
int rightHeight = height(root->right);
if (rightHeight == -1) return -1; // 如果右子树不平衡
if (abs(leftHeight - rightHeight) > 1) return -1; // 如果左右子树高度差大于1,不平衡
return max(leftHeight, rightHeight) + 1;
}
查找节点
TreeNode* search(TreeNode* root, int val) {
if (!root || root->val == val) return root;
if (val < root->val) return search(root->left, val);
return search(root->right, val);
}
二叉树删除
删除节点时,分为三种情况:
- 要删除的节点是叶子节点(没有子节点)。
- 要删除的节点只有一个子节点。
- 要删除的节点有两个子节点。
TreeNode* deleteNode(TreeNode* root, int val) {
if (root == NULL) return root;
// 找到要删除的节点
if (val < root->val) {
root->left = deleteNode(root->left, val);
} else if (val > root->val) {
root->right = deleteNode(root->right, val);
} else {
// 当前节点就是要删除的节点
if (root->left == NULL) {
TreeNode* temp = root->right;
delete root;
return temp;
} else if (root->right == NULL) {
TreeNode* temp = root->left;
delete root;
return temp;
}
// 如果当前节点有两个子节点
TreeNode* temp = findMin(root->right); // 找到右子树中的最小节点
root->val = temp->val; // 替换当前节点的值
root->right = deleteNode(root->right, temp->val); // 删除右子树中的最小节点
}
return root;
}
// 找到最小节点(左子树最深的节点)
TreeNode* findMin(TreeNode* root) {
while (root && root->left) {
root = root->left;
}
return root;
}
二叉树的性质
二叉树的高度(深度)
- 树的高度是从根节点到最远叶子节点的最长路径的长度。
- 叶子节点的高度为0,根节点的高度是树的高度。
int maxDepth(TreeNode* root) {
if (!root) return 0;
return 1 + max(maxDepth(root->left), maxDepth(root->right));
}
二叉树的节点数
- 如果二叉树的高度为
h
,则该树的最大节点数为2^h - 1
。 - 二叉树的节点数等于树的左子树和右子树节点数的和加1。
完全二叉树的节点数
- 完全二叉树的节点数可以通过层次遍历计算。
二叉树应用
二叉搜索树(Binary Search Tree, BST)
二叉搜索树是一种特殊的二叉树,满足以下性质:
- 对于树中的每个节点,左子树的节点值小于该节点,右子树的节点值大于该节点。
- 二叉搜索树的操作如查找、插入和删除可以在平均O(logN)时间内完成。
#include <iostream>
using namespace std;
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
// 插入节点
TreeNode* insertBST(TreeNode* root, int val) {
if (!root) return new TreeNode(val);
if (val < root->val) {
root->left = insertBST(root->left, val);
} else {
root->right = insertBST(root->right, val);
}
return root;
}
// 查找节点
TreeNode* searchBST(TreeNode* root, int val) {
if (!root || root->val == val) return root;
if (val < root->val) return searchBST(root->left, val);
return searchBST(root->right, val);
}
// 中序遍历
void inorder(TreeNode* root) {
if (!root) return;
inorder(root->left);
cout << root->val << " ";
inorder(root->right);
}
int main() {
TreeNode* root = nullptr;
root = insertBST(root, 50);
insertBST(root, 30);
insertBST(root, 70);
insertBST(root, 20);
insertBST(root, 40);
insertBST(root, 60);
insertBST(root, 80);
cout << "Inorder Traversal: ";
inorder(root); // 输出:20 30 40 50 60 70 80
int val = 40;
TreeNode* node = searchBST(root, val);
if (node) {
cout << "\nNode with value " << val << " found." << endl;
} else {
cout << "\nNode with value " << val << " not found." << endl;
}
return 0;
}
最大深度(高度)
二叉树的最大深度是从根节点到最远叶子节点的最长路径的长度。
int maxDepth(TreeNode* root) {
if (!root) return 0;
return max(maxDepth(root->left), maxDepth(root->right)) + 1;
}
int main() {
TreeNode* root = new TreeNode(1);
root->left = new TreeNode(2);
root->right = new TreeNode(3);
root->left->left = new TreeNode(4);
root->left->right = new TreeNode(5);
cout << "Max Depth: " << maxDepth(root) << endl; // 输出:3
return 0;
}
平衡二叉树(AVL树)
平衡二叉树(AVL树)是一种自平衡的二叉搜索树。对于AVL树,每个节点的左右子树的高度差的绝对值不超过1。
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
int height; // 高度
TreeNode(int x) : val(x), left(nullptr), right(nullptr), height(1) {}
};
// 获取节点的高度
int height(TreeNode* node) {
return node ? node->height : 0;
}
// 获取平衡因子
int getBalance(TreeNode* node) {
return node ? height(node->left) - height(node->right) : 0;
}
// 右旋
TreeNode* rightRotate(TreeNode* y) {
TreeNode* x = y->left;
TreeNode* T2 = x->right;
x->right = y;
y->left = T2;
y->height = max(height(y->left), height(y->right)) + 1;
x->height = max(height(x->left), height(x->right)) + 1;
return x;
}
// 左旋
TreeNode* leftRotate(TreeNode* x) {
TreeNode* y = x->right;
TreeNode* T2 = y->left;
y->left = x;
x->right = T2;
x->height = max(height(x->left), height(x->right)) + 1;
y->height = max(height(y->left), height(y->right)) + 1;
return y;
}
// 插入节点并保持平衡
TreeNode* insert(TreeNode* node, int val) {
if (!node) return new TreeNode(val);
if (val < node->val) {
node->left = insert(node->left, val);
} else if (val > node->val) {
node->right = insert(node->right, val);
} else {
return node; // 相等的值不插入
}
// 更新当前节点的高度
node->height = max(height(node->left), height(node->right)) + 1;
// 获取平衡因子
int balance = getBalance(node);
// 左子树不平衡
if (balance > 1 && val < node->left->val) return rightRotate(node);
// 右子树不平衡
if (balance < -1 && val > node->right->val) return leftRotate(node);
// 左右子树不平衡
if (balance > 1 && val > node->left->val) {
node->left = leftRotate(node->left);
return rightRotate(node);
}
// 右左子树不平衡
if (balance < -1 && val < node->right->val) {
node->right = rightRotate(node->right);
return leftRotate(node);
}
return node;
}
int main() {
TreeNode* root = nullptr;
root = insert(root, 30);
root = insert(root, 20);
root = insert(root, 40);
root = insert(root, 10); // 会触发左旋
cout << "Root after balancing: " << root->val << endl; // 输出:20
return 0;
}
最小公共祖先(Lowest Common Ancestor, LCA)
给定二叉树的两个节点,找出它们的最低公共祖先。
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (!root || root == p || root == q) return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (left && right) return root; // 如果p和q分别位于左右子树
return left ? left : right; // 否则返回非空的那个子树
}
int main() {
TreeNode* root = new TreeNode(3);
root->left = new TreeNode(5);
root->right = new TreeNode(1);
root->left->left = new TreeNode(6);
root->left->right = new TreeNode(2);
root->right->left = new TreeNode(0);
root->right->right = new TreeNode(8);
root->left->right->left = new TreeNode(7);
root->left->right->right = new TreeNode(4);
TreeNode* p = root->left; // 节点5
TreeNode* q = root->left->right->right; // 节点4
TreeNode* lca = lowestCommonAncestor(root, p, q);
cout << "Lowest Common Ancestor: " << lca->val << endl; // 输出:5
return 0;
}
路径总和(Path Sum)
给定一个二叉树和一个目标值,判断是否存在从根到叶的路径,使得路径上的所有节点值之和等于目标值。
bool hasPathSum(TreeNode* root, int sum) {
if (!root) return false;
if (!root->left && !root->right) return root->val == sum;
return hasPathSum(root->left, sum - root->val) || hasPathSum(root->right, sum - root->val);
}
int main() {
TreeNode* root = new TreeNode(5);
root->left = new TreeNode(4);
root->right = new TreeNode(8);
root->left->left = new TreeNode(11);
root->right->left = new TreeNode(13);
root->right->right = new TreeNode(4);
root->left->left->left = new TreeNode(7);
root->left->left->right = new TreeNode(2);
root->right->right->right = new TreeNode(1);
int target = 22;
cout << "Has Path Sum: " << hasPathSum(root, target) << endl; // 输出:1 (true)
return 0;
}
Huffman 编码
Huffman 编码是一种贪心算法,用于数据压缩,特别是在无损数据压缩中。它通过给频率较高的字符分配较短的编码,而给频率较低的字符分配较长的编码,从而减少了数据的总体大小。
Huffman 编码是基于 二叉树 的原理构建的,具体来说,它使用 二叉树 来表示字符及其对应的编码。通过构建一个特定的二叉树(Huffman 树),可以生成最优的编码方案。
Huffman 编码步骤
- 统计字符频率:计算每个字符在文本中出现的频率。
- 构建优先队列:使用最小堆(优先队列)来存储频率和字符的对应关系,频率最低的节点具有最高的优先级。
- 构建 Huffman 树:
- 每次从队列中取出两个频率最小的节点,合并成一个新的节点,并将新节点的频率设置为两个节点频率的和。将合并后的节点重新插入队列。
- 重复这个过程,直到队列中只剩下一个节点,这个节点就是 Huffman 树的根节点。
- 生成编码:从 Huffman 树的根节点开始遍历,左边分配
0
,右边分配1
,直到到达叶子节点,叶子节点的路径就是对应字符的 Huffman 编码。
二叉树和 Huffman 编码算法
#include <iostream>
#include <queue>
#include <unordered_map>
#include <vector>
using namespace std;
// 定义 Huffman 树的节点结构
struct HuffmanNode {
char ch; // 字符
int freq; // 字符的频率
HuffmanNode* left; // 左子节点
HuffmanNode* right; // 右子节点
// 构造函数
HuffmanNode(char c, int f) : ch(c), freq(f), left(nullptr), right(nullptr) {}
// 重载比较运算符,用于优先队列
bool operator>(const HuffmanNode& other) const {
return freq > other.freq;
}
};
// 构建 Huffman 树
HuffmanNode* buildHuffmanTree(const unordered_map<char, int>& freqMap) {
priority_queue<HuffmanNode, vector<HuffmanNode>, greater<HuffmanNode>> minHeap;
// 将频率表中的字符和频率加入到优先队列
for (const auto& pair : freqMap) {
minHeap.push(HuffmanNode(pair.first, pair.second));
}
// 构建 Huffman 树
while (minHeap.size() > 1) {
// 取出频率最小的两个节点
HuffmanNode* left = new HuffmanNode(minHeap.top());
minHeap.pop();
HuffmanNode* right = new HuffmanNode(minHeap.top());
minHeap.pop();
// 创建一个新节点,频率为左子树和右子树的频率之和
HuffmanNode* newNode = new HuffmanNode('\0', left->freq + right->freq);
newNode->left = left;
newNode->right = right;
// 将新节点加入到队列中
minHeap.push(*newNode);
}
// 返回最终的 Huffman 树的根节点
return new HuffmanNode(minHeap.top());
}
// 递归生成编码
void generateHuffmanCodes(HuffmanNode* root, const string& str, unordered_map<char, string>& huffmanCodes) {
if (!root) return;
// 如果是叶子节点,则保存该字符的编码
if (root->ch != '\0') {
huffmanCodes[root->ch] = str;
}
// 递归地为左子树和右子树生成编码
generateHuffmanCodes(root->left, str + "0", huffmanCodes);
generateHuffmanCodes(root->right, str + "1", huffmanCodes);
}
// 主函数:执行 Huffman 编码
int main() {
string input = "this is an example for huffman encoding";
// 统计每个字符的频率
unordered_map<char, int> freqMap;
for (char ch : input) {
freqMap[ch]++;
}
// 构建 Huffman 树
HuffmanNode* root = buildHuffmanTree(freqMap);
// 生成 Huffman 编码
unordered_map<char, string> huffmanCodes;
generateHuffmanCodes(root, "", huffmanCodes);
// 输出生成的 Huffman 编码
cout << "Huffman Codes:\n";
for (const auto& pair : huffmanCodes) {
cout << pair.first << ": " << pair.second << endl;
}
// 编码字符串
string encodedStr = "";
for (char ch : input) {
encodedStr += huffmanCodes[ch];
}
cout << "\nEncoded String: " << encodedStr << endl;
return 0;
}
表达式树
表达式树(Expression Tree)是一种特殊的二叉树,其中每个叶子节点代表操作数(如常数或变量),而每个非叶子节点代表操作符(如 +
, -
, *
, /
等)。表达式树用于表示数学表达式,能够方便地执行数学运算、转换为后缀表达式(逆波兰表示法)等操作。
构建表达式树
- 叶子节点:表示操作数(例如,数字、变量)。
- 非叶子节点:表示操作符(例如,
+
,-
,*
,/
等)。
通过二叉树的结构可以轻松地表示表达式,并且通过树的遍历可以方便地求值或者生成不同的表示形式。
如何构建表达式树
假设给定一个中缀表达式(如 a + b * c
),我们可以通过以下步骤构建表达式树:
- 中缀表达式解析:根据操作符的优先级和括号来构建树,低优先级操作符会成为根节点,高优先级操作符会作为子节点。
- 递归构建树:从最内层的操作开始,逐步构建表达式树。
表达式树实现
#include <iostream>
#include <stack>
#include <cctype>
#include <cmath>
using namespace std;
// 定义表达式树的节点
struct TreeNode {
char value; // 操作符或者操作数
TreeNode* left;
TreeNode* right;
TreeNode(char val) : value(val), left(nullptr), right(nullptr) {}
};
int precedence(char op) {
if (op == '+' || op == '-') return 1;
if (op == '*' || op == '/') return 2;
return 0; // 其他操作符(比如括号)的优先级
}
// 创建表达式树的函数
TreeNode* constructExpressionTree(const string& expression) {
stack<TreeNode*> values; // 存储操作数的栈
stack<TreeNode*> operators; // 存储操作符的栈
for (char ch : expression) {
if (isspace(ch)) continue; // 跳过空格
if (isdigit(ch)) { // 如果是操作数
values.push(new TreeNode(ch));
} else if (ch == '(') {
operators.push(new TreeNode(ch)); // 左括号
} else if (ch == ')') {
// 处理右括号:直到遇到左括号
while (!operators.empty() && operators.top()->value != '(') {
TreeNode* opNode = operators.top();
operators.pop();
TreeNode* right = values.top();
values.pop();
TreeNode* left = values.top();
values.pop();
opNode->left = left;
opNode->right = right;
values.push(opNode); // 将合并的节点压入值栈
}
operators.pop(); // 弹出 '('
} else { // 如果是操作符
while (!operators.empty() && precedence(operators.top()->value) >= precedence(ch)) {
TreeNode* opNode = operators.top();
operators.pop();
TreeNode* right = values.top();
values.pop();
TreeNode* left = values.top();
values.pop();
opNode->left = left;
opNode->right = right;
values.push(opNode);
}
operators.push(new TreeNode(ch)); // 当前操作符入栈
}
}
// 处理栈中剩余的操作符
while (!operators.empty()) {
TreeNode* opNode = operators.top();
operators.pop();
TreeNode* right = values.top();
values.pop();
TreeNode* left = values.top();
values.pop();
opNode->left = left;
opNode->right = right;
values.push(opNode);
}
return values.top(); // 返回根节点
}
void inorderTraversal(TreeNode* root) {
if (!root) return;
inorderTraversal(root->left);
cout << root->value;
inorderTraversal(root->right);
}
int evaluateExpressionTree(TreeNode* root) {
if (!root) return 0;
// 如果是操作数(数字)
if (!root->left && !root->right) {
return root->value - '0'; // 将字符转换为整数
}
// 递归计算左右子树的值
int leftValue = evaluateExpressionTree(root->left);
int rightValue = evaluateExpressionTree(root->right);
// 根据操作符计算结果
switch (root->value) {
case '+': return leftValue + rightValue;
case '-': return leftValue - rightValue;
case '*': return leftValue * rightValue;
case '/': return leftValue / rightValue;
}
return 0; // 如果是无效操作符
}
int main() {
string expression = "3 + 5 * (2 - 8)"; // 示例中缀表达式
TreeNode* root = constructExpressionTree(expression);
cout << "Inorder traversal (original expression): ";
inorderTraversal(root);
cout << endl;
int result = evaluateExpressionTree(root);
cout << "Result of expression evaluation: " << result << endl;
return 0;
}