栈实现二叉树先,中,后序遍历
如果是使用递归来实现二叉树的先,中,后序遍历只需要更改三行代码的位置,但若是使用栈来写那便会有趣得多
根结点与其左右子树间的输出优先级
graph TD
1((根结点))---2((左子树))
1---3((右子树))
遍历方式 | 输出优先级 |
---|---|
先序 | 根结点>左子树>右子树 |
中序 | 左子树>根结点>右子树 |
后序 | 左子树>右子树>根节点 |
使用栈的规则
栈内元素是指向结点的指针
- 只有栈顶元素指向的结点内容才会被输出
- 方便,不用记忆太多结点
- 结点内容输出后指向该结点的指针会立即出栈
- 避免结点重复输出
- 当一个指针出栈时,如果该指针指向的结点的左右子树有未被使用过(未遍历过,或者不为空),那么指向其左右子树的指针将随后入栈,且指向后输出的结点的指针早入栈
- 入栈是避免信息的丢失,顺序是栈的特性(先进后出)
- 栈中初始化只有一个指向根节点的指针
- 遍历往往以根节点作为基础参数
- 当栈为空时遍历结束
- 即遍历完所有结点(可以由2,3推导出来)
算法
无论是根节点还是左右子树在栈中的表示方式是一样的都是指针,所以我们可以将左右子树各当作是"一个结点",这样问题就简化为具有3个结点的完全二叉树的输出了
在简化的基础上再考虑上特殊情况规则就可以实现了
通用逻辑栈中初始化只有一个指向根节点的指针,当栈为空时遍历结束
先序遍历
推导
graph TD
1((根结点))---2((左子树))
1---3((右子树))
- 输出顺序为:根结点->左子树->右子树
- 结合规则说明
- 根结点优先输出,结合规则2可得出栈顶指针指向的结点直接输出,栈顶指针会立即被出栈(提前备份)
- 栈顶指针出栈,结合规则3可得出备份指向结点的左子树和右子树的指针会被入栈(非空情况下),且右子树先于左子树入栈
- 综上所述可得出规律
- 先输出栈顶指针指向的结点
- 栈顶指针出栈(栈顶指针备份,否则左右子树信息丢失)
- 备份指针指向结点的右子树入栈(如果右子树指针不为空)
- 备份指针指向结点的左子树入栈(如果左子树指针不为空)
- 回到步骤1
C++代码实现
void preorder_travel() {
if (root == NULL)
return;
std::stack<Node*> s;
s.push(root);//根指针入栈
while (!s.empty())
{
std::cout << s.top()->data << " ";//输出栈顶指针指向的结点
Node* temp = s.top();//备份栈顶指针
s.pop();//栈顶指针出栈
if (temp->R != NULL) s.push(temp->R);//备份指向结点右子树不为空则将其压入栈
if (temp->L != NULL) s.push(temp->L);//备份指向结点左子树不为空则将其压入栈
}
}
中序遍历
推导
graph TD
1((根结点))---2((左子树))
1---3((右子树))
- 输出顺序为:左子树->根结点->右子树
- 结合规则说明
- 左子树优先输出,其次是根节点,但子树不可能直接输出,所以一直搜索栈顶指针指向结点的左子树的左子树的...,直到没有找到没有左子树的结点,然后输出该结点(作为根节点),结合规则2可得出重复压入栈顶指向结点的左子树指针,直到栈顶指向结点的左子树指针为空,然后输出该结点,随后栈顶指针出栈(提前备份)
- 由步骤1可得出栈顶指针总是次栈顶指针指向结点的左子树,且左子树(原栈顶指针)输出之后到根结点(现栈顶指针)指向结点输出,结合规则2可得出重复输出栈顶指针指向的结点(输出之前备份,输出之后出栈)
- 输出栈顶指针,结合规则3可得出若是备份指向结点的右子树存在,则将其入栈
- 右子树入栈打破2中的前提(栈顶指针总是次栈顶指针指向结点的左子树),所以2停止
- 输出栈顶指针,结合规则3可得出若是备份指向结点的右子树存在,则将其入栈
- 回到到步骤1
- 综上所述可得出规律
- 重复压入栈顶结点左子树指针,直到栈顶结点左子树指针为空,然后输出该结点,栈顶指针出栈(栈顶指针备份,否则左右子树信息丢失)
- 重复输出栈顶指针指向的结点(输出之前备份,输出之后出栈),直到备份结点右子树不为空,然后将其右子树入栈,回到1
C++代码实现
void inorder_travel() {
if (root == NULL)
return;
std::stack<Node*> s;
s.push(root);//根指针入栈
while (!s.empty())
{
while (s.top()->L != NULL)//重复将栈顶指针指向结点的左子树压栈,直到栈顶指针指向结点的左子树为空
{
s.push(s.top()->L);
}
while (!s.empty())//重复检查
{
std::cout << s.top->data << " ";//输出栈顶指针指向的结点
Node* temp = s.top();//备份栈顶指针
s.pop();//栈顶指针出栈
if (temp->R != NULL) {//如果栈顶指针指向的结点右子树不为空,则将其右子树入栈,退出检查
s.push(temp->R);
break;
}
}
}
}
后序遍历
推导
graph TD
1((栈顶结点))---2((左子树))
1---3((右子树))
- 输出顺序为:左子树->右子树->栈顶结点
- 结合规则说明
- 左子树优先输出,其次是右子树,但子树不可能直接输出,所以一直搜索栈顶指针指向结点的左子树的左子树的...,直到没有找到没有左子树的结点,结合规则2可得出重复压入栈顶指向结点的左子树指针,直到栈顶指向结点的左子树指针为空
- 由步骤1可得出栈顶指针总是次栈顶指针指向结点的左子树,且左子树(原栈顶指针)输出之后到右子树(现栈顶指针指向结点的右子树)输出
- 在步骤2前提下反复检查
- 若是栈顶指针指向结点的右子树遍历过(使用一个last来标记上一次输出的结点指针),输出栈顶指针,栈顶出栈
- 若是栈顶指针指向结点的右子树为空,输出栈顶指针,栈顶出栈
- 若是栈顶指针指向结点的右子树不为空,由输出顺序可得出右子树入栈
- 右子树入栈打破2中的前提(栈顶指针总是次栈顶指针指向结点的左子树),所以2停止
- 在步骤2前提下反复检查
- 回到到步骤1
- 综上所述可得出规律
- 重复压入栈顶结点左子树指针,直到栈顶指针指向结点的左子树为空
- 重复输判断如果栈顶指针指向结点右子树遍历过或者为空,则栈顶出栈.否则将栈顶指针指向结点右子树入栈
- 回到步骤1
C++代码实现
void postorder_travel() {
if (root == NULL)
return;
std::stack<Node*> s;
s.push(root);//根指针入栈
while (!s.empty())
{
while (s.top()->L != NULL)//重复将栈顶指针指向结点的左子树压栈,直到栈顶指针指向结点的左子树为空
{
s.push(s.top()->L);
}
Node* last = NULL;//上一次遍历过的指针
while (!s.empty())//重复检查
{
if (s.top()->R==NULL||last==s.top()->R) {//如果栈顶指针指向结点的右子树为空或者遍历过
std::cout << s.top()->data << " ";//输出栈顶指向的结点
last = s.top();//更新指针last
s.pop();//栈顶指针出栈
}
else if(s.top()->R!=NULL)//如果栈顶指针指向的结点的右子树不为空
{
s.push(s.top()->R);//将右子树入栈
break;//退出检查
}
}
}
}
整体代码
#include <iostream>
#include <stack>
template <typename T>
class BST {
public:
BST() :root(NULL) {};
~BST() {};
void insert(T data) {
Node* temp = new Node();
temp->L = NULL;
temp->R = NULL;
temp->data = data;
if (root == NULL) {
root = temp;
}
else {
Node* tracer = root;
while (true)
{
if (tracer->data >= data)
if (tracer->L == NULL) {
tracer->L = temp;
break;
}
else
tracer = tracer->L;
else
if (tracer->R == NULL) {
tracer->R = temp;
break;
}
else
tracer = tracer->R;
}
}
}
void preorder_travel() {
if (root == NULL)
return;
std::stack<Node*> s;
s.push(root);//根指针入栈
while (!s.empty())
{
std::cout << s.top()->data << " ";//输出栈顶指针指向的结点
Node* temp = s.top();//备份栈顶指针
s.pop();//栈顶指针出栈
if (temp->R != NULL) s.push(temp->R);//备份指向结点右子树不为空则将其压入栈
if (temp->L != NULL) s.push(temp->L);//备份指向结点左子树不为空则将其压入栈
}
}
void inorder_travel() {
if (root == NULL)
return;
std::stack<Node*> s;
s.push(root);//根指针入栈
while (!s.empty())
{
while (s.top()->L != NULL)//重复将栈顶指针指向结点的左子树压栈,直到栈顶指针指向结点的左子树为空
{
s.push(s.top()->L);
}
while (!s.empty())//重复检查
{
std::cout << s.top->data << " ";//输出栈顶指针指向的结点
Node* temp = s.top();//备份栈顶指针
s.pop();//栈顶指针出栈
if (temp->R != NULL) {//如果栈顶指针指向的结点右子树不为空,则将其右子树入栈,退出检查
s.push(temp->R);
break;
}
}
}
}
void postorder_travel() {
if (root == NULL)
return;
std::stack<Node*> s;
s.push(root);//根指针入栈
while (!s.empty())
{
while (s.top()->L != NULL)//重复将栈顶指针指向结点的左子树压栈,直到栈顶指针指向结点的左子树为空
{
s.push(s.top()->L);
}
Node* last = NULL;//上一次遍历过的指针
while (!s.empty())//重复检查
{
if (s.top()->R==NULL||last==s.top()->R) {//如果栈顶指针指向结点的右子树为空或者遍历过
std::cout << s.top()->data << " ";//输出栈顶指向的结点
last = s.top();//更新指针last
s.pop();//栈顶指针出栈
}
else if(s.top()->R!=NULL)//如果栈顶指针指向的结点的右子树不为空
{
s.push(s.top()->R);//将右子树入栈
break;//退出检查
}
}
}
}
private:
struct Node
{
Node* L;
Node* R;
T data;
};
Node* root;
};
int main(){
BST<int> b;
b.insert(2);
b.insert(4);
b.insert(1);
b.insert(5);
b.insert(3);
b.insert(0);
b.preorder_travel();
std::cout << std::endl;
b.inorder_travel();
std::cout << std::endl;
b.postorder_travel();
}
原文地址:https://www.cnblogs.com/redo19990701/p/11302448.html
时间: 2024-10-07 05:55:08