天天看点

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

date: 2016-08-18 9:17:00

title: 二叉树面试题汇总(二)

categories: 数据结构

版权声明:本站采用开放的[知识共享署名-非商业性使用-相同方式共享 许可协议]进行许可

所有文章出现的代码,将会出现在我的github中,名字可以根据类全名来找,我在github中的文件夹也会加目录备注。

二叉树的先序遍历

递归法

思路:

  • 二叉树的先序、中序、后序遍历使用递归法实现非常简单,因为树是由递归定义的。
  • 不管使用递归还是非递归方法实现对于二叉树的遍历,首先我们需要抓住遍历的关键,即对节点的访问顺序。
  • 使用不同的方法,对每个节点的访问顺序不同。
  • 先序遍历、中序遍历、后序遍历中,先、中、后是指根节点的访问顺序,一般二叉树分为“DLR”,其中D 代表根节点,L代表左子树,R代表右子树,先序为DLR,中序为LDR,后序为LRD
  • 掌握了上面的要领,那么实现对二叉树的先中后序遍历就非常简单了。

代码实现:

public static void preOrderTraverseByRecursion(BinaryTree root) {
        // 递归的出口
        if (root == null)
            return;
        // 1、先访问根节点,在这里只是做简单的数据输出
        System.out.print(root.getValue() + " ");
        // 2、递归访问左子节点,因为左子节点可能还有子节点
        preOrderTraverseByRecursion(root.getLeftChild());
        // 3、递归访问右子节点,理由同上
        preOrderTraverseByRecursion(root.getRightChild());
    }
           

图解:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询
二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询
二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

测试代码:

package com.xinpaninjava.btree;

public class BinaryTreeCountNodesTest {

    public static void main(String[] args) {
        BinaryTree n1 = new BinaryTree(1);
        BinaryTree n2 = new BinaryTree(2);
        BinaryTree n3 = new BinaryTree(3);
        BinaryTree n4 = new BinaryTree(4);
        BinaryTree n5 = new BinaryTree(5);
        BinaryTree n6 = new BinaryTree(6);

        n1.setLeftChild(n2);
        n1.setRightChild(n3);
        n2.setLeftChild(n4);
        n2.setRightChild(n5);
        n3.setRightChild(n6);

        BTreeTraverse.preOrderTraverseByRecursion(n1);
    }

}
           

树结构:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

运行结果

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

迭代法

思路:

  • 要使用非递归法实现二叉树的先序遍历,可以这样想,既然可以用递归实现,那么就可以用栈实现(为什么?)。
  • 即使使用非递归实现二叉树的先序遍历,也要抓住各个节点的访问顺序,在这里,最先访问的是根节点,所以当有节点入栈时,它可以看成是其子节点的根节点,那么可以直接输出该节点的数据
  • 然后再判断当前节点有无左右子节点,这里要注意栈的特点,先存右子树,再存左子树
  • 如果还不能理解,可以通过自己随意画一棵二叉树,对着进栈出栈的顺序来看代码。或者自己根据先序遍历的顺序来写代码也行,重要的是想法。

代码实现:

/**
 * 非递归法 遍历二叉树
 * 
 * @param root
 */
public static void preOrderTraverseByIterative(BinaryTree root) {
    // 判断当前节点是否为空,为空就空操作
    if (root == null)
        return;
    // 定义一个栈来存储节点,一个变量来记录当前节点
    Stack<BinaryTree> stack = new Stack<BinaryTree>();
    BinaryTree currentNode = root;
    stack.push(currentNode);
    // 进入循环体,条件:栈不为空
    while (!stack.isEmpty()) {
        // 栈顶节点出栈并赋值给当前节点
        currentNode = stack.pop();
        // 输出根节点数据
        System.out.print(currentNode.getValue() + ":");
        // 判断当前节点有无右子树,有入栈
        if (currentNode.getRightChild() != null)
            stack.push(currentNode.getRightChild());
        // 判断当前节点有无左子树,有入栈
        if (currentNode.getLeftChild() != null)
            stack.push(currentNode.getLeftChild());
    }
}
           

树结构:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

运行结果:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

二叉树的中序遍历

递归法

思路:

  • 抓住访问顺序,中序遍历:LDR,代码实现可以参照先序遍历。
  • 首先得到根节点,但是中序遍历要先访问左子节点,所以先递归得到子节点,然后再访问根节点,最后访问右子节点

图解:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

代码实现:

/**
 * 使用递归中序遍历二叉树
 * 
 * @param root
 */
public static void inOrderTraverseByRecursion(BinaryTree root) {
    // 递归出口
    if (root == null)
        return;
    // 递归访问左子树
    inOrderTraverseByRecursion(root.getLeftChild());
    // 访问根节点
    System.out.print(root.getValue() + " ");
    // 递归访问右子树
    inOrderTraverseByRecursion(root.getRightChild());
}
           

测试代码:

package com.xinpaninjava.btree;

public class BinaryTreeCountNodesTest {

    public static void main(String[] args) {
        BinaryTree n1 = new BinaryTree(1);
        BinaryTree n2 = new BinaryTree(2);
        BinaryTree n3 = new BinaryTree(3);
        BinaryTree n4 = new BinaryTree(4);
        BinaryTree n5 = new BinaryTree(5);
        BinaryTree n6 = new BinaryTree(6);

        n1.setLeftChild(n2);
        n1.setRightChild(n3);
        n2.setLeftChild(n4);
        n2.setRightChild(n5);
        n3.setRightChild(n6);

        BTreeTraverse.inOrderTraverseByRecursion(n1);
    }

}
           

运行结果:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

迭代

思路:

  • 中序遍历,就先要得到左叶子节点,定义一个栈用来存储左节点(同时也可能是根节点),一个变量来指向当前节点(开始是根节点)
  • 得到左叶子节点后,访问该节点,在本文中,访问的意思是:该节点出栈,并输出该节点的数据,
  • 该节点出栈之后,意味着现在栈顶节点为刚刚出栈节点的根节点
  • 把当前节点指向刚刚出栈节点的右子节点,到这里再返回上面的循环中,如果当前节点(此时指向了右子节点)为空,直接弹出栈顶节点(刚刚出栈节点的根节点),

图解:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询
二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询
二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

实现代码:

public static void inOrderTraverseByIterative(BinaryTree root) {
        // 首先判断根节点是否为空,为空空操作
        if (root == null)
            return;
        // 创建一个栈来存储节点,创建一个节点来指向当前节点
        Stack<BinaryTree> stack = new Stack<BinaryTree>();
        BinaryTree currentNode = root;
        // 定义一个循环,循环体为:
        while (true) {
            // 1、循环把左子节点的所有左节点加入栈,条件是当前节点不为空
            while (currentNode != null) {
                stack.push(currentNode);
                currentNode = currentNode.getLeftChild();
            }
            // 2、当栈为空时跳出总循环,为什么在这里跳?不在外层直接定义条件?
            // 因为刚刚开始栈为空,并且是在循环体内对栈进行压栈操作
            if (stack.isEmpty())
                break;
            // 3、访问节点
            currentNode = stack.pop();
            System.out.print(currentNode.getValue() + " ");
            // 4、把当前节点指向刚刚弹栈的右子节点
            currentNode = currentNode.getRightChild();
        }
    }
           

测试代码:

情况一:根节点最后一个左子节点是叶子节点

树结构:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询
package com.xinpaninjava.btree;

public class BinaryTreeCountNodesTest {

    public static void main(String[] args) {
        BinaryTree n1 = new BinaryTree(1);
        BinaryTree n2 = new BinaryTree(2);
        BinaryTree n3 = new BinaryTree(3);
        BinaryTree n4 = new BinaryTree(4);
        BinaryTree n5 = new BinaryTree(5);
        BinaryTree n6 = new BinaryTree(6);
        // BinaryTree n7 = new BinaryTree(7);
        // BinaryTree n8 = new BinaryTree(8);

        n1.setLeftChild(n2);
        n1.setRightChild(n3);
        n2.setLeftChild(n4);
        n2.setRightChild(n5);
        n3.setRightChild(n6);

        // n4.setRightChild(n7);
        // n7.setLeftChild(n8);

        BTreeTraverse.inOrderTraverseByIterative(n1);
    }

}
           

运行结果:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

情况二:左子树最后一个左子节点不是叶子节点

树的结构:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

测试代码:

package com.xinpaninjava.btree;

public class BinaryTreeCountNodesTest {

    public static void main(String[] args) {
        BinaryTree n1 = new BinaryTree(1);
        BinaryTree n2 = new BinaryTree(2);
        BinaryTree n3 = new BinaryTree(3);
        BinaryTree n4 = new BinaryTree(4);
        BinaryTree n5 = new BinaryTree(5);
        BinaryTree n6 = new BinaryTree(6);
        BinaryTree n7 = new BinaryTree(7);
        BinaryTree n8 = new BinaryTree(8);

        n1.setLeftChild(n2);
        n1.setRightChild(n3);
        n2.setLeftChild(n4);
        n2.setRightChild(n5);
        n3.setRightChild(n6);

        n4.setRightChild(n7);
        n7.setLeftChild(n8);

        BTreeTraverse.inOrderTraverseByIterative(n1);
    }

}
           

运行结果:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

二叉树的后序遍历

递归法

思路:

后续遍历节点访问顺序: LRD,思路可以参考先序、中序遍历~

代码实现:

public static void postOrderTraverseByRecursion(BinaryTree root) {
    // 使用递归方法后序遍历二叉树

    // 递归出口
    if (root == null)
        return;
    // 递归访问左子节点
    postOrderTraverseByRecursion(root.getLeftChild());
    // 递归访问右子节点
    postOrderTraverseByRecursion(root.getRightChild());
    // 访问根节点
    System.out.print(root.getValue() + " ");
}
           

图解:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

测试代码:

package com.xinpaninjava.btree;

public class BinaryTreeCountNodesTest {

    public static void main(String[] args) {
        BinaryTree n1 = new BinaryTree(1);
        BinaryTree n2 = new BinaryTree(2);
        BinaryTree n3 = new BinaryTree(3);
        BinaryTree n4 = new BinaryTree(4);
        BinaryTree n5 = new BinaryTree(5);
        BinaryTree n6 = new BinaryTree(6);

        n1.setLeftChild(n2);
        n1.setRightChild(n3);
        n2.setLeftChild(n4);
        n2.setRightChild(n5);
        n3.setRightChild(n6);

        BTreeTraverse.postOrderTraverseByRecursion(n1);
    }

}
           

树结构:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

运行结果:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

迭代法

思路:

  • 后续遍历对节点的访问顺序是LRD,就在我百思不得其解的时候,我看出了一些破绽
  • 它就是先序遍历的倒序版,既然上面我们已经解决了先序遍历的迭代方法,那么我们只需要多弄一个栈,把里面的节点倒过来,不就实现了后序遍历吗? 够给力吗?
  • 可能有些人会说,先序遍历是DLR啊,后序遍历是LRD,怎么一样?如果你有这样的疑问,那我只能说你不是真的懂先序遍历
  • 先序遍历使用迭代方式实现的时候,不是要把输出当前信息的节点的左右子节点存到栈里面吗?只需要调整左右节点进栈的顺序即可!

图解:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询
二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

代码实现:

// 使用非递归方式实现二叉树的后序遍历
public static void postOrderTraverseByIterative(BinaryTree root) {
    // 首先判断传进来的根节点是否为空,为空空操作
    if (root == null)
        return;
    // 定义两个栈,一个用来实现”DRL"的先序遍历,另外一个栈用来颠倒前面栈的顺序
    Stack<BinaryTree> stack = new Stack<BinaryTree>();
    Stack<BinaryTree> outStack = new Stack<BinaryTree>();
    BinaryTree currentNode = root;
    stack.push(currentNode);
    while (!stack.isEmpty()) {
        // 既然要实现先序遍历,当然是先访问根节点,把先出栈的放到另外一个栈
        currentNode = stack.pop();
        outStack.push(currentNode);
        // 再判断左子节点,不为空入栈1
        if (currentNode.getLeftChild() != null)
            stack.push(currentNode.getLeftChild());
        // 判断右子节点,不为空,入栈1
        if (currentNode.getRightChild() != null)
            stack.push(currentNode.getRightChild());
    }
    // 输出outStack栈中的节点
    while (!outStack.isEmpty()) {
        System.out.print(outStack.pop().getValue() + " ");
    }
}
           

测试代码:

package com.xinpaninjava.btree;

public class BinaryTreeCountNodesTest {

    public static void main(String[] args) {
        BinaryTree n1 = new BinaryTree(1);
        BinaryTree n2 = new BinaryTree(2);
        BinaryTree n3 = new BinaryTree(3);
        BinaryTree n4 = new BinaryTree(4);
        BinaryTree n5 = new BinaryTree(5);
        BinaryTree n6 = new BinaryTree(6);

        n1.setLeftChild(n2);
        n1.setRightChild(n3);
        n2.setLeftChild(n4);
        n2.setRightChild(n5);
        n3.setRightChild(n6);

        BTreeTraverse.postOrderTraverseByIterative(n1);
    }

}
           

树结构:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

运行结果:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

层次查询

非递归方式

思路:

  • 按层次遍历,讲的也是节点访问的顺序。
  • 这时候使用栈就不合适了,应该使用队列,先访问到的可以先输出
  • 先把根节点入队, 当队列不为空的时候,队头节点出队,再判断当前出队节点是否有左右子节点,有就入队

代码实现:

// 非递归方式实现层次遍历
    public static void levelTraverseByIterative(BinaryTree root) {
        if (root == null)
            return;
        // 定义一个队列来实现
        Queue<BinaryTree> queue = new LinkedList<BinaryTree>();
        // 首先把根节点入队
        queue.add(root);
        BinaryTree currentNode = root;
        while (!queue.isEmpty()) {
            // 入队再出队
            currentNode = queue.remove();
            System.out.print(currentNode.getValue() + " ");
            // 然后把左右子节点入队
            if (currentNode.getLeftChild() != null)
                queue.add(currentNode.getLeftChild());
            if (currentNode.getRightChild() != null)
                queue.add(currentNode.getRightChild());
        }
    }

           

图解:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

测试代码:

package com.xinpaninjava.btree;

public class BinaryTreeCountNodesTest {

    public static void main(String[] args) {
        BinaryTree n1 = new BinaryTree(1);
        BinaryTree n2 = new BinaryTree(2);
        BinaryTree n3 = new BinaryTree(3);
        BinaryTree n4 = new BinaryTree(4);
        BinaryTree n5 = new BinaryTree(5);
        BinaryTree n6 = new BinaryTree(6);

        n1.setLeftChild(n2);
        n1.setRightChild(n3);
        n2.setLeftChild(n4);
        n2.setRightChild(n5);
        n3.setRightChild(n6);

        BTreeTraverse.levelTraverseByIterative(n1);
    }

}
           

树结构:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

运行结果:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

改进

在上面的运行结果中我们可以看到,虽然是按分层形式访问的,可是结果并没有显示哪些节点属于哪一层,很容易让人觉得好像这些节点都是属于同一层节点,那么怎样实现让函数在算完一层之后输出一个换行呢?

思路:

  • 可以借助前面求树深度迭代方法实现的思路,在这里定义两个变量,一个用来记住当前层次的节点数,另外一个用来记录下一层的节点数
  • 当记录当前层节点数为0时,输出换行,并且在每次把节点出队和入队,都对这两个变量进行修改,这样就可以实现分层输出!

代码实现:

// 非递归方式实现层次遍历
public static void levelTraverseByIterative(BinaryTree root) {
    if (root == null)
        return;
    // 定义一个队列来实现
    Queue<BinaryTree> queue = new LinkedList<BinaryTree>();

    // 新定义的两个变量!大家可以通过名字来了解其作用
    int currentLevelNodes = 1, nextLevelNodes = 0;
    // 首先把根节点入队

    queue.add(root);
    BinaryTree currentNode = root;
    while (!queue.isEmpty()) {
        // 入队再出队
        currentNode = queue.remove();
        // 在出队时,对当前层节点数进行修改!
        currentLevelNodes--;
        System.out.print(currentNode.getValue() + " ");
        // 然后把左右子节点入队
        if (currentNode.getLeftChild() != null) {
            queue.add(currentNode.getLeftChild());
            // 在入队时,对下一层节点数进行修改!
            nextLevelNodes++;
        }
        if (currentNode.getRightChild() != null) {
            queue.add(currentNode.getRightChild());
            // 在入队时,对下一层节点数进行修改!
            nextLevelNodes++;
        }

        // 当前层节点数为0时,输出换行,并且把“这一行移到下一行”
        // 其实就是把下一行的节点数赋值给当前行节点数
        if (currentLevelNodes == 0) {
            System.out.println();
            currentLevelNodes = nextLevelNodes;
        }
    }
}
           

测试代码还是一样,即树结构一样。

运行结果:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

递归

思路:

  • 向要分层的遍历二叉树,首先想能不能用什么容器把节点”分层“存储?
  • 定义一个二维数组,每一层存放每一层的节点,最后遍历数组即可得到每一层的节点
  • 对于每一个传进来的节点,首先判断其是否为空,为空空操作
  • 不为空根据上一次的操作结果,加上当前节点,存入指定的层数

图解:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

实现代码:

// 递归方式实现层次遍历
public static void levelTraverseByRecursion(BinaryTree root) {
    // 判断根节点是否为空,为空空操作
    if (root == null)
        return;
    // 定义一个双层链表
    ArrayList<ArrayList<Integer>> list = new ArrayList<ArrayList<Integer>>();
    // 使用bfs
    recursion(root, 0, list);
    // 输出链表
    System.out.println(list);
}

/**
 * 按层次递归遍历二叉树
 * 
 * @param root
 *            当前节点
 * @param level
 *            层数
 * @param list
 *            要存储到的容器
 */
private static void recursion(BinaryTree root, int level,
        ArrayList<ArrayList<Integer>> list) {
    // 当前节点为空,空操作
    if (root == null)
        return;
    // 若当前的层数大于等于链表的“层数”,那么为链表“加层”
    if (level >= list.size()) {
        list.add(new ArrayList<Integer>());
    }
    // 把当前节点存到指定层数中
    list.get(level).add(root.getValue());
    // 递归把左子节点对应的树存入链表
    recursion(root.getLeftChild(), level + 1, list);
    // 递归把右子节点对应的树存入链表
    recursion(root.getRightChild(), level + 1, list);
}
           

测试代码:

package com.xinpaninjava.btree;

public class BinaryTreeCountNodesTest {

    public static void main(String[] args) {
        BinaryTree n1 = new BinaryTree(1);
        BinaryTree n2 = new BinaryTree(2);
        BinaryTree n3 = new BinaryTree(3);
        BinaryTree n4 = new BinaryTree(4);
        BinaryTree n5 = new BinaryTree(5);
        BinaryTree n6 = new BinaryTree(6);

        n1.setLeftChild(n2);
        n1.setRightChild(n3);
        n2.setLeftChild(n4);
        n2.setRightChild(n5);
        n3.setRightChild(n6);

        BTreeTraverse.levelTraverseByRecursion(n1);
    }

}
           

树结构:

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

运行结果

二叉树面试题汇总(二)二叉树的先序遍历二叉树的中序遍历二叉树的后序遍历层次查询

【全文完】

参考资料:

Pre-Order Traversal of Binary Tree (Iterative)

Inorder Binary Tree Traversal (Iteratively)

Print a Binary Tree in Post Order Traversal (Iterative using Stack)

二叉树后序遍历的非递归实现

二叉树前序、中序、后序遍历非递归写法的透彻解析

数据结构(C语言版)(第2版)-5.3.1遍历二叉树

郝斌数据结构-第67-72个视频,提取密码:hvzr

继续阅读