继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

二叉树的常用算法

JavaEdge
关注TA
已关注
手记 394
粉丝 1.4万
获赞 1475

节点访问的次序,忽略打印行为
如果将打印安排在同个数字第一次被访问时,即先序遍历
第二次即中序遍历
第三次即后序遍历
现二叉树的先序、中序、后序遍历,包括递归方式和非递归
方式
二叉树结构定义

public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int data) {
            this.value = data;
        }
    }
递归
先序遍历
   /**
     * 先序遍历
     *
     * @param head
     */
    public static void preOrderRecur(Node head) {
        if (head == null) {
            return;
        }
        System.out.print(head.value + " ");
        preOrderRecur(head.left);
        preOrderRecur(head.right);
    }
中序遍历
   /**
     * 中序遍历
     *
     * @param head
     */
    public static void inOrderRecur(Node head) {
        if (head == null) {
            return;
        }
        inOrderRecur(head.left);
        System.out.print(head.value + " ");
        in
后序遍历
   /**
     * 后序遍历
     *
     * @param head
     */
    public static void posOrderRecur(Node head) {
        if (head == null) {
            return;
        }
        posOrderRecur(head.left);
        posOrderRecur(head.right);
        System.out.print(head.value + " ");
    }
2 非递归
2.1 先序遍历非递归
public static void preOrderUnRecur(Node head) {
        System.out.print("pre-order: ");
        if (head != null) {
            //构造栈
            Stack<Node> stack =new Stack<>();
            //压栈以当前结点为头,变成先序的逆序
            stack.add(head);
            while (!stack.isEmpty()) {
                //弹栈
                head = stack.pop();
                System.out.print(head.value + " ");
                /**
                 * 有右孩子就先压,否则压左
                 */
                //右孩子非空时
                if (head.right != null) {
                    //压栈右孩子
                    stack.push(head.right);
                }
                //左孩子非空时
                if (head.left != null) {
                    //压栈左孩子
                    stack.push(head.left);
                }
            }
        }
        System.out.println();
    }
2.2 中序遍历非递归
   /**
     * 中序遍历的非递归版本
     *
     * @param head
     */
    public static void inOrderUnRecur(Node head) {
        System.out.print("in-order: ");
        if (head != null) {
            //构造栈
            Stack<Node> stack =new Stack<>();
            //当前结点为空,从栈弹一个打印,当前结点向右跑
            // 非空,则压栈,向左跑
            while (!stack.isEmpty() || head != null) {
                //会把当前结点的左孩子全都压栈!
                if (head != null) {
                    stack.push(head);
                    head = head.left;
                } else {
                    head = stack.pop();
                    System.out.print(head.value + " ");
                    head = head.right;
                }
            }
        }
        System.out.println();
    }

整棵树都被左边界分解,然后栈的逆序打印即可
再怎么解释都需要自己的代码理解!!!中序遍历非递归也是最难理解的!

2.3 后序遍历非递归

即要实现左右中,逆序中左右其实根据先序遍历非递归改变左右压栈顺序即可,然后该打印时不打印,而是放在辅助栈中,就成了左右中顺序,打印之!

   /**
     * 后序遍历的非递归版本1
     *
     * @param head
     */
    public static void posOrderUnRecur1(Node head) {
        System.out.print("pos-order: ");
        if (head != null) {
            Stack<Node> s1 =new Stack<>();
            //辅助栈
            Stack<Node> s2 =new Stack<>();
            s1.push(head);
            while (!s1.isEmpty()) {
                head = s1.pop();
                //打印时,不让其打印,而是放在辅助栈
                s2.push(head);
                //先压左
                if (head.left != null) {
                    s1.push(head.left);
                }
                //再压右
                if (head.right != null) {
                    s1.push(head.right);
                }
            }
            while (!s2.isEmpty()) {
                //最后打印辅助栈即可!
                System.out.print(s2.pop().value + " ");
            }
        }
        System.out.println();
    }
   /**
     * 后序遍历的非递归版本2
     * 
     * @param h
     */
    public static void posOrderUnRecur2(Node h) {
        System.out.print("pos-order: ");
        if (h != null) {
            Stack<Node> stack =new Stack<>();
            stack.push(h);
            Node c = null;
            while (!stack.isEmpty()) {
                c = stack.peek();
                if (c.left != null && h != c.left && h != c.right) {
                    stack.push(c.left);
                } else if (c.right != null && h != c.right) {
                    stack.push(c.right);
                } else {
                    System.out.print(stack.pop().value + " ");
                    h = c;
                }
            }
        }
        System.out.println();
    }
3 小结

无论递归非递归,本质都是使用了栈ADT,由于二叉树本身只有向下的路径,所以需要有支持回去的路径,栈即为天之骄子!!!二叉树整体是向下的,当遍历到某一结点时需要回去时,就是刚刚好逆序的栈ADT!

福利函数,打印正确的二叉树,用于验证

/**
 * @author Shusheng Shi
 */
public class PrintBinaryTree {

    public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int data) {
            this.value = data;
        }
    }

    public static void printTree(Node head) {
        System.out.println("Binary Tree:");
        printInOrder(head, 0, "H", 17);
        System.out.println();
    }

    public static void printInOrder(Node head, int height, String to, int len) {
        if (head == null) {
            return;
        }
        printInOrder(head.right, height + 1, "v", len);
        String val = to + head.value + to;
        int lenM = val.length();
        int lenL = (len - lenM) / 2;
        int lenR = len - lenM - lenL;
        val = getSpace(lenL) + val + getSpace(lenR);
        System.out.println(getSpace(height * len) + val);
        printInOrder(head.left, height + 1, "^", len);
    }

    public static String getSpace(int num) {
        String space = " ";
        StringBuffer buf = new StringBuffer("");
        for (int i = 0; i < num; i++) {
            buf.append(space);
        }
        return buf.toString();
    }

    public static void main(String[] args) {
        Node head = new Node(1);
        head.left = new Node(-222222222);
        head.right = new Node(3);
        head.left.left = new Node(Integer.MIN_VALUE);
        head.right.left = new Node(55555555);
        head.right.right = new Node(66);
        head.left.left.right = new Node(777);
        printTree(head);

        head = new Node(1);
        head.left = new Node(2);
        head.right = new Node(3);
        head.left.left = new Node(4);
        head.right.left = new Node(5);
        head.right.right = new Node(6);
        head.left.left.right = new Node(7);
        printTree(head);

        head = new Node(1);
        head.left = new Node(1);
        head.right = new Node(1);
        head.left.left = new Node(1);
        head.right.left = new Node(1);
        head.right.right = new Node(1);
        head.left.left.right = new Node(1);
        printTree(head);

    }

}
4 实战coding

在二叉树中找到一个节点的后继节点

现在有一种新的二叉树节点类型如下:

public class Node { 
     public int value; 
     public Node left;
     public Node right; 
     //可找到父结点,头结点指空
     public Node parent;
     public Node(int data) { 
          this.value = data; 
     }
}

该结构比普通二叉树节点结构多了一个指向父节点的parent指针。
假设有一 棵Node类型的节点组成的二叉树,树中每个节点的parent指针都正确地指向 自己的父节点,头节点的parent指向null。
只给一个在二叉树中的某个节点 node,请实现返回node的后继节点的函数。
在二叉树的中序遍历的序列中, node的下一个节点叫作node的后继节点
后继结点示例
前驱结点同理!不赘述

/**
 * @author Shusheng Shi
 */
public class SuccessorNode {

    public static class Node {
        public int value;
        public Node left;
        public Node right;
        public Node parent;

        public Node(int data) {
            this.value = data;
        }
    }

    /**
     * 如果是父节点的左孩子,就直接打印父节点
     * 否则找父节点的父节点,看父节点是否为其父的左孩子
     * 一路上行
     * @param node
     * @return
     */
    public static Node getSuccessorNode(Node node) {
        if (node == null) {
            return node;
        }
        //当前节点右孩子非空,说明有右子树
        if (node.right != null) {
            //直接找右子树的最左结点
            return getLeftMost(node.right);
            //当前节点右孩子为空
        } else {
            //取得其父结点
            Node parent = node.parent;
            //当前结点为其父左孩子时退出循环,可以简单认为整棵树为空节点的左孩子
            while (parent != null && parent.left != node) {
                //否则两指针同时一路上行
                node = parent;
                parent = node.parent;
            }
            return parent;
        }
    }

    /**
     * 给定结点子树的最左结点
     *
     * @param node 某棵子树的头部
     * @return
     */
    public static Node getLeftMost(Node node) {
        if (node == null) {
            return node;
        }
        while (node.left != null) {
            //一路左行即可
            node = node.left;
        }
        return node;
    }

    public static void main(String[] args) {
        Node head = new Node(6);
        head.parent = null;
        head.left = new Node(3);
        head.left.parent = head;
        head.left.left = new Node(1);
        head.left.left.parent = head.left;
        head.left.left.right = new Node(2);
        head.left.left.right.parent = head.left.left;
        head.left.right = new Node(4);
        head.left.right.parent = head.left;
        head.left.right.right = new Node(5);
        head.left.right.right.parent = head.left.right;
        head.right = new Node(9);
        head.right.parent = head;
        head.right.left = new Node(8);
        head.right.left.parent = head.right;
        head.right.left.left = new Node(7);
        head.right.left.left.parent = head.right.left;
        head.right.right = new Node(10);
        head.right.right.parent = head.right;

        Node test = head.left.left;
        System.out.println(test.value + " next: " + getSuccessorNode(test).value);
        test = head.left.left.right;
        System.out.println(test.value + " next: " + getSuccessorNode(test).value);
        test = head.left;
        System.out.println(test.value + " next: " + getSuccessorNode(test).value);
        test = head.left.right;
        System.out.println(test.value + " next: " + getSuccessorNode(test).value);
        test = head.left.right.right;
        System.out.println(test.value + " next: " + getSuccessorNode(test).value);
        test = head;
        System.out.println(test.value + " next: " + getSuccessorNode(test).value);
        test = head.right.left.left;
        System.out.println(test.value + " next: " + getSuccessorNode(test).value);
        test = head.right.left;
        System.out.println(test.value + " next: " + getSuccessorNode(test).value);
        test = head.right;
        System.out.println(test.value + " next: " + getSuccessorNode(test).value);
        test = head.right.right; // 10's next is null
        System.out.println(test.value + " next: " + getSuccessorNode(test));
    }

}
5序列化和反序列化

怎么序列化的就怎么反序列化
 按层序列化示意

import java.util.LinkedList;
import java.util.Queue;

/**
 * @author Shusheng Shi
 */
public class SerializeAndReconstructTree {

    public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int data) {
            this.value = data;
        }
    }

    public static String serialByPre(Node head) {
        if (head == null) {
            return "#!";
        }
        String res = head.value + "!";
        res += serialByPre(head.left);
        res += serialByPre(head.right);
        return res;
    }

    public static Node reconByPreString(String preStr) {
        String[] values = preStr.split("!");
        //加入队列,方便依次出队
        Queue<String> queue =new LinkedList<>();
        for (int i = 0; i != values.length; i++) {
            queue.offer(values[i]);
        }
        return reconPreOrder(queue);
    }

    /**
     * 用给定队列建树
     *
     * @param queue
     * @return
     */
    public static Node reconPreOrder(Queue<String> queue) {
        //出队一个
        String value = queue.poll();
        //空树
        if (value.equals("#")) {
            return null;
        }
        //非空时建立一个头结点,中-左-右顺序
        Node head = new Node(Integer.valueOf(value));
        //左子树递归
        head.left = reconPreOrder(queue);
        //右子树递归
        head.right = reconPreOrder(queue);
        return head;
    }

    /**
     * 按层序列化
     * 
     * @param head
     * @return
     */
    public static String serialByLevel(Node head) {
        if (head == null) {
            return "#!";
        }
        String res = head.value + "!";
        Queue<Node> queue = new LinkedList<Node>();
        queue.offer(head);
        while (!queue.isEmpty()) {
            head = queue.poll();
            if (head.left != null) {
                res += head.left.value + "!";
                queue.offer(head.left);
            } else {
                res += "#!";
            }
            if (head.right != null) {
                res += head.right.value + "!";
                queue.offer(head.right);
            } else {
                res += "#!";
            }
        }
        return res;
    }

    public static Node reconByLevelString(String levelStr) {
        String[] values = levelStr.split("!");
        int index = 0;
        Node head = generateNodeByString(values[index++]);
        Queue<Node> queue = new LinkedList<Node>();
        if (head != null) {
            queue.offer(head);
        }
        Node node = null;
        while (!queue.isEmpty()) {
            node = queue.poll();
            node.left = generateNodeByString(values[index++]);
            node.right = generateNodeByString(values[index++]);
            if (node.left != null) {
                queue.offer(node.left);
            }
            if (node.right != null) {
                queue.offer(node.right);
            }
        }
        return head;
    }

    public static Node generateNodeByString(String val) {
        if (val.equals("#")) {
            return null;
        }
        return new Node(Integer.valueOf(val));
    }

    public static void printTree(Node head) {
        System.out.println("Binary Tree:");
        printInOrder(head, 0, "H", 17);
        System.out.println();
    }

    public static void printInOrder(Node head, int height, String to, int len) {
        if (head == null) {
            return;
        }
        printInOrder(head.right, height + 1, "v", len);
        String val = to + head.value + to;
        int lenM = val.length();
        int lenL = (len - lenM) / 2;
        int lenR = len - lenM - lenL;
        val = getSpace(lenL) + val + getSpace(lenR);
        System.out.println(getSpace(height * len) + val);
        printInOrder(head.left, height + 1, "^", len);
    }

    public static String getSpace(int num) {
        String space = " ";
        StringBuffer buf = new StringBuffer("");
        for (int i = 0; i < num; i++) {
            buf.append(space);
        }
        return buf.toString();
    }

    public static void main(String[] args) {
        Node head = null;
        printTree(head);

        String pre = serialByPre(head);
        System.out.println("serialize tree by pre-order: " + pre);
        head = reconByPreString(pre);
        System.out.print("reconstruct tree by pre-order, ");
        printTree(head);

        String level = serialByLevel(head);
        System.out.println("serialize tree by level: " + level);
        head = reconByLevelString(level);
        System.out.print("reconstruct tree by level, ");
        printTree(head);

        System.out.println("====================================");

        head = new Node(1);
        printTree(head);

        pre = serialByPre(head);
        System.out.println("serialize tree by pre-order: " + pre);
        head = reconByPreString(pre);
        System.out.print("reconstruct tree by pre-order, ");
        printTree(head);

        level = serialByLevel(head);
        System.out.println("serialize tree by level: " + level);
        head = reconByLevelString(level);
        System.out.print("reconstruct tree by level, ");
        printTree(head);

        System.out.println("====================================");

        head = new Node(1);
        head.left = new Node(2);
        head.right = new Node(3);
        head.left.left = new Node(4);
        head.right.right = new Node(5);
        printTree(head);

        pre = serialByPre(head);
        System.out.println("serialize tree by pre-order: " + pre);
        head = reconByPreString(pre);
        System.out.print("reconstruct tree by pre-order, ");
        printTree(head);

        level = serialByLevel(head);
        System.out.println("serialize tree by level: " + level);
        head = reconByLevelString(level);
        System.out.print("reconstruct tree by level, ");
        printTree(head);

        System.out.println("====================================");

        head = new Node(100);
        head.left = new Node(21);
        head.left.left = new Node(37);
        head.right = new Node(-42);
        head.right.left = new Node(0);
        head.right.right = new Node(666);
        printTree(head);

        pre = serialByPre(head);
        System.out.println("serialize tree by pre-order: " + pre);
        head = reconByPreString(pre);
        System.out.print("reconstruct tree by pre-order, ");
        printTree(head);

        level = serialByLevel(head);
        System.out.println("serialize tree by level: " + level);
        head = reconByLevelString(level);
        System.out.print("reconstruct tree by level, ");
        printTree(head);

        System.out.println("====================================");

    }
}
判断一棵二叉树是否是平衡二叉树

平衡二叉树(Self-balancing binary search tree)
又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树,同时,平衡二叉树必定是二叉搜索树,反之则不一定。平衡二叉树的常用实现方法有红黑树AVL替罪羊树Treap伸展树等。

解题思路

以每个结点为头的整棵树都要是平衡的
左子树平衡?
右子树平衡?
都平衡情况下,左子树高度?
右子树高度?
判断差值

package com.sss.class04;

/**
 * @author Shusheng Shi
 */
public class IsBalancedTree {

    public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int data) {
            this.value = data;
        }
    }

    public static boolean isBalance(Node head) {
        boolean[] res = new boolean[1];
        res[0] = true;
        getHeight(head, 1, res);
        return res[0];
    }

    public static int getHeight(Node head, int level, boolean[] res) {
        if (head == null) {
            return level;
        }
        int lH = getHeight(head.left, level + 1, res);
        if (!res[0]) {
            return level;
        }
        int rH = getHeight(head.right, level + 1, res);
        if (!res[0]) {
            return level;
        }
        if (Math.abs(lH - rH) > 1) {
            res[0] = false;
        }
        return Math.max(lH, rH);
    }

    public static void main(String[] args) {
        Node head = new Node(1);
        head.left = new Node(2);
        head.right = new Node(3);
        head.left.left = new Node(4);
        head.left.right = new Node(5);
        head.right.left = new Node(6);
        head.right.right = new Node(7);

        System.out.println(isBalance(head));

    }

}
判断一棵树是否是搜索二叉树、判断一棵树是否是完全二叉树
  • 搜索二叉树(Binary Search Tree)(又:二叉搜索树,二叉排序树)
    它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树
  • 完全二叉树(Complete Binary Tree)
    若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
    /**
     * 是否为完全二叉树
     *
     * @param head
     * @return
     */
    public static boolean isCBT(Node head) {
        if (head == null) {
            return true;
        }
        Queue<Node> queue =new LinkedList<>();
        boolean leaf = false;
        Node left,right;
        //开始时,将头加入队列
        queue.offer(head);
        while (!queue.isEmpty()) {
            //然后在队列中出队当前节点
            head = queue.poll();
            //拿到左右孩子
            left = head.left;
            right = head.right;
            //开启了叶节点的阶段 且 左/右孩子非空
            if ((leaf && (left != null || right != null)) || (left == null && right != null)) {
                return false;
            }
            if (left != null) {
                queue.offer(left);
            }
            if (right != null) {
                queue.offer(right);
            } else {
                leaf = true;
            }
        }
        return true;
    }

    二叉树层序遍历

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP