二叉树(一):深度优先遍历与广度优先遍历

前言:二叉树是一种非常高效的数据结构,树结构可以高效地查找和搜索语义,比如k近邻算法中就用到了kd树来存储训练数据,大大提高了k近邻搜索的效率,又比如电脑的文件管理器,都采用了树结构,通过树查找会变得十分快捷。总之,二叉树这种数据结构的应用非常广泛,能够大大提高搜索效率,本文将全面讲解二叉树结构以及二叉树的相关算法,相信会对大家数据结构与算法的学习产生很大的帮助。

目录

1、二叉树基本概念

2、二叉树的先序、中序、后序遍历

2.1递归序

2.1.1先序遍历

2.1.2中序遍历

2.1.3后序遍历

2.2非递归序

2.2.1先序遍历

2.2.2中序遍历

2.2.3后序遍历

3、二叉树的深度优先遍历和广度优先遍历

3.1深度优先遍历

3.2广度优先遍历


1、二叉树基本概念

       二叉树是一种特殊的树结构,它的每个节点的度最大为2,有左子节点和右子节点之分,且子树的顺序不能颠倒,因此二叉树是有序树。二叉树中每个节点都包含自身存放的数据value、左子节点left、右子节点right这三种基本信息,子节点也是这样的信息结构,像这样不断的延伸下去就形成了二叉树结构,如图1所示,①-⑩分别代表10个节点。

 图1

另外为了方便,可以专门写一个类来代表树中的节点,代码如下:

class Node {
    int value;        //存放的数据
    Node left;        //左子节点
    Node right;       //右子节点

    public Node() {        //无参构造函数
    }

    public Node(int value) {        //有参构造函数
        this.value = value;
    }
}

2、二叉树的先序、中序、后序遍历

        二叉树的遍历方法有很多种,这里讲解一下先序、中序、后序这三种遍历方法,这三种遍历方法又可以采用递归和非递归两种思想进行代码实现,采用递归思想的代码量非常少,但是可能不好理解,而采用非递归思想的代码量非常多,但是非常好理解。

2.1递归序

        在讲解之前,先帮大家理一下二叉树在递归时的过程。举一个例子:打印图2中二叉树各节点存放的数据。

图2

递归时,其顺序依次是:1-2-4-4-4-2-5-5-5-2-1-3-6-6-6-3-7-7-7-3-1,所谓先序遍历打印,就是递归顺序中各节点第一次出现时就打印各节点中存放的数据;中序遍历打印,就是递归顺序中各节点第二次出现时打印各节点中存放的数据;后序遍历打印,就是递归顺序中各节点第三次出现时打印各节点中存放的数据。

2.1.1先序遍历

       依据上面介绍的,先序遍历各节点的打印顺序是:1-2-4-5-3-6-7。不难发现先序遍历在所有子树中都是按头节点=》左子节点=》右子节点的顺序进行遍历的,其先序遍历的递归代码如下:

    //1.先序遍历打印
    public void preOrderRecur(Node head) {
        if (head == null) {
            return;
        }
        System.out.print(head.value + " ");
        preOrderRecur(head.left);
        preOrderRecur(head.right);
    }

2.1.2中序遍历

        中序遍历各节点的打印顺序是:4-2-5-1-6-3-7。不难发现中序遍历在所有子树中都是按左子节点=》头节点=》右子节点的顺序进行遍历的,其中序遍历的递归代码如下:

//2.中序遍历打印
    public void inOrderRecur(Node head) {
        if (head == null) {
            return;
        }
        inOrderRecur(head.left);
        System.out.print(head.value + " ");
        inOrderRecur(head.right);
    }

2.1.3后序遍历

        后序遍历各节点的打印顺序是:4-5-2-6-7-3-1。不难发现后序遍历在所有子树中都是按左子节点=》右子节点=》头节点的顺序进行遍历的,其后序遍历的递归代码如下:

 //3.后序遍历打印
    public void behindOrderRecur(Node head) {
        if (head == null) {
            return;
        }
        behindOrderRecur(head.left);
        behindOrderRecur(head.right);
        System.out.print(head.value + " ");
    }

2.2非递归序

2.2.1先序遍历

        先序遍历:头节点=》左子节点=》右子节点

算法的具体实现过程如下(开辟一个栈空间):

①先把头节点压入栈中;

②while(栈非空):

                         1)从栈中弹出一个节点cur

                         2)打印或者其他操作处理cur

                         3)先把cur右孩子压入栈,再把cur左孩子压入栈(条件是cur有左右孩子)

注意:栈的特点是后进先出

先序遍历的非递归代码如下:

 //1.先序打印
    public void perOrderUnrecur(Node head) {
        System.out.println("pre-order: ");
        if (head != null) {
            Stack<Node> stack = new Stack<>();
            Node cur;
            stack.add(head);
            while (!stack.isEmpty()) {
                cur = stack.pop();
                System.out.print(cur.value + " ");
                if (cur.right != null) {
                    stack.push(cur.right);
                }
                if (cur.left != null) {
                    stack.push(cur.left);
                }
            }
        }
        System.out.println();
    }

2.2.2中序遍历

        中序遍历:左子节点=》头节点=》右子节点

算法的具体实现过程如下(开辟一个栈空间):

①把二叉树根节点head所有子树的左孩子压入栈,从head开始压,然后head的左孩子、head左孩子的左孩子……

②while(栈非空):

                 1)从栈中弹出一个节点cur

                 2)打印或者其他操作处理cur

                 3)把cur右子树所有子树的左孩子压入栈,从cur的右孩子开始压,然后cur右孩子的左孩子, 然后cur右孩子的左孩子的左孩子,……(没有的话就不压入)

中序遍历的非递归代码如下:

 //2.中序打印
    public void inOrderUnrecur(Node head) {
        System.out.println("in-order: ");
        if (head != null) {
            Node temp1 = head;
            Stack<Node> stack = new Stack<>();
            while (temp1 != null) {
                stack.push(temp1);
                temp1 = temp1.left;
            }
            Node cur;
            Node temp2;
            while (!stack.isEmpty()) {
                cur = stack.pop();
                System.out.print(cur.value + " ");
                temp2 = cur.right;
                while (temp2 != null) {
                    stack.push(temp2);
                    temp2 = temp2.left;
                }
            }
        }
        System.out.println();
    }

2.2.3后序遍历

        后序遍历:左子节点=》右子节点=》头节点

算法的具体实现过程如下(开辟两个栈空间,栈1:工具,栈2:收集):

①把头节点压入栈1

②while(栈1非空):

                 1)从栈1中弹出一个节点cur

                 2)将cur压入栈2

                 3)先把cur的左孩子压入栈1,再把cur右孩子压入栈1(条件是cur有左右孩子) ③while(栈2非空):

                 1)从栈2中弹出一个节点node

                 2)打印或其他操作处理node

后序遍历的非递归代码如下:

//3.后序打印
    public void behindOrderUnrecur(Node head) {
        System.out.println("behind-order: ");
        if (head != null) {
            Stack<Node> stack1 = new Stack<>();
            Stack<Node> stack2 = new Stack<>();
            Node cur;
            stack1.add(head);
            while (!stack1.isEmpty()) {
                cur = stack1.pop();
                stack2.push(cur);
                if (cur.left != null) {
                    stack1.push(cur.left);
                }
                if (cur.right != null) {
                    stack1.push(cur.right);
                }
            }
            Node node;
            while (!stack2.isEmpty()) {
                node = stack2.pop();
                System.out.print(node.value + " ");
            }
        }
        System.out.println();
    }

3、二叉树的深度优先遍历和广度优先遍历

        二叉树深度优先遍历的非递归通用做法是采用栈(后进先出),广度优先遍历的非递归通用做法是采用队列(先进先出)

3.1深度优先遍历

        深度优先遍历就是指对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。二叉树的深度优先遍历比较特殊,可以细分为先序遍历,中序遍历,后序遍历,这些遍历方法前面都讲了,这里就不赘述了。

3.2广度优先遍历

        广度优先遍历又叫宽度优先遍历、层次遍历,指从上往下对每一层依次访问,在每一层中从左往右访问节点,访问完就进入下一层,直到没有节点可以访问为止。广度优先遍历的算法思路如下:

①把头节点head放入队列中;

②while(队列非空):

                 1)从队列中弹出一个节点cur;

                 2)打印或者其他操作处理cur;

                 3)先把cur的左孩子(如果有的话)放入队列,再把cur的右孩子(如果有的话)放入队列。

广度优先遍历的代码如下:

//二叉树的广度优先遍历如下:
    public void widthOrder(Node head) {
        if (head == null) {
            return;
        }
        Queue<Node> queue = new LinkedList<>();//java中LinkedList(原理是用双向链表实现队列)就是队列
        queue.add(head);
        Node cur;
        while (!queue.isEmpty()) {
            cur = queue.poll();
            System.out.print(cur.value + " ");
            if (cur.left != null) {
                queue.add(cur.left);
            }
            if (cur.right != null) {
                queue.add(cur.right);
            }
        }
    }

猜你喜欢

转载自blog.csdn.net/Mike_honor/article/details/125828311