二叉树——遍历篇(c++)

二叉树——遍历篇

二叉树很多算法题都与其遍历相关,笔者经过大量学习并进行了思考和总结,写下这篇二叉树的遍历篇。

1、二叉树数据结构及访问函数

#include <stdio.h>
#include <iostream>
#include <stack>
using namespace std;

struct BTNode
{
    int value;
    struct BTNode *left, *right;
    BTNode(int value_) :value(value_),left(NULL),right(NULL){};
};
//访问函数:可根据实际进行修改
void visit(BTNode* node)
{
    cout << node->value << " ";
}

2、二叉树的遍历——深度优先遍历(DFS)(先序、中序、后序)

2.1、具体遍历顺序

  • 先序遍历:访问根节点、先序遍历左孩子、先序遍历右孩子 (根、左、右)

  • 中序遍历:中序遍历左孩子、访问根节点、中序遍历右孩子 (左、根、右)

  • 后序遍历:后序遍历左孩子、后序遍历右孩子、访问根节点 (左、右、根)

2.2、递归遍历

/**
* 先序遍历二叉树
*/
void PreOrder(BTNode* root)
{
    if (root)
    {
        visit(root);
        PreOrder(root->left);
        PreOrder(root->right);
    }
}
/**
* 中序遍历二叉树
*/
void InOrder(BTNode* root)
{
    if (root)
    {
        InOrder(root->left);
        visit(root);
        InOrder(root->right);
    }
}
/**
* 后序遍历二叉树
*/
void PostOrder(BTNode* root)
{
    if (root)
    {
        PostOrder(root->left);
        PostOrder(root->right);
        visit(root);
    }
}

2.3、非递归遍历——借助栈

  • 借助栈,可以实现非递归遍历。
  • 在这里三种非递归遍历都总结和介绍一种算法思路,其栈中保存的节点可以用于路径搜索类的题目,即保存着从根节点到当前访问节点最短路径的所有节点信息,以*标记。
  • 介绍仅用于遍历访问的简单思路,栈中信息难以应用于搜索路径类的题目。

2.31 先序非递归遍历

* PreOrder_1a

算法过程:

由先序遍历过程可知,先序遍历的开始节点是根节点,然后用指针p 指向当前要处理的节点,沿着二叉树的左下方逐一访问,并将它们一一进栈,直至栈顶节点为最左下节点,指针p为空。此时面临两种情况。(1)对栈顶节点的右子树访问(如果有的话,且未被访问),对右子树进行同样的处理;(2)若无右子树,则用指针last记录最后一个访问的节点,指向栈顶节点,弹栈。如此重复操作,直至栈空为止。

void PreOrder_1a(BTNode *root)
{
    if (NULL == root) return;
    stack<BTNode*> stack;
    BTNode *p = root;
    BTNode *last = NULL;
    do {
        while (p)
        {
            visit(p);
            stack.push(p);
            p = p->left;
        }
        //此时p = NULL,栈顶节点为左子树最左节点
        if (!stack.empty())
        {
            BTNode *t = stack.top();
            if (t->right != NULL && t->right != last)
            {
                p = t->right;
            }
            else
            {//若无右子树,则指针P仍为空,则不断弹栈(沿着双亲方向)寻找有未被访问的右子树的节点
                last = t;
                stack.pop();
            }
        }
    } while (!stack.empty());
}
* PreOrder_1b

算法过程:此算法与PreOrder_1a有异曲同工之处,巧妙之处在于对上述两种情况的处理。指针p记录当前栈顶结点的前一个已访问的结点。若无右子树、或者右子树已被访问,则用指针p记录当前栈顶节点,弹栈,不断沿着双亲方向寻找有未访问右子树的节点,找到即退出循环,否则直至栈空。当栈顶节点的右孩子是p时,则将cur指向右孩子,设置flag =0 ,退出当前搜索循环(不断弹栈,搜索有右节点且未被访问的祖先节点),然后对右子树进行同样的处理。如此反复操作,直至栈空为止。

void PreOrder_1b(BTNode *root)
{
    if (NULL == root) return;
    stack<BTNode*>stack;
    int flag;
    BTNode *cur = root,*p;
    do{
        while (cur)
        {
            visit(cur);
            stack.push(cur);
            cur = cur->left;
        }
     //执行到此处时,栈顶元素没有左孩子或左子树均已访问过
        p = NULL;      //p指向栈顶结点的前一个已访问的结点
        flag = 1;      //表示*cur的左孩子已访问或者为空
        while (!stack.empty() && flag == 1)
        {
            cur = stack.top();
            if (cur->right == p)  //表示右孩子结点为空或者已经访问完右孩子结点
            {
                stack.pop();
                p = cur;    //p指向刚访问过的结点
            }
            else
            {
                cur = cur->right; //cur指向右孩子结点
                flag = 0;       //设置未被访问的标记
            }
        }
    } while (!stack.empty());
}
PreOrder_2a && PreOrder_2b

PreOrder_2a 和 PreOrder_2b 算法思路大体相同,PreOrder_2a 实现比较简洁

算法过程:

用指针p指向当前要处理的节点,沿着左下方向逐一访问并压栈,直至指针P为空,栈顶节点为最左下节点。然后p指向栈顶节点的右节点(不管是否空);若右节点为空,则继续弹栈。若右节点非空,则按上述同样处理右节点。如此重复操作直至栈空为止。

缺陷:PreOrder_2 当访问栈顶节点的右节点时,会丢失当前栈顶节点信息,导致从根节点到当前栈顶节点的右节点路径不完整。

优点:算法思路清晰易懂,逻辑简单。

void PreOrder_2a(BTNode *root)
{
    if (NULL == root) return;
    BTNode *p = root;
    stack<BTNode*> stack;
    while (p || !stack.empty())
    {
        if (p)
        {
            visit(p);
            stack.push(p);
            p = p->left;
        }
        else
        {
            BTNode *top = stack.top();
            p = top->right;
            stack.pop();
        }
    }
}
void PreOrder_2b(BTNode *root)
{
    if (NULL == root) return;
    BTNode *p = root;
    stack<BTNode*> stack;
    while (!stack.empty() ||p)
    {
        while (p)
        {
            visit(p);
            stack.push(p);
            p = p->left;
        }
        if (!stack.empty())
        {
            BTNode *top = stack.top();
            p = top->right;
            stack.pop();
        }
    }
}
PreOrder_3

算法过程:

用指针p指向当前要处理的节点。先把根节点压栈,栈非空时进入循环,出栈栈顶节点并访问,然后按照先序遍历先左后右的逆过程把当前节点的右节点压栈(如果有的话),再把左节点压栈(如果有的话)。如此重复操作,直至栈空为止。

特点:栈顶节点保存的是先序遍历下一个要访问的节点,栈保存的所有节点不是根到要访问节点的路径。

void PreOrder_3(BTNode *root)
{
    if (NULL == root) return;
    BTNode *p = root;
    stack<BTNode *> stack;
    stack.push(p);
    while (!stack.empty())
    {
        p = stack.top();
        stack.pop();
        visit(p);
        if (p->right)
            stack.push(p->right);
        if (p->left)
            stack.push(p->left);
    }
}

2.32 中序非递归遍历

中序遍历非递归算法要把握访问栈顶节点的时机。

* InOrder_1

算法过程:

用指针cur指向当前要处理的节点。先扫描(并非访问)根节点的所有左节点并将它们一一进栈,直至栈顶节点为最左下节点,指针cur为空。

此时主要分两种情况。(1)栈顶节点的左节点为空或者左节点已访问,访问栈顶节点(访问位置很重要!),若有右节点则将cur指向右节点,退出当前while循环,对右节点进行上述同样的操作,若无右节点则用指针p记录站顶节点并弹栈;(2)站顶节点的右节点为空或已访问,用指针p记录站顶节点并弹栈。

内部第二个while循环可称为访问搜索循环,在栈顶节点的左节点为空或者左节点已访问的情况下,访问栈顶节点,若有右节点,则退出循环,否则不断弹栈。

如此重复操作,直至栈空为止。

void InOrder_1(BTNode *root)
{
    if (NULL == root) return;
    stack<BTNode *>stack;
    BTNode *cur = root,*p = NULL;
    int flag = 1;
    do{
        while (cur){
            stack.push(cur);
            cur = cur->left;
        }
     //执行到此处时,栈顶元素没有左孩子或左子树均已访问过
        p = NULL;      //p指向栈顶结点的前一个已访问的结点
        flag = 1;      //表示*cur的左孩子已访问或者为空
        while (!stack.empty() && flag)
        {
            cur = stack.top();
            if (cur->left == p) //左节点为空 或者左节点已访问
            {
                visit(cur);     //访问当前栈顶节点
                if (cur->right) //若有右节点  当前节点指向右节点,并退出当前循环,进入上面的压栈循环
                {
                    cur = cur->right;
                    flag = 0;   //flag = 0 标记右节点的左子树未访问
                }
                else           //当前节点没有右节点,P记录访问完的当前节点,弹栈
                {
                    p = cur;
                    stack.pop();
                }
            }
            else      // 此时 cur->right == P 即访问完右子树 ,P记录访问完的当前节点,弹栈
            {
                p = cur;
                stack.pop();
            }
        }
    } while (!stack.empty());
}

InOrder_2a && InOrder_2b

算法过程:

用指针p指向当前要处理的节点。先扫描(并非访问)根节点的所有左节点并将它们一一进栈,当无左节点时表示栈顶节点无左子树,然后出栈这个节点,并访问它,将p指向刚出栈节点的右孩子,对右孩子进行同样的处理。如此重复操作,直至栈空为止。

需要注意的是:当节点*p的所有左下节点入栈后,这时的栈顶节点要么没有左子树,要么其左子树已访问,就可以访问栈顶节点了!

InOrder_2a 、 InOrder_2b 与 PreOrder_1a 、PreOrder_1b 代码基本相同,唯一不同的是访问节点的时机,把握好可方便理解和记忆。

void InOrder_2a(BTNode *root)
{
    if (NULL == root) return;
    BTNode *p = root;
    stack<BTNode *>stack;
    while (p || !stack.empty())
    {
        while (p)
        {
            stack.push(p);
            p = p->left;
        }
        if (!stack.empty())
        {
            p = stack.top();
            visit(p);
            stack.pop();
        }
    }
}

void InOrder_2b(BTNode *root)
{
    if (NULL == root) return;
    stack<BTNode *>stack;
    BTNode *p = root;
    while (p || !stack.empty())
    {
        while (p)
        {
            stack.push(p);
            p = p->left;
        }
        if (!stack.empty())
        {
            p = stack.top();
            visit(p);
            p = p->right;
            stack.pop();
        }
    }
}

2.33 后序非递归遍历

*PostOrder_1

算法过程:

用指针cur指向当前要处理的节点。先扫描(并非访问)根节点的所有左节点并将它们一一进栈,直至栈顶节点为最左下节点,指针cur为空。此时有两种情况。(1)栈顶节点的右节点为空或已访问,访问当前栈顶节点(访问时机很重要!),用指针p保存刚刚访问过的节点(初值为NULL),弹栈;(2)栈顶节点有未被访问的右节点,设置flag,退出当前访问搜索循环。如此重复处理,直至栈空为止。

void PostOrder_1(BTNode *root)
{
    if (NULL == root) return;
    stack<BTNode*>stack;
    int flag;
    BTNode *cur = root, *p;
    do{
        while (cur)
        {
            stack.push(cur);
            cur = cur->left;
        }
      //执行到此处时,栈顶元素没有左孩子或左子树均已访问过
        p = NULL;      //p指向栈顶结点的前一个已访问的结点
        flag = 1;      //表示*cur的左孩子已访问或者为空
        while (!stack.empty() && flag == 1)
        {
            cur = stack.top();
            if (cur->right == p)
            {//表示右孩子结点为空,或者已经访问cur的右子树(p必定是后序遍历cur的右子树最后一个访问节点)
                visit(cur);
                p = cur;    //p指向刚访问过的结点
                stack.pop();
            }
            else
            {
                cur = cur->right; //cur指向右孩子结点
                flag = 0;        //表示*cur的左孩子尚未访问过
            }
        }
    } while (!stack.empty());
}

PostOrder_2

算法过程:

先把根节点压栈,用指针p记录上一个被访问的节点。在栈为空时进入循环,取出栈顶节点。

此时有两种情况:

(1)访问当前节点,注意把握访问的时机,如果当前节点是叶子节点,访问当前节点;如果上一个访问的节点是当前节点的左节点(说明无右节点),访问当前节点;如果上一个访问的节点是当前节点的右节点(说明左右节点都有),访问当前节点;指针p记录当前访问的节点,弹栈。 所以,只有当前节点是叶子节点,或者上一个访问的节点是当前节点的左节点(无右) 或右节点(左右都有) ,才可以访问当前节点。

(2)压栈。 后序遍历顺序为 左、右、根,按照逆序,先把右压栈,再把左压栈(如果有的话)。

如此重复操作,直至栈空为止。

void PostOrder_2(BTNode* root)
{
    if (NULL == root) return;
    BTNode *cur = root, *p=NULL;
    stack<BTNode*> stack;
    stack.push(root);
    while (!stack.empty())
    {
        cur = stack.top();
        if (cur->left == NULL && cur->right == NULL || (p!= NULL && (cur->left==p || cur->right == p)))
        {
            visit(cur);
            stack.pop();
            p = cur;
        }
        else
        {
            if (cur->right)
                stack.push(cur->right);
            if (cur->left)
                stack.push(cur->left);
        }
    }
}

3、二叉树的遍历——广度优先遍历(BFS)

  • 二叉树的广度优先遍历,就是层次遍历,借助队列实现

在进行层次遍历时,对某一层的节点访问完后,再按照对它们的访问次序对各个节点的左、右孩子顺序访问。这样一层一层进行,先访问的节点其左、右孩子也要先访问,与队列的操作原则比较吻合,且符合广度优先搜索的特点。

算法过程:先将根节点进队,在队不空时循环;从队列中出列一个节点,访问它;若它有左孩子节点,将左孩子节点进队;若它有右孩子节点,将右孩子节点进队。如此重复操作直至队空为止。

  void LevelOrder(BTNode *root)
  {
    if (NULL == root) return;
    queue<BTNode*> queue;
    queue.push(root);
    BTNode* p;
    while (!queue.empty())
    {
        p = queue.front();
        queue.pop();
        visit(p);
        if (p->left)
            queue.push(p->left);
        if (p->right)
            queue.push(p->right);
    }
  }

原创所有,转载务必注明出处。

?

原文地址:https://www.cnblogs.com/pz-Feng/p/8119681.html

时间: 2024-10-10 07:27:36

二叉树——遍历篇(c++)的相关文章

在路上---学习篇(一)Python 数据结构和算法 (5)二分查找、二叉树遍历

独白: 利用算法进行查找指定元素,最近学习二分查找和二叉树遍历.二分查找前提是在有序中进行查找,二叉树引入了树的概念.树的概念其中有许多小知识点,也是一种新的数据结构.还是之前的感悟,需了解其本质才会写出更好的算法. 二分查找 二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好:其缺点是要求待查表为有序表,且插入删除困难.因此,折半查找方法适用于不经常变动而查找频繁的有序列表.首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功:否则利用

二叉树遍历,递归,栈,Morris

一篇质量非常高的关于二叉树遍历的帖子,转帖自http://noalgo.info/832.html 二叉树遍历(递归.非递归.Morris遍历) 2015年01月06日 |  分类:数据结构 |  标签:二叉树遍历 |  评论:8条评论 |  浏览:6,603次 二叉树遍历是二叉树中最基本的问题,其实现的方法非常多,有简单粗暴但容易爆栈的递归算法,还有稍微高级的使用栈模拟递归的非递归算法,另外还有不用栈而且只需要常数空间和线性时间的神奇Morris遍历算法,本文将对这些算法进行讲解和实现. 递归

【自考】数据结构之二叉树遍历

什么是数据结构? 首先看看维基百科的定义:在计算机科学或信息科学中,数据结构(英语:data structure)是计算机中存储.组织数据的方式.通常情况下,精心选择的数据结构可以带来最优效率的算法. 课本中的定义:指一组相互之间存在一种或多种特定关系的数据的组织方式和它们在计算机中的存储方式,以及定义在该组数据上的一组操作. 有哪些内容?    直接看图了解数据结构的知识点,如下: 不管是算法还是逻辑结构,都是用数据说话的,所以要先明白了数据的基本概念.大的联系图中已给出,小的知识点需要我们去

数据结构与算法 3:二叉树,遍历,创建,释放,拷贝,求高度,面试,线索树

[本文谢绝转载,原文来自http://990487026.blog.51cto.com] 树 数据结构与算法 3:二叉树,遍历,创建,释放,拷贝,求高度,面试,线索树 二叉树的创建,关系建立 二叉树的创建,关系建立2 三叉链表法 双亲链表: 二叉树的遍历 遍历的分析PPT 计算二叉树中叶子节点的数目:使用全局变量计数器 计算二叉树中叶子节点的数目:不使用全局变量计数器 无论是先序遍历,中序遍历,后序遍历,求叶子的数字都不变;因为本质都是一样的,任何一个节点都会遍历3趟 求二叉树的高度 二叉树的拷

二叉树遍历递归与非递归实现

说明:本文仅供学习交流,转载请标明出处,欢迎转载! 二叉树遍历是二叉树中非常基础的部分,也是学习二叉树必须熟练掌握的部分,下面我们先给出二叉树三种遍历方式的定义,并通过举例来说明二叉树遍历的过程. 二叉树的遍历分为:前序遍历(也叫先序遍历).中序遍历.后序遍历.所谓前.中.后都是根据当前子树根结点相对左右孩子的位置而言,也就是说: 前序遍历:根结点在前,即:根 ----->左------->右: 中序遍历:根结点在中间,即:左------>根------>右: 后序遍历:根结点在最

算法之二叉树遍历

[代码示例] package com.wcs.java; import java.util.ArrayList; import java.util.List; public class BinaryTree { class TreeNode { public String data; //数据 public TreeNode leftNode; //左子树 public TreeNode rightNode; //右子树 public TreeNode(String data, TreeNode

morris算法-----高级二叉树遍历算法

在遍历儿叉树时,常常使用的是递归遍历,或者是借助于栈来迭代,在遍历过程中,每个节点仅访问一次,所以这样遍历的时间复杂度为O(n),空间复杂度为O(n),并且递归的算法易于理解和实现.在遍历过程中,递归遍历过程的空间复杂度却是O(n),就算是转换为使用栈空间迭代时间,还是没有改变算法对额外空间的需求,在学习数据结构课程时,还学习了线索二叉树,在线索二叉树中,使用线索来保存节点的前驱和后继的信息,而这些线索是利用了叶节点的空指针域来保存,所以知道了树种每个节点的前驱和后继的位置(指针)可以有效降低遍

D - 二叉树遍历(推荐)

二叉树遍历问题 Description Tree Recovery Little Valentine liked playing with binary trees very much. Her favorite game was constructing randomly looking binary trees with capital letters in the nodes. This is an example of one of her creations: D / / B E /

poj2255 (二叉树遍历)

poj2255 二叉树遍历 Time Limit:3000MS     Memory Limit:0KB     64bit IO Format:%lld & %llu Description Little Valentine liked playing with binary trees very much. Her favorite game was constructing randomly looking binary trees with capital letters in the