目录
一、前言
二叉树由节点(Node)构成,每个节点都有一个值(value)和至多两个指向子节点的指针(left 和 right)。一个节点的子节点可以是叶节点(leaf node),也可以是一个包含多个子节点的二叉树。
二叉树有多种不同的遍历方法,比如先序遍历(preorder traversal)、中序遍历(inorder traversal)和后序遍历(postorder traversal)。每种遍历方法都有不同的应用场景。例如,先序遍历通常用于复制一棵二叉树,中序遍历通常用于排序,后序遍历通常用于计算一棵二叉树的高度。
二、二叉树的创建
通过二叉树序列的信息可以创建二叉树,但是知道什么信息才能创建二叉树呢,详细内容参见另一篇文章(扩展二叉树的中序序列创建二叉树?_木头linux的博客-CSDN博客),这里做个总结:
🍉 前序序列 + 中序序列;
🍉 后序序列 + 中序序列;
🍉 扩展前序序列;
🍉 扩展后序序列。
以上四种,知其一种即可创建二叉树,此处我们选择第三种方法,根据扩展前序序列创建二叉树,代码如下:
void pre_create_bitree (BiTree &bitree, vector<element_type>input)
{
if(current_element == input.size())return;
if(input[current_element] == '#')
{
cout<< "#"<<" ";
bitree = NULL;
current_element++;
return;
}
else
{
bitree = new BiNode;
bitree->data = input[current_element++];
cout << input[current_element - 1]<< " ";
pre_create_bitree(bitree->lchild, input);
pre_create_bitree(bitree->rchild, input);
}
}
void create_bitree (BiTree &bitree)
{
vector<element_type> input_elem = {'1', '2', '4', '#', '#', '5', '#', '#', '3', '6', '#', '#', '7', '#', '#'};;
pre_create_bitree(bitree, input_elem);
}
三、二叉树的遍历
二叉树遍历指的是按照一定的顺序访问二叉树中的所有节点。通常情况下,我们可以使用三种不同的方法来遍历二叉树:
🍊 前序遍历:首先访问当前结点,在访问左子树,最后访问右子树。
🍊 中序遍历:先访问当前结点左子树,再访问该结点,最后访问该结点的右子树。
🍊 后序遍历:先访问当前结点左子树,再访问右子树,完成该结点左右子树访问后,再访问该节点。
1. 前序遍历
🍅 递归遍历 🍅
在该代码中,如果当前节点不为空,则先访问当前节点,然后递归地访问左子树,再递归地访问右子树。这样的遍历顺序为:根节点 -> 左子树 -> 右子树,即先序遍历。该代码的流程如下:
- 判断当前节点是否为空,如果为空则返回。
- 访问当前节点。
- 递归地访问当前节点的左子树。
- 递归地访问当前节点的右子树。
- 返回。
// 先序遍历(递归)
void pre_order_traverse(BiTree bitree)
{
if(bitree)
{
cout << bitree->data<< " ";// 访问根节点
pre_order_traverse(bitree->lchild);// 访问左子树
pre_order_traverse(bitree->rchild);// 访问右子树
}
// return;// 可以省略
}
🍅 非递归遍历 🍅
在该代码中,使用了栈的数据结构来模拟递归的过程。这样的遍历顺序为:根节点 -> 左子树 -> 右子树,即先序遍历。该代码的流程如下:
- 创建一个栈,将二叉树的根节点入栈。
- 只要当前节点不为空或者栈不为空,则循环。
- 如果当前节点不为空,则访问当前节点,然后将当前节点的左子树入栈,并将当前节点更新为其左子树。
- 如果当前节点为空,则取出栈顶元素,并更新当前节点为其右子树。
- 重复上述步骤。
// 先序遍历(非递归)
void pre_order_traverse_none(BiTree bitree)
{
// 思路:先序顺序为:根节点->左子树->右子树
// 当遍历完左子树之后找不到右子树
// 解决方法:记住根节点
stack<BiTree> stk;
BiTree p = bitree;
// 当前节点不为空,或者栈不为空
while(p || !stk.empty())
{
if(p)// 根节点不为空
{
cout << p->data<< ",";
stk.push(p);// 根指针入栈
p = p->lchild;// 遍历左子树
}
else// 栈不为空
{
p = stk.top();// 获取根节点
stk.pop();// 弹出根节点
p = p->rchild;// 访问右子树
}
}
}
2.中序遍历
🍓 递归遍历 🍓
在该代码中,如果当前节点不为空,则先递归地访问左子树,再访问当前节点,最后递归地访问右子树。这样的遍历顺序为:左子树 -> 根节点 -> 右子树,即中序遍历。该代码的流程如下:
- 判断当前节点是否为空,如果为空则返回。
- 递归地访问当前节点的左子树。
- 访问当前节点。
- 递归地访问当前节点的右子树。
- 返回。
// 中序遍历二叉树(递归)
void in_order_traverse(BiTree &bitree)
{
if(bitree)
{
in_order_traverse(bitree->lchild);
cout << bitree->data<<" ";
in_order_traverse(bitree->rchild);
}
}
前序遍历与中序遍历递归实现方法的代码类似之处:
- 递归函数的结构相似,都是判断当前节点是否为空,如果为空则返回。
- 递归函数的实现方法相似,都是先递归地访问左子树,再访问当前节点,最后递归地访问右子树。
不同之处,前序遍历在遍历到一个节点之前,会先访问该节点,而中序遍历在遍历到一个节点之时,会访问该节点。因此,前序遍历与中序遍历在访问节点的顺序上有所区别。
🍓 非递归遍历 🍓
用这种方法遍历一棵树,会按照中序遍历的顺序访问每个节点,也就是左子树->根节点->右子树。具体实现过程是这样的:
- 首先检查当前节点是否为空,如果为空,就从栈中弹出一个节点,并访问它。如果不为空,就将当前节点压入栈中,然后将当前节点更新为它的左子节点,再次检查当前节点是否为空。这样可以保证在遍历完左子树之后,能够找到根节点。
- 当遍历完左子树之后,如果栈不为空,就从栈中弹出一个节点,并访问它(此时的节点就是根节点)。然后将当前节点更新为它的右子节点,再次检查当前节点是否为空。
- 如果当前节点为空并且栈为空,就说明遍历结束。
// 中序遍历二叉树(非递归)
void in_order_traverse_none(BiTree &bitree)
{
// 思路:中序顺序为:左子树->根节点->右子树
// 当遍历完左子树之后找不到根节点
// 解决方法:记住根节点
BiTree p = bitree;
stack<BiTree> stk;
while(p || !stk.empty())
{
if(p)// 如果根节点非空,压栈,记录根节点
{
stk.push(p);
p = p->lchild;// 首先遍历左子树
}
else// 栈非空,弹出根节点
{
p = stk.top();
cout << p->data<<" " ;
stk.pop();
p = p->rchild;// 访问右子树
}
}
}
对于前序遍历和中序遍历的非递归实现,最大的区别在于,对于当前节点,前序遍历是先访问当前节点,然后遍历左子树和右子树,而中序遍历是先遍历左子树,然后访问当前节点,最后遍历右子树。
3.后序遍历
🍈 递归遍历 🍈
与前两种无明显区别,不再详细叙述。
void post_order_traverse(BiTree &bitree)
{
if(bitree)
{
post_order_traverse(bitree->lchild);
post_order_traverse(bitree->rchild);
cout << bitree->data<< " ";
}
}
🍈 非递归遍历 🍈
与前序遍历和中序遍历的非递归算法相比,后序遍历非递归算法的实现更为复杂。
- 首先,它需要维护一个 last_visit,用来存储已经被访问过的节点;
- 其次,当遍历到一个节点时,需要先判断它的左右子树是否都已经被访问过,如果都被访问过了,就可以访问这个节点,否则,需要先将它的左右子树压入栈中,
- 然后再继续遍历。
因此,后序遍历非递归算法的实现比前序遍历和中序遍历的非递归算法要复杂。
代码如下所示,在执行这段代码时,会按照如下步骤进行:
- 将根节点压入栈中,并访问根节点的左子树,即访问左子节点。
- 如果左子节点不为空,将左子节点压入栈中,并访问左子节点的左子树。
- 如果左子节点的左子树为空,取出栈顶元素,判断栈顶元素的右子树是否为空。
- 如果栈顶元素的右子树不为空,且没有被访问过,将右子树压入栈中,并访问右子树。
- 如果右子树为空,或者右子树已经被访问过了,则访问栈顶元素,并将栈顶元素出栈。
- 回到步骤3,继续遍历。
这样,在遍历完整棵树之后,就可以按照“左子树->右子树->根节点”的顺序访问每个节点。
// 与前序遍历和中序遍历不同
// 前序和中序遍历根节点和左孩子都是相邻输出的
// 后序遍历需要先访问右子树,最后输出根节点
void post_order_traverse_none(BiTree &bitree)
{
// 将每一个非#元素看作根节点遍历一遍
stack<BiTree> stk;
BiTree cur = bitree;
BiTree last_vist = NULL;// 用来区分返回根节点时,是从左子树返回的,还是右子树返回的
BiTree top = NULL;
while(cur || !stk.empty())
{
if(cur)cout<< cur->data<<endl;
else cout<<"cur NULL"<<endl;
if(cur)// 如果cur不为空,将其压入栈中,并访问左子树
{
stk.push(cur);
cur = cur->lchild;
}
else// 如果当前结点cur为空,取出栈顶元素
{
top = stk.top();// 取出栈顶元素
if(top->rchild && last_vist != top->rchild)// 如果栈顶结点的右子节点不为空,且没有被访问过
{
cur = top->rchild;// 如果cur指向的左子树为空,从栈顶取出根节点,将cur指向其右子树
}
else// 右子节点为空,且从右子树返回(一定是右子树返回,无论右子树是否为空)
{
// 否则,访问栈顶元素
cout<< top->data<< "输出"<<endl;
// 将栈顶元素设为最后访问的元素
last_vist = top;
// 将栈顶元素出栈
stk.pop();
}
}
}
}
四、完成代码
1. 前序创建,前序递归、非递归遍历
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
#define element_type char
// 二叉树节点结构体
typedef struct BitNode
{
element_type data;// 数据域
BitNode *lchild, *rchild;// 左右孩子
}BitNode, *BiTree;
int current_num = 0;// 构建二叉树时用于递归计数
// 创建二叉树
void create_bitree(BiTree &bitree);// 注意这里必须要使用引用传值,或者指针传值,形参不可以
// 前序遍历输出二叉树(递归)
void pre_order_traverse(BiTree bitree);
// 前序遍历输出二叉树(非递归)
// 使用栈的原因是:通过根节点可以找到左子树,但是遍历完成左子树找不到右子树
void pre_order_traverse_none(BiTree BiTree);
// 主函数
int main()
{
BiTree bi_tree;
// 创建二叉树
create_bitree(bi_tree);// 如果不是引用传值,这里的bi_tree在函数运行完之后仍然是空
cout << "二叉树已创建"<<endl;
// 先序遍历二叉树(递归)
cout << "线序遍历二叉树(递归)"<<endl;
pre_order_traverse(bi_tree);
cout <<endl;
// 先序遍历二叉树(非递归)
cout << "先序遍历二叉树(非递归)"<<endl;
pre_order_traverse_none(bi_tree);
return 0;
}
// 先序遍历构建二叉树(递归)
// 这里同样需要引用或指针传值
void pre_create_bitree(BiTree &bitree, vector<element_type> input_element)
{
if(current_num == input_element.size())return;
if(input_element[current_num] == '#')
{
bitree = NULL;
current_num++;
return;
}
else
{
bitree = new BitNode;// 首先申请空间
bitree->data = input_element[current_num++];// 数值域赋值
pre_create_bitree(bitree->lchild, input_element);// 递归左子树
pre_create_bitree(bitree->rchild, input_element);// 递归右子树
}
}
// 创建二叉树
void create_bitree(BiTree &bitree)
{
// 先序序列
// 补充空节点之后,按照先序序列建立的二叉树就是唯一的
vector<element_type> input = {'1', '2', '4', '#', '#', '5', '#', '#', '3', '6', '#', '#', '7', '#', '#'};
pre_create_bitree(bitree, input);// 如果不是引用传值,这里的bitree在运行完函数之后仍然是空
}
// 先序遍历(递归)
void pre_order_traverse(BiTree bitree)
{
if(bitree)
{
cout << bitree->data<< " ";// 访问根节点
pre_order_traverse(bitree->lchild);// 访问左子树
pre_order_traverse(bitree->rchild);// 访问右子树
}
// return;// 可以省略
}
// 先序遍历(非递归)
// 参考链接:https://www.cnblogs.com/zhulmz/p/11861058.html
void pre_order_traverse_none(BiTree bitree)
{
// 思路:先序顺序为:根节点->左子树->右子树
// 当遍历完左子树之后找不到右子树
// 解决方法:记住根节点
stack<BiTree> stk;
BiTree p = bitree;
// 当前节点不为空,或者栈不为空
while(p || !stk.empty())
{
if(p)// 根节点不为空
{
cout << p->data<< ",";
stk.push(p);// 根指针入栈
p = p->lchild;// 遍历左子树
}
else// 栈不为空
{
p = stk.top();// 获取根节点
stk.pop();// 弹出根节点
p = p->rchild;// 访问右子树
}
}
}
前序迭代遍历,添加了详细的注释,摘自我的力扣题目答案:
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
if(root == NULL)return res;
TreeNode* p = root;
stack<TreeNode*> stk;// 记录需要回溯的父节点
while(p || !stk.empty())// 这里的p理解为当前节点
{
// 当前节点不为空,访问当前节点并记录当前节点,然后访问左子树
// 这里体现出根->左
if(p != NULL)
{
res.emplace_back(p->val);// 先访问当前节点
stk.push(p);// 因为下面就访问左子树,因此记录当前节点为父节点,便于回溯
p = p->left;// 访问左子树
}
else
{
// 当前节点为空,从栈中取出父节点作为当前节点,并访问当前节点的右子树。
//因为从栈中拿出来的父节点作为当前节点一定是被访问过的,所以直接访问右字数即可。这里体现出根->左完成之后的右,因为从栈中取出,一定完成了根->左这个过程
p = stk.top();
stk.pop();
p = p->right;
}
}
return res;
}
};
2. 前序创建,中序递归、非递归遍历
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
#define elemtype char
// 二叉树节点
typedef struct BiNode
{
elemtype data;
BiNode *lchild, *rchild;
}BiNode, *BiTree;
int current_element = 0;// 当前元素下标
// 中序构建二叉树
void create_bitree(BiTree &bitree);
// 中序遍历二叉树(递归)
void in_order_traverse(BiTree &bitree);
// 中序遍历二叉树(非递归)
void in_order_traverse_none(BiTree &bitree);
int main()
{
BiTree bi_tree;
create_bitree(bi_tree);
cout <<"二叉树构建完成!"<<endl;
cout <<endl;
cout <<"中序遍历二叉树(递归)"<<endl;
in_order_traverse(bi_tree);
cout << endl;
cout <<"中序遍历二叉树(非递归)"<<endl;
in_order_traverse_none(bi_tree);
return 0;
}
// 先序遍历递归方式创建二叉树
void pre_create_bitree(BiTree &bitree, vector<elemtype> input_element)
{
if(current_element == input_element.size())return;
if(input_element[current_element] == '#')
{
bitree = NULL;
current_element++;
cout << "#"<< " ";
return;
}
else
{
bitree = new BiNode;
bitree->data = input_element[current_element++];// 根节点赋值
cout <<input_element[current_element-1]<< " ";
pre_create_bitree(bitree->lchild, input_element);// 左子树构建
pre_create_bitree(bitree->rchild, input_element);// 右子树构建
}
}
// 创建二叉树
void create_bitree(BiTree &bitree)
{
vector<elemtype> input = {'1', '2', '4', '#', '#', '5', '#', '#', '3', '6', '#', '#', '7', '#', '#'};
pre_create_bitree(bitree, input);
}
// 中序遍历二叉树(递归)
void in_order_traverse(BiTree &bitree)
{
if(bitree)
{
in_order_traverse(bitree->lchild);
cout << bitree->data<<" ";
in_order_traverse(bitree->rchild);
}
}
// 中序遍历二叉树(非递归)
void in_order_traverse_none(BiTree &bitree)
{
// 思路:中序顺序为:左子树->根节点->右子树
// 当遍历完左子树之后找不到根节点
// 解决方法:记住根节点
BiTree p = bitree;
stack<BiTree> stk;
while(p || !stk.empty())
{
if(p)// 如果根节点非空,压栈,记录根节点
{
stk.push(p);
p = p->lchild;// 首先遍历左子树
}
else// 栈非空,弹出根节点
{
p = stk.top();
cout << p->data<<" " ;
stk.pop();
p = p->rchild;// 访问右子树
}
}
}
3. 前序创建,后序递归、非递归遍历
#include <iostream>
#include <stack>
#include <vector>
using namespace std;
#define element_type char
// 二叉树结点
typedef struct BiNode
{
element_type data;
BiNode *lchild, *rchild;
}BiNode, *BiTree;
int current_element = 0;// 当前元素计数
void create_bitree(BiTree &bitree);// 创建二叉树
void post_order_traverse(BiTree &bitree);// 后序遍历二叉树(递归)
void post_order_traverse_none(BiTree &bitree);// 后序遍历二叉树(非递归)
int main()
{
BiTree bi_tree;
// 创建二叉树
create_bitree(bi_tree);
cout<<endl;
// 后序遍历二叉树(递归)
post_order_traverse(bi_tree);
cout<<endl;
// 后序遍历二叉树(非递归)
post_order_traverse_none(bi_tree);
cout<< endl;
return 0;
}
void pre_create_bitree (BiTree &bitree, vector<element_type>input)
{
if(current_element == input.size())return;
if(input[current_element] == '#')
{
cout<< "#"<<" ";
bitree = NULL;
current_element++;
return;
}
else
{
bitree = new BiNode;
bitree->data = input[current_element++];
cout << input[current_element - 1]<< " ";
pre_create_bitree(bitree->lchild, input);
pre_create_bitree(bitree->rchild, input);
}
}
void create_bitree (BiTree &bitree)
{
vector<element_type> input_elem = {'1', '2', '4', '#', '#', '5', '#', '#', '3', '6', '#', '#', '7', '#', '#'};;
pre_create_bitree(bitree, input_elem);
}
void post_order_traverse(BiTree &bitree)
{
if(bitree)
{
post_order_traverse(bitree->lchild);
post_order_traverse(bitree->rchild);
cout << bitree->data<< " ";
}
}
// 与前序遍历和中序遍历不同
// 前序和中序遍历根节点和左孩子都是相邻输出的
// 后序遍历需要先访问右子树,最后输出根节点
void post_order_traverse_none(BiTree &bitree)
{
// 将每一个非#元素看作根节点遍历一遍
stack<BiTree> stk;
BiTree cur = bitree;
BiTree last_vist = NULL;// 用来区分返回根节点时,是从左子树返回的,还是右子树返回的
BiTree top = NULL;
while(cur || !stk.empty())
{
if(cur)cout<< cur->data<<endl;
else cout<<"cur NULL"<<endl;
if(cur)// 如果cur不为空,将其压入栈中,并访问左子树
{
stk.push(cur);
cur = cur->lchild;
}
else// 如果当前结点cur为空,取出栈顶元素
{
top = stk.top();// 取出栈顶元素
if(top->rchild && last_vist != top->rchild)// 如果栈顶结点的右子节点不为空,且没有被访问过
{
cur = top->rchild;// 如果cur指向的左子树为空,从栈顶取出根节点,将cur指向其右子树
}
else// 右子节点为空,且从右子树返回(一定是右子树返回,无论右子树是否为空)
{
// 否则,访问栈顶元素
cout<< top->data<< "输出"<<endl;
// 将栈顶元素设为最后访问的元素
last_vist = top;
// 将栈顶元素出栈
stk.pop();
}
}
}
}
4.层次遍历
#include <iostream>
#include <queue>// 使用队列
using namespace std;
#define elem_type char
// 二叉树结点创建
typedef struct BiNode
{
elem_type data;
BiNode *lchild, *rchild;
}BiNode, *BiTree;
int current_elem = 0;// 当前遍历元素
void create_bitree(BiTree &bitree);// 前序遍历创建二叉树
void level_order_traverse(BiTree &bitree);// 层次遍历二叉树
// 二叉树层次遍历:
// 对于一棵二叉树,从根节点开始,从上到下,从左到右的顺序访问每一个结点
// 思路:
// 将根节点入队;
// 队不为空则进行循环:
// 0.出列一个结点,打印输出
// 1.如果有左孩子,将左孩子入队
// 2.如果有有孩子,将右孩子入队
int main()
{
BiTree bitree;
// 创建二叉树
create_bitree(bitree);
// 层次遍历二叉树
cout<<"层次遍历"<<endl;
level_order_traverse(bitree);
return 0;
}
void pre_create_bitree(BiTree &bitree, vector<elem_type> input)
{
if(current_elem == input.size())return;
if(input[current_elem] == '#')
{
cout<< "#"<<endl;
bitree = NULL;
current_elem++;
return;
}
else
{
bitree = new BiNode;
bitree->data = input[current_elem++];
cout<< input[current_elem - 1]<<endl;
pre_create_bitree(bitree->lchild, input);
pre_create_bitree(bitree->rchild, input);
}
}
void create_bitree(BiTree &bitree)
{
// 先序序列
// 补充空节点之后,按照先序序列建立的二叉树就是唯一的
vector<elem_type> input = {'1', '2', '4', '#', '#', '5', '#', '#', '3', '6', '#', '#', '7', '#', '#'};
pre_create_bitree(bitree, input);// 如果不是引用传值,这里的bitree在运行完函数之后仍然是空
}
void level_order_traverse(BiTree &bitree)
{
BiTree p = bitree;// 获取队列头部元素临时变量
queue<BiTree> que;// 定义存储队列
que.push(p);// 根节点入队
while(!que.empty())
{
p = que.front();// 获取队列头部元素
// 因为下面判断了,压栈的内容p不会为空,这里可以无需判断
cout<< p->data<< " ";// 打印队列头部元素
que.pop();// 出队
if(p->lchild != NULL)que.push(p->lchild);
if(p->rchild != NULL)que.push(p->rchild);
}
}
五、加入圈子
🍎欢迎大家加入组织,一起交流、学习,群内多为嵌入式爱好者,也有企业MCU、Linux大佬carry。
🍎 球球群:【嵌入式c、c++、单片机、linux技术交流2群】🍎左侧专栏或下方群名片。
2万+

被折叠的 条评论
为什么被折叠?



