二叉树面试题汇总(二)

来源:互联网 发布:b碗动作数据 编辑:程序博客网 时间:2024/05/21 21:40

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


原创粉丝点击