【树4】二叉树的遍历

简介

遍历二叉树就是按照某种顺序,将树中的结点都枚举一遍,且每个结点仅仅访问一次。因为树不是线性的结构,遍历不像线性表那样简单,因此他的遍历需要特点的算法来完成。

从某种角度讲,对二叉树的遍历就是将树形结构转换为线性结构的操作。

二叉树的遍历方法主要有如下几种:

  • 先序遍历:先访问root结点,再先序遍历左子树,再先序遍历右子树。
  • 中序遍历:先中序遍历左子树,再访问root结点,再中序遍历右子树。
  • 后序遍历:先后序遍历左子树,再后序遍历右子树,再访问root结点。
  • 层遍历:从上到下,从左到右,一层一层的遍历结点。

提示

1、大多数情况下,我们总是习惯 先左后右。遍历的时候,左子树总是先于右子树,这种策略记为LR,相反的策略记为RL。本文中默认是采用LR。

2、 先序遍历,中序遍历,后序遍历都属于深度优先遍历。层遍历则属于广度优先遍历。深度优先遍历需要用栈实现(调用栈或者辅助栈),而广度优先遍历则需要用队列实现。

3、所谓的先、中、后,都是对于访问root结点的顺序来定义的。

构造一个简单的链式二叉树

先通过硬编码构建一个简单的下图所示的链式二叉树,后续的例子都是以这个为基础的。

A
      / \
    B   C
    / \
  D   E

class LinkedBinaryTree
{
    /**
     * 静态内部类,结点类。
     * */
    private static class Node<T>
    {
        private T element;
        private final Node<T> left;
        private final Node<T> right;

        public Node(T element , Node<T> left , Node<T> right)
        {
            this.element = element;
            this.left = left;
            this.right = right;
        }

        @SuppressWarnings("unused")
        public T getElement(){
            return element;
        }

        @SuppressWarnings("unused")
        public void setElement(T element){
            this.element = element;
        }

        public Node<T> getLeft(){
            return left;
        }

        public Node<T> getRight(){
            return right;
        }

        @Override
        public String toString(){
            return element.toString();
        }

    } // class Node end

    private Node<Character> root = null;      //root

    public LinkedBinaryTree()
    {
        creatTree();
    }

    /**
     * 通过硬编码构造一个二叉树
     * */
    private void creatTree()
    {
         /*******************
                A
               /               B   C
             /             D   E
         *********************/

        Node<Character> nodeC = new Node<Character>(‘C‘, null, null);
        Node<Character> nodeD = new Node<Character>(‘D‘, null, null);
        Node<Character> nodeE = new Node<Character>(‘E‘, null, null);

        Node<Character> nodeB = new Node<Character>(‘B‘, nodeD, nodeE);
        root  = new Node<Character>(‘A‘, nodeB, nodeC);
    }

}

定义一个遍历结点访问者接口

/**
 * 遍历的访问者接口。
 * 任何需要访问结点的类都需要实现这个接口,并通过函数visit实现遍历逻辑。
 * */
interface Visitor<T>
{
    void visit(T element);
}

因此我们的遍历函数就可以定义成这样的形式: public void xxxorderTraverse(Visitor<Character>  visitor )

先序遍历

先序遍历

先序遍历的特点是:对于任何一个结点,总是先访问这个结点本身,再去访问他的各个子树。

先序遍历的访问的第一个结点一定是整个树的root结点。

它不仅仅是二叉树特有的遍历方法,它对普通的一般树(generic tree)也适用,当用于遍历一般树时算法如下。

preorder(T,root):      //T 是遍历的树对象,root是T的根结点。
   visit(root);
   for each child w of root in T do
      preorder(T,w);

以下是针对二叉树的先序遍历算法。

递归伪代码(摘自维基百科)

preorder(node)
  if (node = null)
    return
  visit(node)
  preorder(node.left)       //这里可以做一下优化:先判断node.left是否不为null,再递归调用:preorder()
  preorder(node.right)      //这里可以做一下优化:先判断node.right是否不为null,再递归调用:preorder()

非递归伪代码(摘自维基百科)

preorder(node)
  if (node = null)
    return
  s ← empty stack
  s.push(node)
  while (not s.isEmpty())
    node ← s.pop()
    visit(node)
    //右子树先压栈,左子树再压栈。这样下一次出栈的时候,就先遍历左子树。
    if (node.right ≠ null)
      s.push(node.right)
    if (node.left ≠ null)
      s.push(node.left)

代码实现(Java)

private void _preorderTraverseRecursive(Node<Character> root , Visitor<Character> visitor )
    {
        if(root == null) return ;
        visitor.visit(root.getElement());  //访问当前root结点

        _preorderTraverseRecursive(root.getLeft(),visitor); //访问左子树
        _preorderTraverseRecursive(root.getRight(),visitor);//访问右子树

    }

    public void preorderTraverseRecursive(Visitor<Character> visitor)
    {
        _preorderTraverseRecursive(root,visitor);
    }
    /******************************************************************/

    public void preorderTraverseLoop(Visitor<Character> visitor)
    {
        if(root == null) return ;

        Deque<Node<Character>> stack = new LinkedList<Node<Character>>();  //使用双端队列代表栈
        stack.push(root);
        while(!stack.isEmpty())
        {
            Node<Character> node = stack.pop();
            visitor.visit(node.getElement());

            if(node.getRight()!=null) {
                stack.push(node.getRight());
            }
            if(node.getLeft()!=null) {
                stack.push(node.getLeft());
            }
        }

    }

中序遍历

中序遍历

对于LR中序遍历,第一个被访问的结点一定是树的最左边的那个结点。

对于RL中序遍历,第一个被访问的结点一定是树的最右边的那个结点。

中序遍历一般只针对二叉树。

递归伪代码(摘自维基百科)

inorder(node)
  if (node = null)
    return
  inorder(node.left)
  visit(node)
  inorder(node.right)

非递归伪代码(摘自维基百科)

inorder(node)
  s ← empty stack
  while (not s.isEmpty() or node ≠ null)
    if (node ≠ null)
      s.push(node)
      node ← node.left
    else
      node ← s.pop()
      visit(node)
      node ← node.right
private void _inorderTraverseRecursive(Node<Character> root , Visitor<Character> visitor )
    {
        if(root == null) return ;
        _inorderTraverseRecursive(root.getLeft() , visitor);
        visitor.visit(root.getElement());
        _inorderTraverseRecursive(root.getRight() , visitor);
    }
    public void inorderTraverseRecursive(Visitor<Character> visitor)
    {
        _inorderTraverseRecursive(root, visitor);
    }

    /**
     * 非递归的中序遍历
     * */
    public void inorderTraverseLoop(Visitor<Character> visitor)
    {
        Deque<Node<Character>> stack = new LinkedList<Node<Character>>();  //使用双端队列代表栈
        Node<Character> node = root;

        while(!stack.isEmpty() || node!=null)
        {
            if(node!=null)
            {
                stack.push(node);
                node = node.getLeft();
            }
            else
            {
                node = stack.pop();
                visitor.visit(node.getElement());
                node = node.getRight();
            }
        }
    }

后序遍历

后序遍历

后序遍历最后一个被访问的结点一定是整个树的root结点。

后序遍历的特点是:对于任何一个结点,总是先访问这个结点的各个子树,再去访问这个结点本身。

它不仅仅是二叉树特有的遍历方法,它对普通的一般树(generic tree)也适用,当用于遍历一般树时算法如下。

postorder(T,root):      //T 是遍历的树对象,root是T的根结点。
   for each child w of root in T do
      postorder(T,w);
   visit(root);

后序遍历是很有作用的。

例如,我们要统计一个文件夹的占用空间大小,用内部结点表示文件夹,用叶子结点表示文件。则应该先去统计这个文件夹下的所有的文件以及子文件夹的大小,才能得出这个顶层文件夹的大小,使用后序遍历就可以实现。

又例如,当二叉树使用二叉链表存储,我们在执行销毁操作时,必须先销毁一个结点的左右子树,再销毁这个结点本身,就可以使用后序遍历实现。

后序遍历二叉树的算法:

递归伪代码(摘自维基百科)

postorder(node)
  if (node = null)
    return
  postorder(node.left)
  postorder(node.right)
  visit(node)

非递归伪代码(摘自维基百科)

postorder(node)
  s ← empty stack
  lastNodeVisited ← null
  while (not s.isEmpty() or node ≠ null)
    if (node ≠ null)
      s.push(node)
      node ← node.left
    else
      peekNode ← s.peek()
      // if right child exists and traversing node from left child, then move right
      if (peekNode.right ≠ null and lastNodeVisited ≠ peekNode.right)
        node ← peekNode.right
      else
        visit(peekNode)
        lastNodeVisited ← s.pop()
private void _postorderTraverseRecursive(Node<Character> root , Visitor<Character> visitor)
    {
        if(root == null) return ;

        _postorderTraverseRecursive(root.getLeft(), visitor);
        _postorderTraverseRecursive(root.getRight(),visitor);
        visitor.visit(root.getElement());
    }

    public void postorderTraverseRecursive(Visitor<Character> visitor)
    {
        _postorderTraverseRecursive(root,visitor);
    }

    /**
     * 后序遍历非递归实现
     * */
    public void postorderTraverseLoop(Visitor<Character> visitor)
    {
        Deque<Node<Character>> stack = new LinkedList<Node<Character>>();  //使用双端队列代表栈
        Node<Character> lastNodeVisited = null , peekNode  =null;
        Node<Character> node = root;

        while(!stack.isEmpty() || node !=null)
        {
            if(node!=null)
            {
                stack.push(node);
                node = node.getLeft();
            }
            else
            {
                peekNode = stack.peek();
                if(peekNode.getRight() != null && lastNodeVisited!= peekNode.getRight())
                    node = peekNode.getRight();
                else
                {
                    visitor.visit(peekNode.getElement());
                    lastNodeVisited = stack.pop();
                }
            }
        }

    }

层遍历

层遍历:对应图的广度优先遍历。层遍历也适用于一般树(generic tree)。

伪代码(摘自维基百科)

levelorder(root)
  q ← empty queue
  q.enqueue(root)
  while (not q.isEmpty())
    node ← q.dequeue()
    visit(node)
    if (node.left ≠ null)
      q.enqueue(node.left)
    if (node.right ≠ null)
      q.enqueue(node.right)
public void levelorderTraverseLoop(Visitor<Character> visitor)
{

    Deque<Node<Character>> queue =  new LinkedList<>();
    Node<Character> node = null;
    queue.addLast(root);   //入队
    while(! queue.isEmpty())
    {
        node = queue.removeFirst();   //出队
        visitor.visit(node.getElement());

        if(node.getLeft()!=null)
            queue.addLast(node.getLeft());
        if(node.getRight()!=null)
            queue.addLast(node.getRight());
    }

}

给一个二叉树的图,如何快速准确的写出 先、中、后遍历序列?

以下方法均由博主亲自实践总结。希望能帮助大家。

方法1:按照调用栈的流程写出函数递归调用过程,然后即可得出遍历序列。以下用先序遍历举例。其它的同理。

 方法2:使用画图的小技巧轻松解决。

动画演示:以中序遍历为例

提示:这个方法中,如果使用的是RL 顺序,则需要做如下修改:
1、需要从行走路径末尾往前走,也就是从root结点的右边开始”行走“
2、将先序点和后序点交换位置:先序点位于结点的右边,后序点位于结点的左边。

如何由【中序+先序】或者【中序+后序】遍历画出树的形态?

提示:这种题,一定要给出中序遍历,否则是不能确定的。

前面我已经提到了不同遍历方法的规律:

1、先序遍历的访问的第一个结点一定是整个树的root结点。

2、后序遍历最后一个被访问的结点一定是整个树的root结点。

3、对于LR中序遍历,第一个被访问的结点一定是树的最左边的那个结点。

对于RL中序遍历,第一个被访问的结点一定是树的最右边的那个结点。

技巧提示:重复以下步骤

1、先由先序(或者后序)确定root结点。(先后确定根结点)

2、根据root结点和中序划分左右子树。    (root中序分两边)

题目:

前序遍历:         GDAFEMHZ

中序遍历:         ADEFGHMZ

写出后序遍历。

解:

首先,由 【前序遍历:  GDAFEMHZ】确定root为G,再由root和【中序遍历:   ADEFGHMZ】,确定 ADEF是G的左子树结点,HMZ是G的右子树结点。

然后,只看左子树【ADEF】,同样先由【前序遍历: DAFE 】 确定root结点为D , 再由 root 和【中序遍历: ADEF】 确定A是D的左子树,FE是A的右子树。

........如此重复,即可画出树的图形。

时间: 2024-12-26 01:51:51

【树4】二叉树的遍历的相关文章

树、二叉树、遍历二叉树的总结

首先介绍树: 如上图所示就是一棵树,先介绍树的几个关键名词: 节点:A.B.C.D等都叫节点 节点的度:节点有几个分支,就叫节点的度,比如节点B有2个分支,那B的度为2 终端节点(叶子):没有分支的节点,如E.F.G.H 非终端节点:有分支的节点,如A.B.D.C 节点的层次:自上而下排列层次,A为1层,B为2层,D为3层 树的度:哪个节点的度最大,这个最大的度就是树的度,如图树的度为2 树的深度:简而言之,就是树有几层,如图的树的深度为4 我们接触最多的树是二叉树 二叉树:在计算机科学中,二叉

数据结构实验五:树和二叉树

一.实验目的 巩固树和二叉树的相关知识,特别是二叉树的相关内容.学会运用灵活应用. 1.回树和二叉树的逻辑结构和存储方法,清楚掌握树和二叉树的遍历操作. 2.学习树的相关知识来解决实际问题. 3.进一步巩固程序调试方法. 4.进一步巩固模板程序设计. 二.实验时间 准备时间为第10周到第12前半周,具体集中实验时间为12周周四.2个学时. 三..实验内容 1.自己设计一个二叉树,深度最少为4,请递归算法分别用前序.中序.后序遍历输出树结点. 2.写程序判定出六枚硬币中的一枚假硬币.参照课本P13

树与二叉树基础算法的比较

一:树的创建 在数据结构中,树是以二叉树的形式储存的. 树转换为二叉树形式分为三步: ⑴加线——树中所有相邻兄弟之间加一条连线. ⑵去线——对树中的每个结点,只保留它与第一个孩子结点之间的连线,删去它与其它孩子结点之间的连线. ⑶层次调整——以根结点为轴心,将树顺时针转动一定的角度,使之层次分明. 转换后结果如图: 所以树的创建算法有两个思路: 1.将树转化为二叉树后,以二叉树中结点的关系输入而创建树. 2.直接以树中结点的关系输入,用代码转换为相应的二叉树. 第一种方法实际就是二叉树创建,只不

树(二叉树)的建立和遍历算法(一)(前序,中序,后序)

最近学习树的概念,有关二叉树的实现算法记录下来... 不过学习之前要了解的预备知识:树的概念:二叉树的存储结构:二叉树的遍历方法.. 二叉树的存储结构主要了解二叉链表结构,也就是一个数据域,两个指针域,(分别为指向左右孩子的指针),从下面程序1,二叉树的存储结构可以看出. 二叉树的遍历方法:主要有前序遍历,中序遍历,后序遍历,层序遍历.(层序遍历下一篇再讲,本篇主要讲的递归法) 如这样一个二叉树: 它的前序遍历顺序为:ABDGHCEIF(规则是先是根结点,再前序遍历左子树,再前序遍历右子树) 它

数据结构第三部分:树与树的表示、二叉树及其遍历、二叉搜索树、平衡二叉树、堆、哈夫曼树、集合及其运算

参考:浙大数据结构(陈越.何钦铭)课件 1.树与树的表示 什么是树? 客观世界中许多事物存在层次关系 人类社会家谱 社会组织结构 图书信息管理 分层次组织在管理上具有更高的效率! 数据管理的基本操作之一:查找(根据某个给定关键字K,从集合R 中找出关键字与K 相同的记录).一个自然的问题就是,如何实现有效率的查找? 静态查找:集合中记录是固定的,没有插入和删除操作,只有查找 动态查找:集合中记录是动态变化的,除查找,还可能发生插入和删除 静态查找——方法一:顺序查找(时间复杂度O(n)) int

数据结构-王道2017-第4章 树与二叉树-二叉树的遍历

typedef int ElemType; typedef struct BitNode { ElemType data; //数据域 struct BitNode *lchild, *rchild; //左右孩子指针 }BitNode,*BitTree; void visit(BitNode *b) { printf("%d ", b->data); } //无论采用哪种遍历方法,时间复杂度都是O(n),因为每个结点都访问一次且仅访问一次,递归工作栈的栈深恰好为树的深度,空间复

javascript实现数据结构: 树和二叉树,二叉树的遍历和基本操作

树型结构是一类非常重要的非线性结构.直观地,树型结构是以分支关系定义的层次结构. 树在计算机领域中也有着广泛的应用,例如在编译程序中,用树来表示源程序的语法结构:在数据库系统中,可用树来组织信息:在分析算法的行为时,可用树来描述其执行过程等等. 下面讲解的内容完整代码在这:https://github.com/LukeLin/data-structure-with-js/blob/master/Binary%20tree/BinaryTree.js 首先看看树的一些概念: 1.树(Tree)是n

数据结构与算法-----&gt;数据结构-----&gt;树-------&gt;二叉树的遍历

二叉树的遍历 第一部分 基本概念以及编程实现 概述: 遍历树,就是指按照一定的顺序访问树中的所有节点. 遍历树有三种常用方法,分别是中序遍历(inorder).前序遍历(preorder).后序遍历(postorder) 三种遍历方法的三个步骤都是相同的,只不过这三个步骤的执行顺序不同.三种遍历方式的名称的由来是根据""访问节点内容""这个步骤的执行时间来定的,这个步骤在第一步执行的是前序遍历,在第二步执行的是中序遍历,在第三步执行的是后序遍历. 1.1中序遍历(i

javascript实现数据结构: 树和二叉树的应用--最优二叉树(赫夫曼树),回溯法与树的遍历--求集合幂集及八皇后问题

赫夫曼树及其应用 赫夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,有着广泛的应用. 最优二叉树(Huffman树) 1 基本概念 ① 结点路径:从树中一个结点到另一个结点的之间的分支构成这两个结点之间的路径. ② 路径长度:结点路径上的分支数目称为路径长度. ③ 树的路径长度:从树根到每一个结点的路径长度之和. 以下图为例: A到F :结点路径 AEF : 路径长度(即边的数目) 2 : 树的路径长度:3*1+5*2+2*3=19: ④ 结点的带权路径长度:从该结点的到树的根结