二叉树遍历算法

摘要:二叉树主要有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 G E B F 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)

5. Java版本的实现(补充)

class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
public void preOrder(TreeNode root)
{
    Stack<TreeNode> stack = new Stack<TreeNode>();
    while(root != null || !stack.empty())
    {
        while(root != null)
        {
            System.out.print(root.val + " ");
            stack.push(root);
            root = root.left;
        }
        if(!stack.empty())
        {
            root = stack.pop();
            root = root.right;
        }
    }
}

void midOrder(TreeNode root){
    Stack<TreeNode> stack = new Stack<TreeNode>();
    while(root != null || !stack.empty())
    {
        while(root != null)
        {
            stack.push(root);
            root = root.left;
        }
        if(!stack.empty())
        {
            root = stack.pop();
            System.out.print(root.val + " ");
            root = root.right;
        }
    }
}

/**
 * 后序遍历的难点在于:
 * 需要判断上次访问的节点是位于左子树,还是右子树。
 * 若是位于左子树,则需跳过根节点,先进入右子树,再回头访问根节点;
 * 若是位于右子树,则直接访问根节点
 * @param root
 */
void postOrder(TreeNode root){
    Stack<TreeNode> stack = new Stack<TreeNode>();
    TreeNode curr = root, last = null;
    //先把curr移动到左子树最下边
    while (curr != null){
        stack.push(curr);
        curr = curr.left;
    }
    while (!stack.isEmpty()){
        curr = stack.peek();
        //一个根节点被访问的前提是:无右子树或右子树已被访问过
        if (curr.right == null || curr.right == last){
            System.out.print(curr.val + " ");
            last = curr;
            stack.pop();
        }
        else {
            //进入右子树
            curr = curr.right;
            while (curr != null){
                stack.push(curr);
                curr = curr.left;
            }
        }
    }
}
void levelOrder(TreeNode root){
    if (root == null)
        return;
    Queue<TreeNode> queue = new LinkedList<TreeNode>();
    queue.add(root);
    while (!queue.isEmpty()){
        TreeNode node = queue.poll();
        System.out.print(node.val + " ");
        if (node.left != null)
            queue.add(node.left);
        if (node.right != null)
            queue.add(node.right);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容