算法设计与分析 二叉树

简介: 算法设计与分析 二叉树

二叉树

简述

image.png

递归序(递归访问)

  • 递归:访问次序image.png

递归序:1,2,4,4,4,2,5,5,5,2,1,3,6,6,6,3,7,7,7,3,1

先序:第一次在递归序出现就打印,第二,三次访问时无操作:1,2,4,5,3,6,7

中序:第二次在递归序出现就打印,第一,三次访问时无操作:4,2,5,1,6,3,7

后序:第三次在递归序出现就打印,第一,二次访问时无操作:4,5,2,6,7,3,1

public static void preOrderRecur(Node head){
        if (head == null){
            return;
        }
        System.out.println(head.value + " ");
        preOrderRecur(head.left);
        preOrderRecur(head.right);
    }
    public static void inOrderRecur(Node head){
        if (head == null){
            return;
        }
        inOrderRecur(head.left);
        System.out.println(head.value + " ");
        inOrderRecur(head.right);
    }
    public static void posOrderRecur(Node head){
        if (head == null){
            return;
        }
        posOrderRecur(head.left);
        posOrderRecur(head.right);
        System.out.println(head.value + " ");
    }

非递归访问

  • 非递归:通过手写栈实现
    1 先序:
    (1)从栈中弹出一个结点
    (2)打印(处理)该结点
    (3)先右后左(如果有)压入栈中(保证出栈是左,右)
    (4)重复,直至栈为空
public static void preOrderRecur(Node head){
        if (head != null){
            Stack<Node> stack = new Stack<>();
            stack.push(head);
            while (!stack.isEmpty()){
                Node p = stack.pop();
                System.out.println(p.value);
                if (p.right != null){
                    //先压入右子树
                    stack.push(p.right);
                }
                if (p.left != null){
                    stack.push(p.left);
                }
            }
        }
    }

2 中序:

(1)每颗子树的左边界进栈

(2)依次弹出的过程中打印(处理),弹出结点的右子树(及其左边界)进栈

(3)重复1,2

public static void inOrderRecur(Node head){
        if (head != null){
            Stack<Node> stack = new Stack<Node>();
            Node p = head;
            while (!stack.isEmpty() || p != null){
                if (p != null){
                    stack.push(p);
                    p = p.left;
                }else {
                    p = stack.pop();
                    System.out.println(p.value + " ");
                    p = p.right;
                }
            }
        }
    }

3 后序:需要一个收集栈

(1)从栈中弹出一个结点

(2)将该结点放入收集栈(父节点在收集栈底)

(3)先左后右(如果有)压入栈中(保证收集栈里是右,左)

(4)重复,直至栈为空

(5)打印(处理)收集栈(出栈是左,右,父)

public static void preOrderRecur(Node head){
        if (head != null){
            Stack<Node> stack = new Stack<>();
            Stack<Node> temp = new Stack<>(); //收集栈
            stack.push(head);
            while (!stack.isEmpty()){
                Node p = stack.pop();
                temp.push(p); //压入收集栈
                if (p.left != null){
                    //先压入左子树
                    stack.push(p.left);
                }
                if (p.right != null){
                    stack.push(p.right);
                }
            }
            while (!temp.isEmpty()){
                Node p = temp.pop();
                System.out.println(p.value + " ");
            }
        }
    }

直观打印树

public static class Node{
        int value;
        Node left;
        Node right;
        public Node(int date){
            //构造函数
            this.value = date;
        }
    }
    public static void printTree(Node head){
        System.out.println("Binary Tree");
        printInOrder(head, 0, "H", 12);
        System.out.println();
    }
    public static void printInOrder(Node head, int height, String to, int len){
        //head 头结点
        //height 目前深度
        //to 表示关系,H:头结点;↖:表示父节点在其左上;↙:表示父节点在其左下
        //len 树显示长度的控制,可自行调整
        if (head == null){
            return;
        }
        printInOrder(head.right, height + 1, "↙", 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 = " ";
        StringBuilder buf = new StringBuilder(" ");
        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(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);
        head.left.right.right = new Node(8);
        head.right.right.left = new Node(9);
        printTree(head);
    }

理想图:

image.png

效果图:H:头结点;↖:表示父节点在其左上;↙:表示父节点在其左下

image.png

题目一:求树的最大宽度(广度优先)

层次遍历

public static void levelOrder(Node head){
        if (head == null){
            return;
        }
        Queue<Node> queue = new LinkedList<Node>();
        Node p = head;
        queue.add(p);
        while (!queue.isEmpty()){
            p = queue.poll();
            if (p.left != null){
                queue.add(p.left);
            }
            if (p.right != null){
                queue.add(p.right);
            }
        }
    }
  • 笔试代码:
    (1)思路:三个值,当前结点所在层,每层中的结点个数,树中结点最多的层
    (2)实现方法:结合层次遍历,使用HashMap使得每个结点与对应的层绑定。出队时,将对应层数的结点加一,最后找到结点最多的层即可
public static int levelMax(Node head){
        if (head == null){
            return 0;
        }
        Queue<Node> queue = new LinkedList<Node>();
        int level = 1; //层数
        int levelNum = 0; //该层的结点数
        int nodeMax = 0; //最多结点个数
        HashMap<Node, Integer> map = new HashMap<>();
        Node p = head;
        map.put(p, 1);
        queue.add(p);
        while (!queue.isEmpty()){
            p = queue.poll();
            if (map.get(p) == level){
                //当前结点层与要记录层相同
                levelNum++;
            }else {
                //当前结点层与要记录层不相同,进入下一层,更新数据
                nodeMax = Math.max(nodeMax, levelNum); //最大值更新
                level++; //层数更新
                levelNum = 1; //重置对应层的结点数
            }
            if (p.left != null){
                map.put(p.left, level + 1);
                queue.add(p.left);
            }
            if (p.right != null){
                map.put(p.right, level + 1);
                queue.add(p.right);
            }
        }
        return  nodeMax;
    }
  • 面试思路:
    (1)思路:四个值,当前层的结束结点,当前层下一次的结束结点,每层中的结点个数,树中结点最多的层
    (2)实现方式:结合层次遍历,第一层,该层的结束结点就是head,到了第二层将在第一层记录的下一层结束结点作为当前层的结束结点,在出,入对的同时更新下一次的结束结点,同时记录每层中的结点个数。
public static int levelMax(Node head){
        if (head == null){
            return 0;
        }
        Queue<Node> queue = new LinkedList<Node>();
        Node levelEnd = head; //本层结束结点
        Node levelNextEnd = null; //下一次结束结点
        int levelNum = 0; //该层的结点数
        int nodeMax = 0; //最多结点个数
        Node p = head;
        queue.add(p);
        while (!queue.isEmpty()){
            p = queue.poll();
            if (p.left != null){
                //时刻更新levelNextEnd
                levelNextEnd = p.left;
                queue.add(p.left);
                levelNum++;
            }
            if (p.right != null){
                levelNextEnd =p.right;
                queue.add(p.right);
                levelNum++;
            }
            if (p == levelEnd){
                //遍历到该层最后一个结点,数据更新
                levelEnd = levelNextEnd;
                nodeMax = Math.max(levelNum, nodeMax);
                levelNum = 0;
                levelNextEnd = null;
            }
        }
        return  nodeMax;
    }

题目二:二叉树相关概念判断

1. 如何判断是否为搜索二叉树(排序二叉树)

思路:中序遍历二叉树得到的序列,若为升序序列则为搜索二叉树

递归中序:
 public static void inOrderRecur(Node head){
        if (head == null){
            return;
        }
        inOrderRecur(head.left);
        System.out.println(head.value + " ");
        inOrderRecur(head.right);
    }
代码修改:
public static int preValue = Integer.MIN_VALUE;
    public static boolean checkBST(Node head){
        if (head == null){
            return true;
        }
        if (!checkBST(head.left)){
            //递归左子树
            return false;
        }
        if (head.value <= preValue){
            //右边 <= 左边,不满足BST
            return false;
        }else {
            preValue = head.value;
        }
        return checkBST(head.right);
    }
非递归中序:
public static void inOrderRecur(Node head){
        if (head != null){
            Stack<Node> stack = new Stack<Node>();
            Node p = head;
            while (!stack.isEmpty() || p != null){
                if (p != null){
                    stack.push(p);
                    p = p.left;
                }else {
                    p = stack.pop();
                    System.out.println(p.value + " ");
                    p = p.right;
                }
            }
        }
    }
代码修改:
public static boolean checkBST(Node head){
        if (head != null){
            Stack<Node> stack = new Stack<Node>();
            Node p = head;
            int preValue = Integer.MIN_VALUE;
            while (!stack.isEmpty() || p != null){
                if (p != null){
                    stack.push(p);
                    p = p.left;
                }else {
                    p = stack.pop();
                    if (p.value <= preValue){
                        return false;
                    }else {
                        preValue = p.value;
                    }
                    p = p.right;
                }
            }
        }
        return true;
    }

总结:两种均为动态检查,也可先将二叉树的序列存入list类的集合中,在判断序列是否有序

2. 如何判断是否为完全二叉树

思路:广度优先(层次遍历),判断1:只要有结点是有右孩子,而无左孩子就返回false;判断2:从第一个子树不完整的结点开始,其后结点都要为叶子结点

public static boolean isCBT(Node head){
        if (head == null){
            return true;
        }
        Queue<Node> queue = new LinkedList<Node>();
        boolean flag = false; //表示是否遇到了子树不完整的结点
        Node p = head;
        queue.add(p);
        while (!queue.isEmpty()){
            p = queue.poll();
            if ((p.left == null && p.right != null) || flag && (p.left != null && p.right != null)){
                //不为满二叉树的条件
                return false;
            }
            if (p.left != null){
                queue.add(p.left);
            }
            if (p.right != null){
                queue.add(p.right);
            }
            if (p.left == null || p.right == null){
                //子树不完整
                flag = true;
            }
        }
        return true;
    }

3. 如何判断是否为满二叉树

思路:统计二叉树的深度L,节点数为N,若N = 2 ^ L - 1,则为满二叉树

方法:递归调用,求左子树的L,N;求右子树的L,N;求该树的L,N

public static boolean isFull(Node head){
        ReturnType returnType = process(head);
        //1 << x, 2的x次方
        return returnType.num == (1 << returnType.height - 1);
    }
    public static class ReturnType{
        //自定义类型
        public int height; //树的高度
        public int num; //节点数
        public ReturnType(int height, int num){
            //构造函数
            this.height = height;
            this.num = num;
        }
    }
    public static ReturnType process(Node head){
        if (head == null){
            return new ReturnType(0, 0);
        }
        ReturnType leftTree = process(head.left); //递归调用
        ReturnType rightTree= process(head.right);
        int height = Math.max(leftTree.height, rightTree.height) + 1; //高度
        int num = leftTree.num + rightTree.num + 1; //节点数
        return new ReturnType(height, num);
    }

4. 如何判断是否为平衡二叉树

思路:平衡二叉树条件:(1)左子树平衡,左高度;(2)右子树平衡,右高度;(3)|左高度 - 右高度| <= 1

结合递归实现

    public static boolean isBalancedTree(Node head){
        return process(head).isBalanced;
    }
    public static class ReturnType{
        //自定义类型
        public boolean isBalanced; //树是否平衡
        public int height; //树高
        public ReturnType(boolean isBalanced,int height){
            //构造函数
            this.isBalanced = isBalanced;
            this.height = height;
        }
    }
    public static ReturnType process(Node head){
        if (head == null){
            //空树平衡,高度0
            return new ReturnType(true, 0);
        }
        ReturnType leftTree = process(head.left); //递归调用
        ReturnType rightTree= process(head.right);
        int height = Math.max(leftTree.height, rightTree.height) + 1;
        boolean isBalanced = leftTree.isBalanced && rightTree.isBalanced
                && Math.abs(leftTree.height - rightTree.height) < 2;
        return new ReturnType(isBalanced, height);
    }

5. 二叉树递归套路(树型DP,动态规划)

思路:在判断二叉树是什么树时?分解为左子树是否满足,右子树是否满足,左右子树合并后是否满足(树型DP)

  1. 二叉树递归套路:判断是否为搜索二叉树
    (1)左树搜索,左的max,min;(2)右树搜索,右的min,max;(3)左max < head.value < 右min
  public static boolean isBSTree(Node head){
        return process(head).isBST;
    }
    public static class ReturnType{
        //自定义类型
        public boolean isBST; //树是否为搜索树
        public int min; //树的最小值
        public int max; //树的最大值
        public ReturnType(boolean isBST,int min, int max){
            //构造函数
            this.isBST = isBST;
            this.min = min;
            this.max = max;
        }
    }
    public static ReturnType process(Node head){
        if (head == null){
            //空树的min,max不好设置,直接设置为null
            return null;
        }
        ReturnType leftTree = process(head.left); //递归调用
        ReturnType rightTree= process(head.right);
        int min = head.value; //min,max的设置
        int max = head.value;
        if (leftTree != null){
            min = Math.min(min, leftTree.min);
            max = Math.max(max, leftTree.max);
        }
        if (rightTree != null){
            min = Math.min(min, rightTree.min);
            max = Math.max(max, rightTree.max);
        }
        boolean isBST = true; //isBST设置
        if (leftTree != null && (leftTree.isBST || leftTree.max > head.value)){
            //左不为空的条件下:左子树不是搜索二叉树或左的max > head,value
            //不满足搜索二叉树
            isBST = false;
        }
        if (rightTree != null && (rightTree.isBST || rightTree.min < head.value)){
            isBST = false;
        }
        return new ReturnType(isBST, min, max);
    }
  1. 在判断满二叉树,平衡二叉树时都是此方法

题目三:俩节点最低公共祖先

  • 笔试思路:
    (1)使用HashMap建立,每个节点与其父节点的关系
    (2)使用HashSet存储,node1所有的祖先节点
    (3)依次找node2的祖先节点,并在node1的HashSet中寻找是否有相同的,当遇到第一个相同的便是最低公共祖先节点
    public static Node lowCommonAncestor(Node head, Node node1, Node node2){
        HashMap<Node, Node> fatherMap = new HashMap<Node,Node>();
        fatherMap.put(head, head); //头节点的父节点为自己
        process(head, fatherMap);
        HashSet<Node> setNode1 = new HashSet<Node>(); //建立node1的祖先节点集
        Node p = node1;
        while (p != fatherMap.get(head)){
            //从当前节node1点从下往上遍历头节点
            setNode1.add(p);
            p = fatherMap.get(p);
        }
        p = node2;
        while (p != fatherMap.get(head)){
            //从当前节node2点从下往上遍历头节点
            if (setNode1.contains(p)){
                //有相同就是最低公共祖先节点
                return p;
            }
            p = fatherMap.get(p);
        }
        return null;
    }
    public static void process(Node head, HashMap<Node, Node> fatherMap){
        //节点与父节点关系的建立
        if (head == null){
            return;
        }
        fatherMap.put(head.left, head);
        fatherMap.put(head.right, head);
        process(head.left, fatherMap);
        process(head.right, fatherMap);
    }
  • 面试思路:
    情况分析:
    (1)node1是node2的祖先节点,或node2是node1的祖先节点
    (2)node1与node2不互为祖先节点,需要向上遍历才能找到
    方法:遍历树节点,碰到node1,node2返回对应节点,其余的节点碰到都只是返回空
    情况一:image.png

情况二:

image.png

    public static Node lowCommonAncestor(Node head, Node node1, Node node2){
        if (head == null || head == node1 || head == node2){
            //node1,node2时返回对应值
            return head;
        }
        Node left = lowCommonAncestor(head.left, node1, node2);
        Node right = lowCommonAncestor(head.right, node1, node2);
        if (left != null && right != null){
            //情况二,两个节点互不为公共祖先
            //则同时左右返回为node1,node2的就是最低公共祖先
            return head;
        }
        //碰到node1,node2的其中一个时返回不空的
        return left != null ? left : right;
    }

题目四:树的后继节点查找(含父亲节点)

image.png

后继节点:在中序遍历的序列中,node的下一个节点叫作node的后继节点

情况分析:

(1)node有右子树,则后继节点就是右子树最左边的节点

(2)node无右子树,则后继结点就是看node是那个结点左子树最右边的结点(判断当前节点是否是其祖先节点的左子树)

    public static Node getSuccessorNode(Node node){
        if (node == null){
            return node;
        }
        if (node.right != null){
            //当前节点存在右子树
            return getLeftNode(node);
        }
        else {
            //无右子树,通过父节点查找
            Node parent = node.parent;
            while (parent != null && parent.left != node){
                //该节点是否为父节点的左子树
                node = parent;
                parent = node.parent;
            }
            return parent;
        }
    }
    public static Node getLeftNode(Node node){
        if (node == null){
            return node;
        }
        while (node.left != null){
            node = node.left;
        }
        return node;
    }

题目五:二叉树的序列化和反序列化

image.png

思路:使用 “" 表示相隔节点之间的表示方式;使用 "#” 表示空结点

先序序列:序列化
    public static String serialByPre(Node head){
        if (head == null){
            return "#_";
        }
        String str = head.value + "_";
        str += serialByPre(head.left);
        str += serialByPre(head.right);
        return str;
    }
反序列化
    public static Node reconByPreString(String preStr){
        //将字符串分割,存入队列
        String[] values = preStr.split("_");
        Queue<String> queue = new LinkedList<String>();
        for (int i = 0; i < values.length; i++){
            queue.add(values[i]);
        }
        return reconPreOrder(queue);
    }
    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;
    }

题目六:折纸问题

image.png

问题分析:

image.png

思路:找到折纸的规律,使用树的方式来表示。父节点与左子树都是凹,右子树都是凸

方法:给定N,确定树的高度,模拟树的中序遍历便可以得到折痕方向

    public static void printAllFolds(int N){
        printProcess(1, N, true);
    }
    public static void printProcess(int height, int N, Boolean down){
        //模拟树的中序遍历
        //height当前高度,N为树高
        //down = true -> 凹,down = false ->凸
        if (height > N){
            return;
        }
        printProcess(height + 1, N, true);
        System.out.println(down ? "凹" : "凸");
        printProcess(height + 1, N, false);
    }


目录
相关文章
|
2月前
|
机器学习/深度学习 算法 搜索推荐
从理论到实践,Python算法复杂度分析一站式教程,助你轻松驾驭大数据挑战!
【10月更文挑战第4天】在大数据时代,算法效率至关重要。本文从理论入手,介绍时间复杂度和空间复杂度两个核心概念,并通过冒泡排序和快速排序的Python实现详细分析其复杂度。冒泡排序的时间复杂度为O(n^2),空间复杂度为O(1);快速排序平均时间复杂度为O(n log n),空间复杂度为O(log n)。文章还介绍了算法选择、分而治之及空间换时间等优化策略,帮助你在大数据挑战中游刃有余。
76 4
|
17天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
46 1
|
1月前
|
算法
分享一些提高二叉树遍历算法效率的代码示例
这只是简单的示例代码,实际应用中可能还需要根据具体需求进行更多的优化和处理。你可以根据自己的需求对代码进行修改和扩展。
|
1月前
|
存储 缓存 算法
如何提高二叉树遍历算法的效率?
选择合适的遍历算法,如按层次遍历树时使用广度优先搜索(BFS),中序遍历二叉搜索树以获得有序序列。优化数据结构,如使用线索二叉树减少空指针判断,自定义节点类增加辅助信息。利用递归与非递归的特点,避免栈溢出问题。多线程并行遍历提高速度,注意线程安全。缓存中间结果,避免重复计算。预先计算并存储信息,提高遍历效率。综合运用这些方法,提高二叉树遍历算法的效率。
51 5
|
1月前
|
机器学习/深度学习 JSON 算法
二叉树遍历算法的应用场景有哪些?
【10月更文挑战第29天】二叉树遍历算法作为一种基础而重要的算法,在许多领域都有着不可或缺的应用,它为解决各种复杂的问题提供了有效的手段和思路。随着计算机科学的不断发展,二叉树遍历算法也在不断地被优化和扩展,以适应新的应用场景和需求。
36 0
|
2月前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
2月前
|
存储 算法 关系型数据库
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
这篇文章主要介绍了多路查找树的基本概念,包括二叉树的局限性、多叉树的优化、B树及其变体(如2-3树、B+树、B*树)的特点和应用,旨在帮助读者理解这些数据结构在文件系统和数据库系统中的重要性和效率。
29 0
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
|
2月前
|
存储 算法 搜索推荐
数据结构与算法学习十七:顺序储存二叉树、线索化二叉树
这篇文章主要介绍了顺序存储二叉树和线索化二叉树的概念、特点、实现方式以及应用场景。
32 0
数据结构与算法学习十七:顺序储存二叉树、线索化二叉树
|
2月前
|
存储 算法
【二叉树】—— 算法题
【二叉树】—— 算法题
【二叉树】—— 算法题
|
2月前
|
算法
PID算法原理分析
【10月更文挑战第12天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。