二叉树前中后序遍历的非递归实现以及层次遍历、zig-zag型遍历详解

本文详细介绍了如何使用非递归方式实现二叉树的前序、中序、后序遍历以及层次遍历。对于每种遍历,不仅给出了递归实现,还重点解析了非递归实现的思路,利用栈或队列等数据结构完成。此外,还涉及到了Zig-Zag遍历,增加了遍历的多样性。代码示例清晰易懂,适合学习二叉树遍历的读者参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言
二叉树的遍历是一个比较常见的问题,递归实现二叉树的前中后序遍历比较简单,但非递归实现二叉树的前中后序遍历相对有难度。这篇博客将详述如何使用非递归的方式实现二叉树的前中后序遍历,在进行理论描述的同时会附上递归实现以及非递归实现的代码。此外,本文还将描述二叉树的层次遍历以及zig-zag型遍历。

一、前序遍历
1、概念
对于一棵二叉树,前序遍历将先遍历根节点,再遍历左子树(如果存在的话),最后遍历右子树(如果存在的话)。其中左右子树又分别是一棵二叉树,因此,它们的遍历也是采用根节点->左子树->右子树的顺序。

2、递归实现
递归实现前序遍历比较简单,直接按照概念来即可,需要注意的是,遍历前要对二叉树的根节点判断是否为空。

struct treeNode {
    treeNode *left, *right;
    int val;
    treeNode(int a=0):left(nullptr), right(nullptr), val(a){};
};
 
void preOrderRecursively(const treeNode *root) {
    if(root) {
        cout << root->val << " ";
        preOrderRecursively(root->left);
        preOrderRecursively(root->right);
    }
}
3、非递归实现
非递归实现前序遍历也相对简单,使用栈这样一个数据结构即可实现,栈顶元素即为每次访问的元素。首先将非空的根节点入栈,其次访问根节点并将其出栈。由于前序遍历需要先访问根节点的左子树,再访问根节点的右子树,而根据栈先进后出的特性,接下来我们需要将根节点的右孩子先入栈(如果存在右孩子的话),再将根节点的左孩子入栈(如果存在左孩子的话)。接下来继续访问栈顶元素并将其出栈,再将其右孩子(如果存在的话)、左孩子(如果存在的话)分别入栈。重复这样一个过程,直到栈中没有元素。源码如下:

void preOrder(const treeNode *root) {
    if(!root) {
        return;
    }
    stack<const treeNode*> s;
    const treeNode *t=nullptr;
    s.push(root);
    while(!s.empty()) {
        t=s.top();
        s.pop();
        cout << t->val << " ";
        if(t->right) {
            s.push(t->right);
        }
        if(t->left) {
            s.push(t->left);
        }
    }
    cout << endl;
}
二、中序遍历
1、概念
中序遍历是指先访问二叉树的左子树,再访问二叉树的根节点,最后访问二叉树的右子树。对于左右子树这两个二叉树来说,访问同样遵循左子树->根节点->右子树的顺序。

2、递归实现
中序遍历的递归实现也很简单,直接按照概念来。需要注意的是,一开始同样需要判断根节点是否为空。

void inOrderRecursively(const treeNode *root) {
    if(root) {
        inOrderRecursively(root->left);
        cout << root->val << " ";
        inOrderRecursively(root->right);
    }
}
3、非递归实现
中序遍历的非递归实现稍微复杂一点,同样需要借助栈这个数据结构来实现。首先我们回顾一下中序遍历的概念,对于一个二叉树来说,中序遍历先访问二叉树的左子树,再访问二叉树的根节点,最后访问二叉树的右子树。而对于左子树来说,中序遍历继续访问其左子树,再访问其根节点与右子树。因此,我们首先需要将整棵树的根节点入栈,其次将栈顶元素的左孩子依次入栈,直至栈顶元素没有左孩子。此时,栈顶元素没有左孩子,因此访问栈顶元素(将其记为节点t1)并将其出栈。

当访问完节点t1之后,需要判断它是否有右孩子。

当节点t1没有右孩子时,只需要继续访问栈顶元素并将其出栈即可,接下来仍然是进一步判断刚出栈的元素是否存在右孩子并继续上述过程。

当节点t1存在右孩子,则我们需要将右孩子入栈,同时我们需要将栈顶元素的左孩子依次入栈(如果存在的话),直至栈顶元素没有左孩子。此时,栈顶元素没有左孩子,因此访问栈顶元素(将其记为节点t2)并将其出栈。

同样的道理,访问完节点t2之后,也需要进一步判断它是否有右孩子。接下来就是跟节点t1类似的过程。

下面给出一个具体的例子。

第一步:节点1,2,4入栈

第二步:由于节点4没有左孩子,因此访问节点4并将其出栈。同时,由于节点4有右孩子,因此我们将节点4的右孩子节点8入栈并将节点8的左孩子节点10入栈

第三步:由于节点10没有左孩子,因此访问节点10并将其出栈

第四步:由于节点10也没有右孩子,因此继续访问栈顶元素节点8并将其出栈

第五步:由于节点8也没有右孩子,因此继续访问栈顶元素节点2并将其出栈。同时,由于节点2有右孩子,因此,我们将节点2的右孩子节点5以及节点5的左孩子节点9入栈

第六步:由于节点9没有左孩子,因此访问节点9并将其出栈

第七步:由于节点9也没有右孩子,因此继续访问栈顶元素节点5并将其出栈

第八步:由于节点5没有右孩子,因此继续访问栈顶元素节点1并将其出栈。同时,由于节点1存在右孩子,因此将节点1的右孩子节点3以及节点3的左孩子节点6入栈

第九步:由于节点6没有左孩子,因此访问节点6并将其出栈

第十步:由于节点6也没有右孩子,因此继续访问栈顶元素节点3并将其出栈。同时,由于节点3存在右孩子,因此将节点3的右孩子节点7入栈

第十一步:访问节点7。栈空。

源码如下:

void inOrder(const treeNode *root){
    if(!root) {
        return;
    }
    stack<const treeNode*> s;
    const treeNode *t=nullptr;
    s.push(root);
    while(!s.empty()) {
        while(s.top()->left) {
            s.push(s.top()->left);
        }
        while(!s.empty()) {
            t=s.top(); s.pop();
            cout << t->val << " ";
            if(t->right) {
                s.push(t->right);
                break;
            }
        }
    }
    cout << endl;
}
三、后序遍历
1、概念
后序遍历先访问左子树,再访问右子树,最后访问根节点。

2、递归实现
void postOrderRecursively(const treeNode *root) {
    if(root) {
        postOrderRecursively(root->left);
        postOrderRecursively(root->right);
        cout << root->val << " ";
    }
}
3、非递归实现
后序遍历的非递归实现与中序遍历的大体相同。首先仍然是将整棵树的根节点入栈,其次将栈顶元素的左孩子依次入栈,直至栈顶元素没有左孩子。

接下来需要判断栈顶元素是否存在右孩子。

若栈顶元素存在右孩子且该右孩子与最近刚出栈的元素不同,则将右孩子也入栈,同时将栈顶元素的左孩子依次入栈,直至栈顶元素没有左孩子。接下来继续判断栈顶元素是否存在右孩子,重复上述过程。

否则,则直接访问栈顶元素并将其出栈。接下来继续判断栈顶元素是否存在右孩子,重复上述过程。

之所以增加红色部分的判断,是为了避免出现下面这种情况:若节点8是节点4的右孩子,节点8是叶子节点,且4和8都在栈中,那么由于节点8没有左孩子也没有右孩子,则我们访问节点8并将其出栈。接下来是判断节点4是否存在右孩子。如果没有红色部分的判断,那么节点8又会入栈,从而导致死循环。而增加红色部分的判断,是为了避免节点8再次入栈。

源码如下:

void postOrder(const treeNode *root){
    if(!root) {
        return ;
    }
    stack<const treeNode*> s;
    s.push(root);
    const treeNode *prev=nullptr;
    while(!s.empty()) {
        while(s.top()->left) {
            s.push(s.top()->left);
        }
        while(!s.empty()) {
            if(s.top()->right&&s.top()->right!=prev) {
                s.push(s.top()->right);
                break;
            }
            cout << s.top()->val << " ";
            prev=s.top();
            s.pop();
        }
    }
    cout << endl;
}
四、层次遍历
1、概念
层次遍历指的是按照树的层次对树进行遍历,即按照从根节点往叶子节点,从左往右的顺序遍历二叉树。上面中序遍历的例子中,层次遍历序列即为1,2,3,4,5,6,7,8,9,10

2、实现
基本的层次遍历的实现还是比较简单,借助队列queue数据结构即可实现。若要增加一点难度,则需要在输出时体现层次感,将不同层的节点放在不同行输出,而同一层的节点在相同行输出。上面中序遍历的例子中,改进版的层次遍历序列即为

1

2 3

4 5 6 7

8 9

10

解决方案是增加一个计数器,记录现在正在输出的节点所属行的节点总数,每次输出该行的一个节点,计数器减一。当计数器变为0时,即输出换行符,并将其值重置为队列的节点总数(下一行的节点总数)。

void traverseTreeLinely(const treeNode *root) {
    if(!root) {
        return;
    }
    queue<const treeNode*> q;
    unsigned numInQueue=1;
    q.push(root);
    const treeNode *t=nullptr;
    while(!q.empty()) {
        t=q.front(); q.pop(); --numInQueue;
        cout << t->val;
        if(t->left) {
            q.push(t->left);
        }
        if(t->right) {
            q.push(t->right);
        }
        if(numInQueue) {
            cout << " ";
        }
        else {
            cout << endl;
            numInQueue+=q.size();
        }
    }
}
五、Zig-Zag遍历
1、概念
Zig-Zag遍历指的是相邻两层按照从左到右和从右到左的顺序遍历。第一层从左到右遍历,第二层从右到左遍历,第三层又变成从左到右遍历...上面中序遍历的例子中,层次遍历序列即为1,3,2,4,5,6,7,9,8,10

2、实现
借助两个栈即可实现。

void traverseTreeZigZag(const treeNode *root) {
    if(!root) {
        return;
    }
    stack<const treeNode*> leftFirst, rightFirst;
    rightFirst.push(root);
    while(!leftFirst.empty()||!rightFirst.empty()) {
        if(!rightFirst.empty()) {
            while(!rightFirst.empty()) {
                cout << rightFirst.top()->val << " ";
                if(rightFirst.top()->left) {
                    leftFirst.push(rightFirst.top()->left);
                }
                if(rightFirst.top()->right) {
                    leftFirst.push(rightFirst.top()->right);
                }
                rightFirst.pop();
            }
            cout << endl;
        }
        if(!leftFirst.empty()) {
            while(!leftFirst.empty()) {
                cout << leftFirst.top()->val << " ";
                if(leftFirst.top()->right) {
                    rightFirst.push(leftFirst.top()->right);
                }
                if(leftFirst.top()->left) {
                    rightFirst.push(leftFirst.top()->left);
                }
                leftFirst.pop();
            }
            cout << endl;
        }
    }
}
六、附录
附上测试代码

/*
 * main.cpp
 *
 *  Created on: 2019年7月6日
 *      Author: lee
 */
#include<iostream>
#include<queue>
#include<stack>
using namespace std;
 
struct treeNode {
    treeNode *left, *right;
    int val;
    treeNode(int a=0):left(nullptr), right(nullptr), val(a){};
};
 
treeNode* constructBinTree();
void traverseTreeLinely(const treeNode*);
void traverseTreeZigZag(const treeNode*);
void preOrderRecursively(const treeNode*);
void preOrder(const treeNode*);
void inOrderRecursively(const treeNode*);
void inOrder(const treeNode*);
void postOrderRecursively(const treeNode*);
void postOrder(const treeNode*);
void deleteTree(treeNode*&);
 
int main() {
    cout << "Construct a binary tree." << endl;
    treeNode *root=constructBinTree();
    cout << "Traverse the tree linely:" << endl;
    traverseTreeLinely(root);
    cout << "Traverse the tree in a zig-zag manner:" << endl;
    traverseTreeZigZag(root);
    cout << "Traverse the tree in pre-order:" << endl;
    preOrderRecursively(root);
    cout << endl;
    preOrder(root);
    cout << "Traverse the tree in in-order:" << endl;
    inOrderRecursively(root);
    cout << endl;
    inOrder(root);
    cout << "Traverse the tree in post-order:" << endl;
    postOrderRecursively(root);
    cout << endl;
    postOrder(root);
    cout << "Destroy the tree." << endl;
    deleteTree(root);
 
    return 0;
}
 
treeNode* constructBinTree() {
    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);
    root->left->left->right=new treeNode(8);
    root->left->right->left=new treeNode(9);
    root->left->left->right->left=new treeNode(10);
    return root;
}
 
void traverseTreeLinely(const treeNode *root) {
    if(!root) {
        return;
    }
    queue<const treeNode*> q;
    unsigned numInQueue=1;
    q.push(root);
    const treeNode *t=nullptr;
    while(!q.empty()) {
        t=q.front(); q.pop(); --numInQueue;
        cout << t->val;
        if(t->left) {
            q.push(t->left);
        }
        if(t->right) {
            q.push(t->right);
        }
        if(numInQueue) {
            cout << " ";
        }
        else {
            cout << endl;
            numInQueue+=q.size();
        }
    }
}
 
void traverseTreeZigZag(const treeNode *root) {
    if(!root) {
        return;
    }
    stack<const treeNode*> leftFirst, rightFirst;
    rightFirst.push(root);
    while(!leftFirst.empty()||!rightFirst.empty()) {
        if(!rightFirst.empty()) {
            while(!rightFirst.empty()) {
                cout << rightFirst.top()->val << " ";
                if(rightFirst.top()->left) {
                    leftFirst.push(rightFirst.top()->left);
                }
                if(rightFirst.top()->right) {
                    leftFirst.push(rightFirst.top()->right);
                }
                rightFirst.pop();
            }
            cout << endl;
        }
        if(!leftFirst.empty()) {
            while(!leftFirst.empty()) {
                cout << leftFirst.top()->val << " ";
                if(leftFirst.top()->right) {
                    rightFirst.push(leftFirst.top()->right);
                }
                if(leftFirst.top()->left) {
                    rightFirst.push(leftFirst.top()->left);
                }
                leftFirst.pop();
            }
            cout << endl;
        }
    }
}
 
void preOrderRecursively(const treeNode *root) {
    if(root) {
        cout << root->val << " ";
        preOrderRecursively(root->left);
        preOrderRecursively(root->right);
    }
}
 
void preOrder(const treeNode *root) {
    if(!root) {
        return;
    }
    stack<const treeNode*> s;
    const treeNode *t=nullptr;
    s.push(root);
    while(!s.empty()) {
        t=s.top();
        s.pop();
        cout << t->val << " ";
        if(t->right) {
            s.push(t->right);
        }
        if(t->left) {
            s.push(t->left);
        }
    }
    cout << endl;
}
 
void inOrderRecursively(const treeNode *root) {
    if(root) {
        inOrderRecursively(root->left);
        cout << root->val << " ";
        inOrderRecursively(root->right);
    }
}
 
void inOrder(const treeNode *root){
    if(!root) {
        return;
    }
    stack<const treeNode*> s;
    const treeNode *t=nullptr;
    s.push(root);
    while(!s.empty()) {
        while(s.top()->left) {
            s.push(s.top()->left);
        }
        while(!s.empty()) {
            t=s.top(); s.pop();
            cout << t->val << " ";
            if(t->right) {
                s.push(t->right);
                break;
            }
        }
    }
    cout << endl;
}
 
void postOrderRecursively(const treeNode *root) {
    if(root) {
        postOrderRecursively(root->left);
        postOrderRecursively(root->right);
        cout << root->val << " ";
    }
}
 
void postOrder(const treeNode *root){
    if(!root) {
        return ;
    }
    stack<const treeNode*> s;
    s.push(root);
    const treeNode *prev=nullptr;
    while(!s.empty()) {
        while(s.top()->left) {
            s.push(s.top()->left);
        }
        while(!s.empty()) {
            if(s.top()->right&&s.top()->right!=prev) {
                s.push(s.top()->right);
                break;
            }
            cout << s.top()->val << " ";
            prev=s.top();
            s.pop();
        }
    }
    cout << endl;
}
 
void deleteTree(treeNode *&root) {
    if(root) {
        deleteTree(root->left);
        deleteTree(root->right);
        delete root;
        root=nullptr;
    }
}


原文链接:https://blog.csdn.net/li1914309758/article/details/95045130

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值