(写给未来遗忘的自己)
1.二叉树的种类
1. 满二叉树:所有分支都有数(都填满)
2. 完全二叉树:除了最底层没填满外其他的都满了,而且最底层从左到右是存在数的位置是连续的
3.二叉搜索树:二叉搜索树是一个有序树
$ 左子树不空,则左子树上所有节点的值小于根节点
$右子树不空,则右子树所有节点的值大于根节点
$ 左右子树下面也符合上述的两条
4.平衡二叉搜索树:空树或者左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是二 叉搜索树
2.二叉树的存储方式:
1.链式存储
链式存储以类似链表的方式存储。
代码如下:
#include <iostream>
#include <queue>
using namespace std;
// 定义二叉树的节点结构
struct TreeNode {
int data;
TreeNode* left;
TreeNode* right;
// 构造函数
TreeNode(int val) : data(val), left(nullptr), right(nullptr) {}
};
// 插入节点的函数
TreeNode* insert(TreeNode* root, int val) {
// 如果根节点为空,创建新的节点作为根节点
if (root == nullptr) {
return new TreeNode(val);
}
// 使用队列进行层次遍历,找到第一个可以插入的空位置
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
TreeNode* node = q.front();
q.pop();
// 插入左子节点
if (node->left == nullptr) {
node->left = new TreeNode(val);
break;
}
else {
q.push(node->left);
}
// 插入右子节点
if (node->right == nullptr) {
node->right = new TreeNode(val);
break;
}
else {
q.push(node->right);
}
}
return root;
}
int main() {
TreeNode* root=nullptr;
// 插入节点
root = insert(root, 1);
root = insert(root, 2);
root = insert(root, 3);
root = insert(root, 4);
return 0;
}
2.顺序存储
二叉树的节点按照层次遍历的顺序存储在数组中。对于一个节点在数组中的位置,可以通过以下规则来访问其子节点和父节点:
- 节点在数组中的索引为
i
:- 左子节点的位置为
2*i + 1
。 - 右子节点的位置为
2*i + 2
。 - 父节点的位置为
(i - 1) / 2
(对整数除法取整)
- 左子节点的位置为
1
/ \
2 3
/ \ / \
4 5 6 7
在顺序存储中,这棵树表示为一个数组:
[1, 2, 3, 4, 5, 6, 7]
#include <iostream>
#include <vector>
class BinaryTree {
public:
// 构造函数,接受一个数组来初始化树
BinaryTree(const std::vector<int>& elements) : tree(elements) {}
// 获取指定节点的左子节点
int leftChild(int index) {
int leftIndex = 2 * index + 1;
if (leftIndex < tree.size()) {
return tree[leftIndex];
} else {
throw std::out_of_range("No left child.");
}
}
// 获取指定节点的右子节点
int rightChild(int index) {
int rightIndex = 2 * index + 2;
if (rightIndex < tree.size()) {
return tree[rightIndex];
} else {
throw std::out_of_range("No right child.");
}
}
// 获取指定节点的父节点
int parent(int index) {
if (index == 0) {
throw std::out_of_range("No parent for root node.");
}
int parentIndex = (index - 1) / 2;
return tree[parentIndex];
}
// 打印树的内容
void printTree() {
for (int value : tree) {
std::cout << value << " ";
}
std::cout << std::endl;
}
private:
std::vector<int> tree;
};
int main() {
// 创建一个树 [1, 2, 3, 4, 5, 6, 7]
std::vector<int> elements = {1, 2, 3, 4, 5, 6, 7};
BinaryTree bt(elements);
// 打印树
bt.printTree();
// 获取节点和打印其子节点
try {
std::cout << "Left child of 0: " << bt.leftChild(0) << std::endl;
std::cout << "Right child of 0: " << bt.rightChild(0) << std::endl;
std::cout << "Parent of 1: " << bt.parent(1) << std::endl;
std::cout << "Parent of 2: " << bt.parent(2) << std::endl;
} catch (const std::out_of_range& e) {
std::cout << e.what() << std::endl;
}
return 0;
}
优点:构造函数使用初始化列表直接初始化成员变量,避免额外的拷贝
缺点:不适合频繁变化的树结构(如节点插入和删除),因为需要移动大量节点。也不适用于不完全的二叉树,因为会浪费数组空间
3.二叉树的遍历方式:
1
/ \
2 3
/ \ / \
4 5 6 7
/ \ / \ / \ / \
8 9 10 11 12 13 14 15
//三种方式都是深度优先算法,所以中(将中一直搜索到最底,没有了才开始下一个左)
//前序遍历:(中左右)
1, 2, 4, 8, 9, 5, 10, 11, 3, 6, 12, 13, 7, 14, 15
//中序遍历:(左中右)
8, 4, 9, 2, 10, 5, 11, 1, 12, 6, 13, 3, 14, 7, 15
//后序遍历
8, 9, 4, 10, 11, 5, 2, 12, 13, 6, 14, 15, 7, 3, 1
3.1递归遍历:
递归是一种简洁的实现方式,适合自然的分治问题。每次递归调用函数时,都会自动处理子树
前序遍历:(中左右)
#include <iostream>
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
// 递归前序遍历
void preorderTraversalRecursive(TreeNode* root) {
if (root == nullptr) return;
std::cout << root->val << " "; // 访问根节点
preorderTraversalRecursive(root->left); // 遍历左子树
preorderTraversalRecursive(root->right); // 遍历右子树
}
中序遍历:(左中右)
void inorderTraversalRecursive(TreeNode* root) {
if (root == nullptr) return;
inorderTraversalRecursive(root->left); // 遍历左子树
std::cout << root->val << " "; // 访问根节点
inorderTraversalRecursive(root->right); // 遍历右子树
}
后序遍历:(左右中)
#include <iostream>
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
// 后序遍历的递归实现
void postorderTraversalRecursive(TreeNode* root) {
if (root == nullptr) return;
postorderTraversalRecursive(root->left); // 递归遍历左子树
postorderTraversalRecursive(root->right); // 递归遍历右子树
std::cout << root->val << " "; // 访问根节点
}
3.2迭代遍历:
非递归遍历使用栈来显式地保存状态,可以避免函数调用栈的开销,并且更适合处理非常深的树结构,防止栈溢出。
前序遍历:
#include <iostream>
#include <stack>
void preorderTraversalIterative(TreeNode* root) {
if (root == nullptr) return;
std::stack<TreeNode*> s;
s.push(root);
while (!s.empty()) {
TreeNode* node = s.top();
s.pop();
std::cout << node->val << " "; // 访问根节点
// 注意这里先压右子节点,再压左子节点,因为栈是后进先出
if (node->right) s.push(node->right);
if (node->left) s.push(node->left);
}
}
中序遍历:
void inorderTraversalIterative(TreeNode* root) {
std::stack<TreeNode*> s;
TreeNode* curr = root;
while (curr != nullptr || !s.empty()) {
// 不断访问左子节点并压入栈中
while (curr != nullptr) {
s.push(curr);
curr = curr->left;
}
// 弹出栈顶节点,访问该节点
curr = s.top();
s.pop();
std::cout << curr->val << " ";
// 遍历右子树
curr = curr->right;
}
}
后序遍历:
#include <iostream>
#include <stack>
#include <vector>
void postorderTraversalIterative(TreeNode* root) {
if (root == nullptr) return;
std::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);
}
// 最后从栈2中弹出节点并访问
while (!s2.empty()) {
std::cout << s2.top()->val << " ";
s2.pop();
}
}
广度优先算法(层次遍历):
#include <iostream>
#include <queue>
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
void levelOrderTraversal(TreeNode* root) {
if (root == nullptr) return; // 若根节点为空,直接返回
std::queue<TreeNode*> q; // 定义一个队列
q.push(root); // 将根节点入队
while (!q.empty()) {
TreeNode* node = q.front(); // 取队列头部的节点
q.pop(); // 出队列
std::cout << node->val << " "; // 访问节点
// 若有左子节点,将左子节点入队
if (node->left != nullptr) {
q.push(node->left);
}
// 若有右子节点,将右子节点入队
if (node->right != nullptr) {
q.push(node->right);
}
}
}
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);
root->right->left = new TreeNode(6);
root->right->right = new TreeNode(7);
// 执行广度优先遍历
std::cout << "二叉树的广度优先遍历结果: ";
levelOrderTraversal(root);
return 0;
}