梦想不会自己发光,真正闪耀的是那个为梦狂奔的你。献给知行的孩子们!(Eric.He著)
在数据结构的世界中,我们已经接触过线性结构(如数组、链表、栈、队列),它们的特点是数据元素之间呈“一对一”的线性关系。但在实际生活中,很多场景的关系并非线性,比如:
这些“一对多”的层级关系,用线性结构难以高效表示和处理,而“树”结构正是为解决这类问题而生的。树结构不仅能清晰地刻画层级关系,还能提供高效的查找、插入、删除操作,是后续学习红黑树、B树、堆等高级数据结构的基础。
二叉树是树结构中最常用、最核心的一种,它的结构相对简单,且任何复杂的树都可以转化为二叉树来处理,因此本教程将重点围绕树的基本概念和二叉树展开。
树(Tree)是由n(n≥0)个有限节点组成的具有 层次关系 的集合,当n=0时,称为“空树”;当n≥1时,树的结构满足以下两个条件:
| 术语 | 定义 |
|---|---|
| 节点 | 节点(Node)是构成树的最基本、最核心的单元,所有树的操作(如插入、删除、遍历、查找)都是围绕节点展开的。二叉树的节点是存储数据 + 关联左右子节点 的数据单元,本质是 “数据 + 指针”。二叉树中通常包含根节点(root)、父节点(parent)、子节点(child)、左节点(left child)、右节点(right child)、叶节点(leaf)等称呼。 |
| 度 | 度(Degree) 是描述节点“分支能力” 的核心指标,一个节点拥有的子节点的数量(即该节点的 “分支数”)。例:二叉树的度为<=2、三叉树的度为<=3。 |
| 层次 | 层次(Level) 是描述节点在树中纵向位置的核心指标,用于衡量节点与根节点的 “距离”。 |
| 深度 | 深度(Depth)是从根节点到该节点的路径上的节点数(等价于层次数) |
| 高度 | 高度(Height)从该节点到其最远叶子节点的路径上的节点总数。 |
| 路径 | 路径(Path)从一个节点到另一个节点的有序节点序列。 |
二叉树(Binary Tree)是一种特殊的树结构,它的每个节点的度都不超过2,即每个节点最多有两个子树,且这两个子树有明确的左右之分,分别称为“左子树”和“右子树”。
二叉树有5种基本形态:
二叉树具有以下几个核心性质,这些性质是后续学习二叉树遍历、存储和应用的基础:
在二叉树中,有三种特殊的类型应用广泛:满二叉树、完全二叉树和平衡二叉树。
| 对比维度 | 满二叉树 | 完全二叉树 | 平衡二叉树 |
|---|---|---|---|
| 核心定义 |
|
任意节点的左右子树高度差的绝对值 ≤ 1(平衡因子 ∈ {-1,0,1}) |
|
| 节点数公式 | 高度为 h 时,总节点数 n=2h−1(节点数固定) | 高度为 h 时,节点数范围 2h−1≤n≤2h−1 | 无固定公式,满足 h=O(log n) 即可 |
| 叶子节点特征 | 全部位于最底层,数量为 2h−1 | 叶子节点只能在最后两层,且最后一层的叶子靠左排列 | 叶子节点位置无限制,只需满足高度差约束 |
| 高度特性 | 高度 h=log2(n+1),形态唯一 | 高度 h=⌊log2n⌋+1,形态不唯一 | 最大高度为 O(log n),避免退化为链表 |
| 包含关系 |
|
|
|
| 核心目标 | 结构极致规整,充分利用层级空间 | 适配数组存储(可通过下标直接定位父 / 子节点) | 保证增删查的时间复杂度稳定在 O(log n),避免树退化为链表 |
| 判断方法 |
|
按层序遍历,检查缺失节点是否都在最底层右侧 |
|
| 常见应用 | 霍夫曼树、完美二叉树模型分析 | 堆(大顶堆 / 小顶堆)的底层存储结构、二叉树的数组化表示 | 平衡二叉搜索树(AVL 树、红黑树)、数据库索引、有序集合容器 |
遍历(Traversal) 指的是按照某种既定规则,依次访问树中所有节点且每个节点仅被访问一次的操作。所有树的操作(如插入、删除、遍历、查找)都需通过遍历算法实现。
二叉树每个节点包含根节点(N)、左子树(L)、右子树(R)三部分,遍历的核心是确定这三者的访问顺序。
四种遍历访问顺序:
主流遍历方式分为深度优先遍历(DFS)和广度优先遍历(BFS)两大类:
| 遍历方式 | 访问顺序 | 核心特性 |
|---|---|---|
| (DFS)前序遍历 | 根 → 左 → 右 | 根节点优先访问,适合复制树结构 |
| (DFS)中序遍历 | 左 → 根 → 右 | 对二叉搜索树(BST),输出升序序列 |
| (DFS)后序遍历 | 左 → 右 → 根 | 子节点优先访问,适合删除树、计算子树大小 |
| (BFS)层次遍历 | 从上到下、从左到右 | 能直观反映树的层级结构,适合计算树的高度、按层处理节点。 |
二叉搜索树结构是一对多的关系,除了树根之外,每一个节点有唯一的直接前驱(父亲),除了叶子之外,每一个节点有一个或两个直接后继(孩子)。
可以采用顺序存储和链式存储两种形式:
把根节点存储在下标 i = 1 的位置,把左子节点存储在下标 2 * i = 2 的位置,右子节点存储在 2 * i + 1 = 3 的位置。树为满二叉树或完全二叉树时不会浪费太多存储空间。
每一个节点有三个域,即数据域data、lchild左节点地址、rchild右节点地址。
| 操作名称 | 功能描述 |
|---|---|
| 前序建立二叉树 | 根据前序和中序序列,建立一棵二叉树 |
| 后序建立二叉树 | 根据后序和中序序列,建立一棵二叉树 |
| 层序建立二叉树 | 根据层序和中序序列,建立一棵二叉树 |
| 删除二叉树 | 删除指定二叉树,包含它的左子树和右子树 |
| 前序遍历(递归) | 根据根 → 左 → 右方式遍历打印二叉树所有元素 |
| 中序遍历(递归) | 根据左 → 根 → 右方式遍历打印二叉树所有元素 |
| 后序遍历(递归) | 根据左 → 右 → 根方式遍历打印二叉树所有元素 |
| 前序遍历(迭代) | 根据根 → 左 → 右方式遍历打印二叉树所有元素 |
| 中序遍历(迭代) | 根据左 → 根 → 右方式遍历打印二叉树所有元素 |
| 后序遍历(迭代) | 根据左 → 右 → 根方式遍历打印二叉树所有元素 |
| 层序遍历(迭代) | 从上到下、从左到右方式遍历打印二叉树所有元素 |
提供的代码是模板化的二叉树实现(支持任意类型),采用C++结构体封装,分为头文件(声明)和源文件(实现)两部分,核心结构如下:
| 模块 | 作用 | 关键结构/函数 |
|---|---|---|
| 二叉树结构体 | 封装所有操作 | ArrayBinaryTree<T>(模板类) |
分为私有成员(内部实现)和公有成员(对外接口):
template <typename T>
struct ArrayBinaryTree{
// ------------------------------------------------------------------------------------------------------
// 私有成员
// 注:私有成员只能在该结构体内部访问调用,外部通过该结构体定义(实例化)的变量(对象)不能访问调用)
//------------------------------------------------------------------------------------------------------
private:
//---------------声明私有成员变量---------------
T tree[MAXSIZE]; // 存储二叉树数据,支持任意类型
int qty; // 用于跟踪二叉树中的元素数量
T valNull = T(); // 空节点标识(模板化)
//---------------声明私有成员函数---------------
//辅助函数
int findRootIndex(const T inTree[], int start, int end, const T rootVal); // 查找根节点在中序序列中的索引
int isInRange(const T val, const T inOrder[], int start, int end); // 检查元素是否在中序的指定范围内(用于筛选层序中的子树节点)
int filterLevelNodes(const T levelOrder[], int levelLen, const T inOrder[], int inStart, int inEnd, T subLevel[]);// 筛选层序数组中属于指定中序范围的节点,生成子层序数组
// ------------------------------------------------------------------------------------------------------
// 公共成员
// 注:公共成员能在该结构体内部访问调用,外部通过该结构体定义(实例化)的变量(对象)也能访问调用)
//------------------------------------------------------------------------------------------------------
public:
//---------------声明公共成员函数---------------
// 构造函数:外部通过该结构体定义(实例化)变量(对象)时,自动执行该函数(主要用于初始化成员变量的值)
ArrayBinaryTree() {
qty = 0;
for(int i=0;i < MAXSIZE;i++) { tree[i] = T(); }
}
//功能函数
void buildTreeByPre(const T preTree[], int preStart, int preEnd, const T inTree[], int inStart, int inEnd, int nodeNum, int treeSize);// 根据前序和中序列建立二叉树
void buildTreeByPost(const T postTree[], int postStart, int postEnd, const T inTree[], int inStart, int inEnd, int nodeNum, int treeSize);// 根据后序和中序列建立二叉树
void buildTreeByLevel(const T levelTree[], int levelLen, const T inTree[], int inStart, int inEnd, int nodeNum, int treeSize);// 根据层序和中序列建立二叉树
void deleteTree(int index);// 删除二叉树
//递归遍历
void preOrder(int index);// 前序遍历(根 → 左 → 右)
void inOrder(int index);// 中序遍历(左 → 根 → 右)→ 升序输出
void postOrder(int index);// 后序遍历(左 → 右 → 根)
//迭代遍历
void preOrderIter(int index);// 前序遍历(根 → 左 → 右)
void inOrderIter(int index);// 中序遍历(左 → 根 → 右)→ 升序输出
void postOrderIter(int index);// 后序遍历(左 → 右 → 根)
void levelOrderIter(int index);// 层序遍历(从上到下、从左到右)
// 析构函数:自动销毁队列,避免内存泄漏
~ArrayBinaryTree() {
}
};
给定二叉树的中序和其他(前序、后序、层序)一种遍历的序列就可以确定一棵二叉树的结构。
根据前序和中序序列建立二叉树
// 根据前序和中序列建立二叉树
// 参数:preTree-前序序列, preStart-前序起始索引, preEnd-前序结束索引
// inTree-中序序列, inStart-中序起始索引,inEnd-中序结束索引
// nodeNum-当前节点的编号(从1开始)
template <typename T>
void ArrayBinaryTree<T>::buildTreeByPre(const T preTree[], int preStart, int preEnd,
const T inTree[], int inStart, int inEnd, int nodeNum, int treeSize) {
if (preStart > preEnd || inStart > inEnd) {
return;
}
qty = treeSize;//设置二叉树最大节点数
T rootVal = preTree[preStart]; // 1. 当前根节点值(前序子序列第一个元素)
tree[nodeNum] = rootVal; // 2. 将根节点存入数组对应位置
int rootIdx = findRootIndex(inTree, inStart, inEnd, rootVal);// 3. 找到根节点在中序序列中的索引
int leftSize = rootIdx - inStart; // 4. 计算左子树的节点个数
// 5. 递归构建左子树
// 前序左子树范围:[preStart+1, preStart+leftSize],
// 中序左子树范围:[inStart, rootIdx-1]
// 左子节点编号:nodeNum * 2
buildTreeByPre(preTree, preStart + 1, preStart + leftSize,
inTree, inStart, rootIdx - 1,
nodeNum * 2, treeSize);
// 6. 递归构建右子树
// 前序右子树范围:[preStart+leftSize+1, preEnd]
// 中序右子树范围:[rootIdx+1, inEnd]
// 右子节点编号:nodeNum * 2 + 1
buildTreeByPre(preTree, preStart + leftSize + 1, preEnd,
inTree, rootIdx + 1, inEnd,
nodeNum * 2 + 1, treeSize);
}
根据后序和中序列建立二叉树
// 根据后序和中序序列建立二叉树
// 参数:postTree-后序序列, postStart-后序起始索引, postEnd-后序结束索引
// inTree-中序序列, inStart-中序起始索引,inEnd-中序结束索引
// nodeNum-当前节点的编号(从1开始)
template <typename T>
void ArrayBinaryTree<T>::buildTreeByPost(const T postTree[], int postStart, int postEnd, const T inTree[], int inStart, int inEnd, int nodeNum, int treeSize) {
if (postStart > postEnd || inStart > inEnd) {
return;
}
qty = treeSize;//设置二叉树最大节点数
T rootVal = postTree[postEnd]; // 1. 后序序列最后一个元素是当前根节点
tree[nodeNum] = rootVal; // 2. 将根节点存入数组对应位置
int rootIdx = findRootIndex(inTree, inStart, inEnd, rootVal);// 3. 找到根节点在中序序列中的索引
int leftSize = rootIdx - inStart; // 4. 计算左子树的节点个数
// 5. 递归构建左子树
// 后序左子树范围:[postStart, postStart + leftSize - 1]
// 中序左子树范围:[inStart, rootIdx - 1]
// 左子节点编号:nodeNum * 2
buildTreeByPost(postTree, postStart, postStart + leftSize - 1,
inTree, inStart, rootIdx - 1,
nodeNum * 2, treeSize);
// 6. 递归构建右子树
// 后序右子树范围:[postStart + leftSize, postEnd - 1]
// 中序右子树范围:[rootIdx + 1, inEnd]
// 右子节点编号:nodeNum * 2 + 1
buildTreeByPost(postTree, postStart + leftSize, postEnd - 1,
inTree, rootIdx + 1, inEnd,
nodeNum * 2 + 1, treeSize);
}
根据层序和中序序列建立二叉树
// 根据层序和中序列建立二叉树
// 参数:levelTree-层序序列, levelLen-层序长度
// inTree-中序序列, inStart-中序起始索引,inEnd-中序结束索引
// nodeNum-当前节点的编号(从1开始)
template <typename T>
void ArrayBinaryTree<T>::buildTreeByLevel(const T levelTree[], int levelLen, const T inTree[], int inStart, int inEnd, int nodeNum, int treeSize) {
if (inStart > inEnd || levelLen <= 0) {
return;
}
qty = treeSize;//设置二叉树最大节点数
T rootVal = levelTree[0]; // 1. 层序序列第一个元素是当前根节点
tree[nodeNum] = rootVal; // 2. 将根节点存入数组对应位置
int rootIdx = findRootIndex(inTree, inStart, inEnd, rootVal);// 3. 找到根节点在中序序列中的索引
int leftSize = rootIdx - inStart; // 4. 计算左子树的节点个数
int rightSize = inEnd - rootIdx; // 5. 计算右子树的节点个数
// 6. 筛选层序数组中属于左、右子树的节点
T leftLevel[MAXSIZE], rightLevel[MAXSIZE];
int leftLevelLen = filterLevelNodes(levelTree, levelLen, inTree, inStart, rootIdx - 1, leftLevel);
int rightLevelLen = filterLevelNodes(levelTree, levelLen, inTree, rootIdx + 1, inEnd, rightLevel);
// 4. 递归构建左、右子树
buildTreeByLevel(leftLevel, leftLevelLen, inTree, inStart, rootIdx - 1, nodeNum * 2, treeSize);
buildTreeByLevel(rightLevel, rightLevelLen, inTree, rootIdx + 1, inEnd, nodeNum * 2 + 1, treeSize);
}
二叉树的遍历分为前序、中序、后序、层次。
从root节点开始,根 → 左 → 右的顺序遍历树,采用递归的方式实现,如果节点数比较大建议采用迭代方式实现,防止内存溢出。
template <typename T>
void ArrayBinaryTree<T>::preOrder(int index)
{
if (index >qty) return;
if(tree[index] != valNull)
std::cout<<tree[index]<<" ";
preOrder(index*2);
preOrder(index*2+1);
}
从root节点开始,左 → 根 → 右的顺序遍历树,采用递归的方式实现,如果节点数比较大建议采用迭代方式实现,防止内存溢出。
template <typename T>
void ArrayBinaryTree<T>::inOrder(int index)
{
if (index >qty) return;
inOrder(index*2);
if(tree[index] != valNull)
std::cout<<tree[index]<<" ";
inOrder(index*2+1);
}
从root节点开始,左 → 右 → 根的顺序遍历树,采用递归的方式实现,如果节点数比较大建议采用迭代方式实现,防止内存溢出。
template <typename T>
void ArrayBinaryTree<T>::postOrder(int index)
{
if (index >qty) return;
postOrder(index*2);
postOrder(index*2+1);
if(tree[index] != valNull)
std::cout<<tree[index]<<" ";
}
二叉树的遍历分为前序、中序、后序、层次。
从root节点开始,根 → 左 → 右的顺序遍历树,采用迭代+栈的方式实现,如果节点数比较小建议采用递归方式实现(容易实现)。
template <typename T>
void ArrayBinaryTree<T>::preOrderIter(int index)
{
std::stack<int> stack1; // 存储节点编号
stack1.push(index); // 根节点入栈
while (stack1.size() >= 1) {
int currIndex = stack1.top();stack1.pop(); // 出栈
if (currIndex >= qty) {
continue;
}
if(tree[currIndex] != valNull)
std::cout<<tree[currIndex]<<" "; // 访问根
// 栈后进先出,先入栈右子树,后入栈左子树
int rightIndex = currIndex * 2 + 1;
if (rightIndex <= qty) {
stack1.push(rightIndex);
}
int leftIndex = currIndex * 2;
if (leftIndex <= qty) {
stack1.push(leftIndex);
}
}
}
从root节点开始,左 → 根 → 右的顺序遍历树,采用迭代+栈的方式实现,如果节点数比较小建议采用递归方式实现(容易实现)。
template <typename T>
void ArrayBinaryTree<T>::inOrderIter(int index)
{
std::stack<int> stack1; // 存储节点编号
int currIndex = index;
T valNull = T();
// 迭代终止条件:当前节点为空 且 栈为空
while (stack1.size()>=1 || (currIndex < qty && tree[currIndex] != valNull)) {
// 第一步:遍历到最左子节点,路径上的节点索引入栈
while( currIndex <= qty && tree[currIndex] != valNull ) {
stack1.push(currIndex);
currIndex = currIndex * 2; // 左子节点
}
// 第二步:弹出栈顶节点(最左节点)
currIndex = stack1.top();stack1.pop();
std::cout<<tree[currIndex]<<" ";
// 第三步:转向右子节点
currIndex = currIndex * 2 + 1;
}
}
从root节点开始,左 → 右 → 根的顺序遍历树,采用迭代+栈的方式实现,如果节点数比较小建议采用递归方式实现(容易实现)。
template <typename T>
void ArrayBinaryTree<T>::postOrderIter(int index)
{
std::stack<int> stack1, stack2;
stack1.push(index);
// 栈1:根→右→左;栈2:存储逆序结果
while (stack1.size() >= 1) {
int currIndex = stack1.top(); stack1.pop();
if (currIndex > qty) {
continue;
}
stack2.push(currIndex);
// 先入栈左子树,后入栈右子树(栈1出栈顺序相反)
int leftIndex = currIndex * 2;
if (leftIndex <= qty) {
stack1.push(leftIndex);
}
int rightIndex = currIndex * 2 + 1;
if (rightIndex <= qty) {
stack1.push(rightIndex);
}
}
// 输出栈2(后序遍历结果)
while(stack2.size() >= 1) {
int currIndex = stack2.top(); stack2.pop();
if(tree[currIndex] != valNull)
std::cout<<tree[currIndex]<<" ";
}
}
从rode节点开始,从上到下、从左到右顺序遍历树。采用迭代+队列的方式实现。
template <typename T>
void ArrayBinaryTree<T>::levelOrderIter(int index)
{
if (index <= 0) return;
std::queue queues;
queues.push(index); // 根节点入队
while (!queues.empty()) {
int curr = queues.front(); // 获取队头节点
queues.pop();// 出队头节点
if(tree[curr] != valNull)
std::cout<<tree[curr]<<" "; // 输出当前节点值
// 左子节点入队(先左后右)
if (curr <= qty) {
queues.push(curr*2);
}
// 右子节点入队
if (curr <= qty) {
queues.push(curr*2+1);
}
}
}
#include "ArrayBinaryTree.h"
#include <iostream>
int main() {
ArrayBinaryTree<char> tree1;
ArrayBinaryTree<char> tree2;
ArrayBinaryTree<char> tree3;
// 1. 根据前序和中序建立二叉树
printf("== 1.建立二叉树(根据前序和中序) ==\n");
printf(" A \n");
printf(" / \\ \n");
printf(" B C \n");
printf(" / \\ / \\ \n");
printf(" D E F \n");
printf(" / / \n");
printf(" G H \n");
char preTree1[] = {'A', 'B', 'D', 'E', 'G', 'C', 'F', 'H'};// 前序序列
char inTree1[] = {'D', 'B', 'G', 'E', 'A', 'C', 'H', 'F'};// 中序序列
int n1 = sizeof(preTree1) / sizeof(preTree1[0]); // 计算元素个数
int k1 = floor(log2(n1)) + 1;// 计算二叉树深度
int treeSize1 = pow(2, k1) - 1;// 计算二叉树最大节点数
tree1.buildTreeByPre(preTree1, 0, n1-1, inTree1, 0, n1-1, 1, treeSize1); //建立二叉树
printf("\n===== 递归遍历测试 =====\n");
printf("前序遍历:"); tree1.preOrder(1); printf("\n");
printf("中序遍历:"); tree1.inOrder(1); printf("\n");
printf("后序遍历:"); tree1.postOrder(1); printf("\n");
printf("===== 迭代遍历测试 =====\n");
printf("前序遍历:"); tree1.preOrderIter(1); printf("\n");
printf("中序遍历:"); tree1.inOrderIter(1); printf("\n");
printf("后序遍历:"); tree1.postOrderIter(1); printf("\n");
printf("层序遍历:"); tree1.levelOrderIter(1); printf("\n");
// 2. 根据后序和中序建立二叉树
printf("\n== 2.建立二叉树(根据后序和中序)==\n");
printf(" A \n");
printf(" / \\ \n");
printf(" B C \n");
printf(" \\ / \\ \n");
printf(" D E F\n");
printf(" \\ / \n");
printf(" G H \n");
char postTree2[] = {'G', 'D', 'B', 'E', 'H', 'F', 'C', 'A'};// 后序序列
char inTree2[] = {'D', 'G', 'B', 'A', 'E', 'C', 'H', 'F'};// 中序序列
int n2 = sizeof(postTree2) / sizeof(postTree2[0]); // 计算元素个数
int k2 = floor(log2(n2)) + 1;// 计算二叉树深度
int treeSize2 = pow(2, k2) - 1;// 计算二叉树最大节点数
tree2.buildTreeByPost(postTree2, 0, n2-1, inTree2, 0, n2-1, 1, treeSize2);//建立二叉树
printf("===== 递归遍历测试 =====\n");
printf("前序遍历:"); tree2.preOrder(1); printf("\n");
printf("中序遍历:"); tree2.inOrder(1); printf("\n");
printf("后序遍历:"); tree2.postOrder(1); printf("\n");
printf("===== 迭代遍历测试 =====\n");
printf("前序遍历:"); tree2.preOrderIter(1); printf("\n");
printf("中序遍历:"); tree2.inOrderIter(1); printf("\n");
printf("后序遍历:"); tree2.postOrderIter(1); printf("\n");
printf("层序遍历:"); tree2.levelOrderIter(1); printf("\n");
// 3. 根据层序和中序建立二叉树
printf("\n== 3.建立二叉树(根据层序和中序)==\n");
printf(" A \n");
printf(" / \\ \n");
printf(" B C \n");
printf(" \\ / \\ \n");
printf(" D E F\n");
printf(" \\ / \n");
printf(" G H \n");
char levelTree3[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'};// 层序序列
char inTree3[] = {'D', 'G', 'B', 'A', 'E', 'C', 'H', 'F'};// 中序序列
int n3 = sizeof(levelTree3) / sizeof(levelTree3[0]); // 计算元素个数
int k3 = floor(log2(n3)) + 1;// 计算二叉树深度
int treeSize3 = pow(2, k3) - 1;// 计算二叉树最大节点数
tree3.buildTreeByLevel(levelTree3, n3, inTree3, 0, n3-1, 1, treeSize3);//建立二叉树
printf("===== 递归遍历测试 =====\n");
printf("前序遍历:"); tree3.preOrder(1); printf("\n");
printf("中序遍历:"); tree3.inOrder(1); printf("\n");
printf("后序遍历:"); tree3.postOrder(1); printf("\n");
printf("===== 迭代遍历测试 =====\n");
printf("前序遍历:"); tree3.preOrderIter(1); printf("\n");
printf("中序遍历:"); tree3.inOrderIter(1); printf("\n");
printf("后序遍历:"); tree3.postOrderIter(1); printf("\n");
printf("层序遍历:"); tree3.levelOrderIter(1); printf("\n");
return 0;
}
== 1.建立二叉树(根据前序和中序) ==
A
/ \
B C
/ \ / \
D E F
/ /
G H
===== 递归遍历测试 =====
前序遍历:A B D E G C F H
中序遍历:D B G E A C H F
后序遍历:D G E B H F C A
===== 迭代遍历测试 =====
前序遍历:A B D E G C F H
中序遍历:D B G E A C H F
后序遍历:D G E B H F C A
层序遍历:A B C D E F G H
== 2.建立二叉树(根据后序和中序)==
A
/ \
B C
\ / \
D E F
\ /
G H
===== 递归遍历测试 =====
前序遍历:A B D G C E F H
中序遍历:D G B A E C H F
后序遍历:G D B E H F C A
===== 迭代遍历测试 =====
前序遍历:A B D G C E F H
中序遍历:D G B A E C H F
后序遍历:G D B E H F C A
层序遍历:A B C D E F G H
== 3.建立二叉树(根据层序和中序)==
A
/ \
B C
\ / \
D E F
\ /
G H
===== 递归遍历测试 =====
前序遍历:A B D G C E F H
中序遍历:D G B A E C H F
后序遍历:G D B E H F C A
===== 迭代遍历测试 =====
前序遍历:A B D G C E F H
中序遍历:D G B A E C H F
后序遍历:G D B E H F C A
层序遍历:A B C D E F G H
#ifndef ARRAYBINARYTREE_H_INCLUDED
#define ARRAYBINARYTREE_H_INCLUDED
#include<queue>
#include<stack>
//************************************************************************************************************************************************************************
//
// 类名:数组二叉树(ArrayBinaryTree)
//
// 概述:一个可复用的数组二叉树,存储结构采用数组实现,支持任意类型数据(模板泛型)。
//
// 说明:默认二叉树的最大容量为100
//
//************************************************************************************************************************************************************************
#define MAXSIZE 100 // 二叉树的最大容量
//========================================================================================================================================================================
//
// 数组二叉树结构体(ArrayBinaryTree)
//
//========================================================================================================================================================================
template <typename T>
struct ArrayBinaryTree{
// ------------------------------------------------------------------------------------------------------
// 私有成员
// 注:私有成员只能在该结构体内部访问调用,外部通过该结构体定义(实例化)的变量(对象)不能访问调用)
//------------------------------------------------------------------------------------------------------
private:
//---------------声明私有成员变量---------------
T tree[MAXSIZE]; // 存储二叉树数据,支持任意类型
int qty; // 用于跟踪二叉树中的元素数量
T valNull = T(); // 空节点标识(模板化)
//---------------声明私有成员函数---------------
//辅助函数
int findRootIndex(const T inTree[], int start, int end, const T rootVal); // 查找根节点在中序序列中的索引
int isInRange(const T val, const T inOrder[], int start, int end); // 检查元素是否在中序的指定范围内(用于筛选层序中的子树节点)
int filterLevelNodes(const T levelOrder[], int levelLen, const T inOrder[], int inStart, int inEnd, T subLevel[]);// 筛选层序数组中属于指定中序范围的节点,生成子层序数组
// ------------------------------------------------------------------------------------------------------
// 公共成员
// 注:公共成员能在该结构体内部访问调用,外部通过该结构体定义(实例化)的变量(对象)也能访问调用)
//------------------------------------------------------------------------------------------------------
public:
//---------------声明公共成员函数---------------
// 构造函数:外部通过该结构体定义(实例化)变量(对象)时,自动执行该函数(主要用于初始化成员变量的值)
ArrayBinaryTree() {
qty = 0;
for(int i=0;i < MAXSIZE;i++) { tree[i] = T(); }
}
//功能函数
void buildTreeByPre(const T preTree[], int preStart, int preEnd, const T inTree[], int inStart, int inEnd, int nodeNum, int treeSize);// 根据前序和中序列建立二叉树
void buildTreeByPost(const T postTree[], int postStart, int postEnd, const T inTree[], int inStart, int inEnd, int nodeNum, int treeSize);// 根据后序和中序列建立二叉树
void buildTreeByLevel(const T levelTree[], int levelLen, const T inTree[], int inStart, int inEnd, int nodeNum, int treeSize);// 根据层序和中序列建立二叉树
void deleteTree(int index);// 删除二叉树
//递归遍历
void preOrder(int index);// 前序遍历(根 → 左 → 右)
void inOrder(int index);// 中序遍历(左 → 根 → 右)→ 升序输出
void postOrder(int index);// 后序遍历(左 → 右 → 根)
//迭代遍历
void preOrderIter(int index);// 前序遍历(根 → 左 → 右)
void inOrderIter(int index);// 中序遍历(左 → 根 → 右)→ 升序输出
void postOrderIter(int index);// 后序遍历(左 → 右 → 根)
void levelOrderIter(int index);// 层序遍历(从上到下、从左到右)
// 析构函数:自动销毁队列,避免内存泄漏
~ArrayBinaryTree() {
}
};
#endif // ARRAYBINARYTREE_H_INCLUDED
#include <iostream>
#include "ArrayBinaryTree.h"
//---------------实现私有成员函数---------------
// 查找根节点在中序序列中的索引
template <typename T>
int ArrayBinaryTree<T>::findRootIndex(const T inTree[], int start, int end, const T rootVal) {
for (int i = start; i <= end; i++) {
if (inTree[i] == rootVal) {
return i;
}
}
return -1; // 未找到(理论上不会出现)
}
// 检查元素是否在中序的指定范围内(用于筛选层序中的子树节点)
template <typename T>
int ArrayBinaryTree<T>::isInRange(const T val, const T inOrder[], int start, int end) {
for (int i = start; i <= end; i++) {
if (inOrder[i] == val) {
return 1;
}
}
return 0;
}
// 筛选层序数组中属于指定中序范围的节点,生成子层序数组
template <typename T>
int ArrayBinaryTree<T>::filterLevelNodes(const T levelOrder[], int levelLen, const T inOrder[], int inStart, int inEnd, T subLevel[]) {
int idx = 0;
for (int i = 0; i < levelLen; i++) {
if (isInRange(levelOrder[i], inOrder, inStart, inEnd)) {
subLevel[idx++] = levelOrder[i];
}
}
return idx; // 返回子层序数组的长度
}
//---------------实现公共成员函数---------------
// 根据前序和中序列建立二叉树
// 参数:preTree-前序序列, preStart-前序起始索引, preEnd-前序结束索引
// inTree-中序序列, inStart-中序起始索引,inEnd-中序结束索引
// nodeNum-当前节点的编号(从1开始)
template <typename T>
void ArrayBinaryTree<T>::buildTreeByPre(const T preTree[], int preStart, int preEnd, const T inTree[], int inStart, int inEnd, int nodeNum, int treeSize) {
if (preStart > preEnd || inStart > inEnd) {
return;
}
qty = treeSize;//设置二叉树最大节点数
T rootVal = preTree[preStart]; // 1. 当前根节点值(前序子序列第一个元素)
tree[nodeNum] = rootVal; // 2. 将根节点存入数组对应位置
int rootIdx = findRootIndex(inTree, inStart, inEnd, rootVal);// 3. 找到根节点在中序序列中的索引
int leftSize = rootIdx - inStart; // 4. 计算左子树的节点个数
// 5. 递归构建左子树
// 前序左子树范围:[preStart+1, preStart+leftSize],
// 中序左子树范围:[inStart, rootIdx-1]
// 左子节点编号:nodeNum * 2
buildTreeByPre(preTree, preStart + 1, preStart + leftSize,
inTree, inStart, rootIdx - 1,
nodeNum * 2, treeSize);
// 6. 递归构建右子树
// 前序右子树范围:[preStart+leftSize+1, preEnd]
// 中序右子树范围:[rootIdx+1, inEnd]
// 右子节点编号:nodeNum * 2 + 1
buildTreeByPre(preTree, preStart + leftSize + 1, preEnd,
inTree, rootIdx + 1, inEnd,
nodeNum * 2 + 1, treeSize);
}
// 根据后序和中序列建立二叉树
// 参数:postTree-后序序列, postStart-后序起始索引, postEnd-后序结束索引
// inTree-中序序列, inStart-中序起始索引,inEnd-中序结束索引
// nodeNum-当前节点的编号(从1开始)
template <typename T>
void ArrayBinaryTree<T>::buildTreeByPost(const T postTree[], int postStart, int postEnd, const T inTree[], int inStart, int inEnd, int nodeNum, int treeSize) {
if (postStart > postEnd || inStart > inEnd) {
return;
}
qty = treeSize;//设置二叉树最大节点数
T rootVal = postTree[postEnd]; // 1. 后序序列最后一个元素是当前根节点
tree[nodeNum] = rootVal; // 2. 将根节点存入数组对应位置
int rootIdx = findRootIndex(inTree, inStart, inEnd, rootVal);// 3. 找到根节点在中序序列中的索引
int leftSize = rootIdx - inStart; // 4. 计算左子树的节点个数
// 5. 递归构建左子树
// 后序左子树范围:[postStart, postStart + leftSize - 1]
// 中序左子树范围:[inStart, rootIdx - 1]
// 左子节点编号:nodeNum * 2
buildTreeByPost(postTree, postStart, postStart + leftSize - 1,
inTree, inStart, rootIdx - 1,
nodeNum * 2, treeSize);
// 6. 递归构建右子树
// 后序右子树范围:[postStart + leftSize, postEnd - 1]
// 中序右子树范围:[rootIdx + 1, inEnd]
// 右子节点编号:nodeNum * 2 + 1
buildTreeByPost(postTree, postStart + leftSize, postEnd - 1,
inTree, rootIdx + 1, inEnd,
nodeNum * 2 + 1, treeSize);
}
// 根据层序和中序列建立二叉树
// 参数:levelTree-层序序列, levelLen-层序长度
// inTree-中序序列, inStart-中序起始索引,inEnd-中序结束索引
// nodeNum-当前节点的编号(从1开始)
template <typename T>
void ArrayBinaryTree<T>::buildTreeByLevel(const T levelTree[], int levelLen, const T inTree[], int inStart, int inEnd, int nodeNum, int treeSize) {
if (inStart > inEnd || levelLen <= 0) {
return;
}
qty = treeSize;//设置二叉树最大节点数
T rootVal = levelTree[0]; // 1. 层序序列第一个元素是当前根节点
tree[nodeNum] = rootVal; // 2. 将根节点存入数组对应位置
int rootIdx = findRootIndex(inTree, inStart, inEnd, rootVal);// 3. 找到根节点在中序序列中的索引
int leftSize = rootIdx - inStart; // 4. 计算左子树的节点个数
int rightSize = inEnd - rootIdx; // 5. 计算右子树的节点个数
// 6. 筛选层序数组中属于左、右子树的节点
T leftLevel[MAXSIZE], rightLevel[MAXSIZE];
int leftLevelLen = filterLevelNodes(levelTree, levelLen, inTree, inStart, rootIdx - 1, leftLevel);
int rightLevelLen = filterLevelNodes(levelTree, levelLen, inTree, rootIdx + 1, inEnd, rightLevel);
// 4. 递归构建左、右子树
buildTreeByLevel(leftLevel, leftLevelLen, inTree, inStart, rootIdx - 1, nodeNum * 2, treeSize);
buildTreeByLevel(rightLevel, rightLevelLen, inTree, rootIdx + 1, inEnd, nodeNum * 2 + 1, treeSize);
}
// 删除二叉树
template <typename T>
void ArrayBinaryTree<T>::deleteTree(int index)
{
if (index>qty) return;
tree[index] = T();
deleteTree(index*2);
deleteTree(index*2+1);
}
// 前序遍历(根 → 左 → 右)
template <typename T>
void ArrayBinaryTree<T>::preOrder(int index)
{
if (index >qty) return;
if(tree[index] != valNull)
std::cout<<tree[index]<<" ";
preOrder(index*2);
preOrder(index*2+1);
}
// 中序遍历(左 → 根 → 右)
template <typename T>
void ArrayBinaryTree<T>::inOrder(int index)
{
if (index >qty) return;
inOrder(index*2);
if(tree[index] != valNull)
std::cout<<tree[index]<<" ";
inOrder(index*2+1);
}
// 后序遍历(左 → 右 → 根)
template <typename T>
void ArrayBinaryTree<T>::postOrder(int index)
{
if (index >qty) return;
postOrder(index*2);
postOrder(index*2+1);
if(tree[index] != valNull)
std::cout<<tree[index]<<" ";
}
// 迭代---前序遍历(根 → 左 → 右)
template <typename T>
void ArrayBinaryTree<T>::preOrderIter(int index)
{
std::stack<int> stack1; // 存储节点编号
stack1.push(index); // 根节点入栈
while (stack1.size() >= 1) {
int currIndex = stack1.top();stack1.pop(); // 出栈
if (currIndex >= qty) {
continue;
}
if(tree[currIndex] != valNull)
std::cout<<tree[currIndex]<<" "; // 访问根
// 栈后进先出,先入栈右子树,后入栈左子树
int rightIndex = currIndex * 2 + 1;
if (rightIndex <= qty) {
stack1.push(rightIndex);
}
int leftIndex = currIndex * 2;
if (leftIndex <= qty) {
stack1.push(leftIndex);
}
}
}
// 迭代---中序遍历(左 → 根 → 右)
template <typename T>
void ArrayBinaryTree<T>::inOrderIter(int index)
{
std::stack<int> stack1; // 存储节点编号
int currIndex = index;
T valNull = T();
// 迭代终止条件:当前节点为空 且 栈为空
while (stack1.size()>=1 || (currIndex < qty && tree[currIndex] != valNull)) {
// 第一步:遍历到最左子节点,路径上的节点下标入栈
while( currIndex <= qty && tree[currIndex] != valNull ) {
stack1.push(currIndex);
currIndex = currIndex * 2; // 左子节点
}
// 第二步:弹出栈顶节点(最左节点)
currIndex = stack1.top();stack1.pop();
std::cout<<tree[currIndex]<<" ";
// 第三步:转向右子节点
currIndex = currIndex * 2 + 1;
}
}
// 迭代---后序遍历(左 → 右 → 根)
template <typename T>
void ArrayBinaryTree<T>::postOrderIter(int index)
{
std::stack<int> stack1, stack2;
stack1.push(index);
// 栈1:根→右→左;栈2:存储逆序结果
while (stack1.size() >= 1) {
int currIndex = stack1.top(); stack1.pop();
if (currIndex > qty) {
continue;
}
stack2.push(currIndex);
// 先入栈左子树,后入栈右子树(栈1出栈顺序相反)
int leftIndex = currIndex * 2;
if (leftIndex <= qty) {
stack1.push(leftIndex);
}
int rightIndex = currIndex * 2 + 1;
if (rightIndex <= qty) {
stack1.push(rightIndex);
}
}
// 输出栈2(后序遍历结果)
while(stack2.size() >= 1) {
int currIndex = stack2.top(); stack2.pop();
if(tree[currIndex] != valNull)
std::cout<<tree[currIndex]<<" ";
}
}
// 迭代---层序遍历(根 → 左 → 右)
template <typename T>
void ArrayBinaryTree<T>::levelOrderIter(int index)
{
if (index <= 0) return;
std::queue<int> queues;
queues.push(index); // 根节点入队
while (!queues.empty()) {
int curr = queues.front(); // 获取队头节点
queues.pop();// 出队头节点
if(tree[curr] != valNull)
std::cout<<tree[curr]<<" "; // 输出当前节点值
// 左子节点入队(先左后右)
if (curr <= qty) {
queues.push(curr*2);
}
// 右子节点入队
if (curr <= qty) {
queues.push(curr*2+1);
}
}
}
// 显式实例化常用类型(避免链接错误,可选)
template class ArrayBinaryTree<int>;
template class ArrayBinaryTree<float>;
template class ArrayBinaryTree<std::string>;
#include "ArrayBinaryTree.h"
#include <iostream>
#include <string>
int main() {
ArrayBinaryTree<char> tree1;
ArrayBinaryTree<char> tree2;
ArrayBinaryTree<char> tree3;
// 1. 根据前序和中序建立二叉树
printf("== 1.建立二叉树(根据前序和中序) ==\n");
printf(" A \n");
printf(" / \\ \n");
printf(" B C \n");
printf(" / \\ / \\ \n");
printf(" D E F \n");
printf(" / / \n");
printf(" G H \n");
char preTree1[] = {'A', 'B', 'D', 'E', 'G', 'C', 'F', 'H'};// 前序序列
char inTree1[] = {'D', 'B', 'G', 'E', 'A', 'C', 'H', 'F'};// 中序序列
int n1 = sizeof(preTree1) / sizeof(preTree1[0]); // 计算元素个数
int k1 = floor(log2(n1)) + 1;// 计算二叉树深度
int treeSize1 = pow(2, k1) - 1;// 计算二叉树最大节点数
tree1.buildTreeByPre(preTree1, 0, n1-1, inTree1, 0, n1-1, 1, treeSize1); //建立二叉树
printf("\n===== 递归遍历测试 =====\n");
printf("前序遍历:"); tree1.preOrder(1); printf("\n");
printf("中序遍历:"); tree1.inOrder(1); printf("\n");
printf("后序遍历:"); tree1.postOrder(1); printf("\n");
printf("===== 迭代遍历测试 =====\n");
printf("前序遍历:"); tree1.preOrderIter(1); printf("\n");
printf("中序遍历:"); tree1.inOrderIter(1); printf("\n");
printf("后序遍历:"); tree1.postOrderIter(1); printf("\n");
printf("层序遍历:"); tree1.levelOrderIter(1); printf("\n");
// 2. 根据后序和中序建立二叉树
printf("\n== 2.建立二叉树(根据后序和中序)==\n");
printf(" A \n");
printf(" / \\ \n");
printf(" B C \n");
printf(" \\ / \\ \n");
printf(" D E F\n");
printf(" \\ / \n");
printf(" G H \n");
char postTree2[] = {'G', 'D', 'B', 'E', 'H', 'F', 'C', 'A'};// 后序序列
char inTree2[] = {'D', 'G', 'B', 'A', 'E', 'C', 'H', 'F'};// 中序序列
int n2 = sizeof(postTree2) / sizeof(postTree2[0]); // 计算元素个数
int k2 = floor(log2(n2)) + 1;// 计算二叉树深度
int treeSize2 = pow(2, k2) - 1;// 计算二叉树最大节点数
tree2.buildTreeByPost(postTree2, 0, n2-1, inTree2, 0, n2-1, 1, treeSize2);//建立二叉树
printf("===== 递归遍历测试 =====\n");
printf("前序遍历:"); tree2.preOrder(1); printf("\n");
printf("中序遍历:"); tree2.inOrder(1); printf("\n");
printf("后序遍历:"); tree2.postOrder(1); printf("\n");
printf("===== 迭代遍历测试 =====\n");
printf("前序遍历:"); tree2.preOrderIter(1); printf("\n");
printf("中序遍历:"); tree2.inOrderIter(1); printf("\n");
printf("后序遍历:"); tree2.postOrderIter(1); printf("\n");
printf("层序遍历:"); tree2.levelOrderIter(1); printf("\n");
// 3. 根据层序和中序建立二叉树
printf("\n== 3.建立二叉树(根据层序和中序)==\n");
printf(" A \n");
printf(" / \\ \n");
printf(" B C \n");
printf(" \\ / \\ \n");
printf(" D E F\n");
printf(" \\ / \n");
printf(" G H \n");
char levelTree3[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'};// 层序序列
char inTree3[] = {'D', 'G', 'B', 'A', 'E', 'C', 'H', 'F'};// 中序序列
int n3 = sizeof(levelTree3) / sizeof(levelTree3[0]); // 计算元素个数
int k3 = floor(log2(n3)) + 1;// 计算二叉树深度
int treeSize3 = pow(2, k3) - 1;// 计算二叉树最大节点数
tree3.buildTreeByLevel(levelTree3, n3, inTree3, 0, n3-1, 1, treeSize3);//建立二叉树
printf("===== 递归遍历测试 =====\n");
printf("前序遍历:"); tree3.preOrder(1); printf("\n");
printf("中序遍历:"); tree3.inOrder(1); printf("\n");
printf("后序遍历:"); tree3.postOrder(1); printf("\n");
printf("===== 迭代遍历测试 =====\n");
printf("前序遍历:"); tree3.preOrderIter(1); printf("\n");
printf("中序遍历:"); tree3.inOrderIter(1); printf("\n");
printf("后序遍历:"); tree3.postOrderIter(1); printf("\n");
printf("层序遍历:"); tree3.levelOrderIter(1); printf("\n");
return 0;
}
模板类型约束:
</>/==运算符(内置类型如int/string默认支持,自定义类型需重载);<<运算符(遍历输出时使用)。通过理论学习+代码实践,就能真正掌握树与二叉树的核心逻辑,为后续学习更复杂的数据结构打下坚实基础!