二叉树遍历算法

作者: 杨赟快跑 | 来源:发表于2018-09-23 15:36 被阅读90次

摘要:二叉树主要有3种遍历算法,分为为先序、中序、后序。本文对二叉树的3种遍历算法的遍历规则进行介绍,并给出3种遍历算法的递归和迭代版本的C++实现。文章发出后,我的好朋友指出还有一个层次遍历,我将在文章最后介绍。

1. 二叉树遍历

如图1所示,一颗二叉树T由根节点V、左子树L和右子树R构成。


图1 二叉树示意图

则二叉树T的遍历指:按照某种次序访问树中各节点,每个节点被访问恰好一次。二叉树T的先序、中序、后序遍历的结果如图2所示。


图2 二叉树的先序、中序、后序遍历

从图中可以看出,先序、中序、后序的区别在于局部根节点的访问时机,而对于左右孩子的访问,其访问顺序总是满足先左后右的规则。下面举例说明二叉树的遍历规则,一棵二叉树的结构如图3所示,则其遍历结果如下:
先序遍历结果:A B D E G H C F
中序遍历结果:D B G H E A C F
后序遍历结果:D H F E B G C A


图3 一颗普通的二叉树

2. 二叉树节点

在实现二叉树的遍历算法之前,我们先创建一个二叉树节点类。二叉树节点是二叉树的基本组成单位,节点与节点之间有父子关系、兄弟关系等,由于节点的数据类型不确定,所以采用模板实现二叉树的节点类。

/*
二叉树节点
*/
#define BiNodePos(T) BiNode<T>*
template<typename T> 
struct BiNode{
    BiNodePos(T) parent= nullptr, lChild = nullptr, rChild = nullptr;
    T data; int height;
    BiNode(T data, BiNodePos(T) parent){
        this->data = data;
        this->parent = parent;
    }
    //子树规模
    int size() {
        int s = 1;//计入本身
        if (lChild) s += lChild.size();//递归计入左子树规模
        if (rChild) s += rChild.size();//递归计入右子树规模
        return s;
    }
    //插入左孩子
    BiNodePos(T) insertAsLC(const T &) {
        return lChild = new BiNode(e, this);
    }
    //插入右孩子
    BiNodePos(T) insertAsRC(const T &) {
        return rChild = new BiNode(e, this);
    }
};

3. 二叉树遍历算法的实现

3.1. 递归版本

#include <stack>
using namespace std;

//先序遍历
template<typename T, typename VST >
void preTraverse(BiNodePos(T) x, VST& visit)
{
    if (!x) return;
    visit(x->data);
    preTraverse(x->lChild);
    preTraverse(x->rChild);
}//O(n)
//中序遍历
template<typename T, typename VST >
void midTraverse(BiNodePos(T) x, VST& visit)
{
    if (!x) return;
    midTraverse(x->lChild);
    visit(x->data);
    midTraverse(x->rChild);
}//O(n)
//后序遍历
template<typename T, typename VST >
void postTraverse(BiNodePos(T) x, VST& visit)
{
    if (!x) return;
    postTraverse(x->lChild);
    postTraverse(x->rChild);
    visit(x->data);
}//O(n)

3.2. 迭代版本

递归版本的时间复杂度是O(n),理论上递归遍历算法已经事件复杂度已经最小了。但实际上,由于递归实例必须具有通用的格式,所以在运行栈中,每一个递归实例对应的一帧不可能做到足够的小。而针对于具体的问题,我们完全可以通过精巧的设计使得每一帧做到足够小的,所以,我们有必要将递归版本转化为迭代版本。

#include <stack>
using namespace std;

template<typename T, typename VST>
static void visitAlongLeftBranch(BiNodePos(T) x, VST& visit, stack<BiNodePos(T)> &s)
{
    while (x!=nullptr)
    {
        visit(x->data);
        s.push(x->rChild);
        x = x->lChild;
    }
}
template<typename T, typename VST >
void preIterationTraverse(BiNodePos(T) x, VST& visit)
{
    stack<BiNodePos(T)> s;
    while (true)
    {
        visitAlongLeftBranch(x, visit, s);
        if (s.empty) break;
        x = s.top();
        s.pop();
    }
}//O(n)
template<typename T>
static void goAlongLeftBranch(BiNodePos(T) x,stack<BiNodePos(T)> &s) 
{
    while (x!=nullptr)
    {
        s.push(x);
        x = x->lChild;
    }
}
template<typename T, typename VST >
void inIterationTraverse(BiNodePos(T) x, VST& visit)
{
    stack<BiNodePos(T)> s;
    while (true)
    {
        goAlongLeftBranch(x, s);
        if (s.empty) break;
        x = s.top();
        s.pop();
        visit(x->data);
        x = x->rChild;
    }
}//O(n)

template < typename T> 
static void gotoLVFL(stack<BiNodePos(T)> s) {
    BiNodePos(T) node;
    while (node = s.top()) {
        if (node->lChild){
            if (node->rChild)
                s->push(node->rChild);
            s->push(node->lChild);
        }
        else
            s->push(node->rChild);
    }
    s.pop();
}
template<typename T, typename VST >
void postIterationTraverse(BiNodePos(T) x, VST& visit)
{
    stack<BiNodePos(T)> s;
    if (x!=nullptr) s.push(x);
    while (!s.empty())
    {
        if (s.top() != x->parent)
            gotoLVFL<T>(s);
        x = s.top();
        visit(x->data);
    }
}//O(n)

4. 层次遍历

首先,感谢大佬帮我指正问题。
层次遍历是指:按层次遍历树的所有节点(即逐层地,从左到右访问所有节点)。下面是层次遍历的迭代实现。

#include <deque>
using namespace std;

template<typename T, typename VST >
void levelIterationTraverse(BiNodePos(T) x, VST& visit)
{
    deque<BiNodePos(T)> q;
    q.push_back(x);
    while (!q.empty())
    {
        x = q.front(); q.pop_front();
        visit(x.data);
        if (x->lChild) q.push_back(x->lChild);
        if (x->rChild) q.push_back(x->rChild);
    }
}//O(n)

相关文章

  • ALI 算法

    二叉树遍历算法: 按层遍历, 中序前序后序:

  • 算法-二叉树算法总结

    二叉树算法总结 1 二叉树的遍历 1.1 前序遍历 递归 迭代 1.2 中序遍历 递归 迭代 1.3 后序遍历 递...

  • 二叉树的中序遍历(Java)——Morris迭代算法

    二叉树的中序遍历 对于此题而言,若采用递归算法(简单),我们使用深度优先算法来遍历二叉树。若采用迭代算法,我们使用...

  • 二叉树的遍历

    二叉树的遍历 二叉树常用的遍历方式有:前序遍历、中序遍历、后序遍历、层序遍历四种遍历方式,不同的遍历算法,其思想略...

  • 翻转二叉树(Java)

    翻转二叉树 对于此题而言,我们使用深度优先算法来遍历二叉树。 1、深度优先算法是根据二叉树的路径进行遍历2、广度优...

  • 二叉树遍历(递归算法和非递归算法)

    实验三 二叉树遍历(递归算法和非递归算法) 一.实验目的 1.掌握二叉树的存储结构与基本操作 2.掌握二叉树的遍历...

  • 二叉树的遍历

    数据结构算法 二叉树的遍历

  • 二叉树遍历算法

    摘要:二叉树主要有3种遍历算法,分为为先序、中序、后序。本文对二叉树的3种遍历算法的遍历规则进行介绍,并给出3种遍...

  • 二叉树遍历-JAVA实现

    基础二叉树 二叉树遍历分为前序、中序、后序递归和非递归遍历、还有层序遍历。 前序递归遍历算法:访问根结点-->递归...

  • 面试题

    面试题 二叉树 非递归实现二叉树遍历 节点: 前序遍历 中序遍历 后序遍历 排序 快速排序 其他问题 算法题 给一...

网友评论

  • lg的精神食粮:可以把那树和左右树枝三个看成前中后,前序的话,左边的就在第二个位置,中序的话,中间的在第二个位置,这样比较容易记忆。嘿嘿😁。
    杨赟快跑:@lg的精神食粮 你是对的,我应该是笔误了:joy:,谢谢你发现错误
    lg的精神食粮:@杨赟快跑 那个后序是我理解错了,还是笔误?我的怎么是DHGEBFCA,G跟F的位置。😂😂😂
    杨赟快跑:@lg的精神食粮 是的呢,我是记父亲节点的,前序遍历父亲在前,中序父亲在中,左右孩子总是先左后右。跟你的方法差不多:grin:

本文标题:二叉树遍历算法

本文链接:https://www.haomeiwen.com/subject/nnofoftx.html