二叉树(基础)

Posted by 令德湖周杰伦 on 01-11,2024

1. 二叉树的数据结构

public class TreeNode {
    public int val;
    public TreeNode left; //左子树
    public TreeNode right; //右子树

    public TreeNode(int x) { val = x; }
}

2. 遍历

二叉树的遍历方式有二种:

  1. 深度优先遍历,主要包括:前序遍历、中序遍历、后序遍历;
  2. 广度优先遍历,主要包括:层次遍历

2.1 前序遍历

思路:根-左-右

2.1.1 递归

public static void queryPreByDg(TreeNode node){
        if(node == null){
            return;
        }
        //打印根节点
        System.out.println(node.val);
        //遍历左子树
        queryPre(node.left);
        //遍历右子树
        queryPre(node.right);
    }

2.1.2 非递归

思路:使用stack的特性

  1. 栈顶出来,即为cur
  2. 有右压入右,有左压入左,那么出栈一定是先左后右
public static void queryPre(TreeNode node){
        if(node == null){
            return;
        }
	//根节点先入栈	
        TreeNode cur = node;
        Stack<TreeNode> stack = new Stack<>();
        stack.add(cur);
        while (!stack.isEmpty()){
            TreeNode popNode = stack.pop();
	    //打印根节点
            System.out.println(popNode.val);
	    //有右压入右
            if(popNode.right!=null){
                stack.add(popNode.right);
            }
	    //有左压入左
            if(popNode.left!=null){
                stack.add(popNode.left);
            }
        }
    }

2.2 中序遍历

思路:左-根-右

2.2.1 递归

 public static void queryMidByDg(TreeNode node){
        if(node == null){
            return;
        }
        //遍历左子树
        queryPre(node.left);
        //打印根节点
        System.out.println(node.val);
        //遍历右子树
        queryPre(node.right);
    }

2.2.2 非递归

思路:

  1. 当前节点cur,cur为头的整个左边界进栈,直到遇到空 -》2
  2. 从栈中弹出节点打印,节点的右孩子成为cur回 -》1
  3. 栈为空停
public static void queryMid(TreeNode node){
        if(node == null){
            return;
        }
        //将以根节点的左边界入栈
        TreeNode cur = node;
        Stack<TreeNode> stack = new Stack<>();
        while (cur!=null){
            stack.add(cur);
            cur = cur.left;
        }
        //开始出栈,从最左边的节点
        while (!stack.isEmpty()){
            TreeNode popNode = stack.pop();
            //打印节点
            System.out.println(popNode.val);
            //将节点的右孩子的左边界入栈
            if(popNode.right!=null){
                cur = popNode.right;
                while (cur!=null){
                    stack.add(cur);
                    cur = cur.left;
                }
            }
        }
    }

2.3 后序遍历

思路:左-右-根,递归和非递归的方式可以参考前序遍历
理论上还可以有:根-右-左,右-根-左,右-左-根 这些遍历的方式

2.4 层次遍历

2.4.1 普通层次遍历

思路:可以使用队列实现

public List<Integer> levelQuery(TreeNode root,List<Integer> list){
	//头节点入队
        TreeNode node = root;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(node);
        while(queue.size()>0){
            //根结点出队,记录
            node = queue.poll();
            list.add(node.val);
	    //左孩子入队列
            if(node.left!=null){
                queue.add(node.left);
            }
	    //右孩子入队列
            if(node.right!=null){
                queue.add(node.right);
            }
        }
        return list;
    }

2.4.2 Z层次遍历

思路:

  1. 每层需要记录层数
  2. 偶数层从左边遍历,期数层从右开始遍历,

整体有2种实现方式,分别为:双端队列;栈 + 队列实现
详情见:http://www.wyjblog.com/archives/103%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%94%AF%E9%BD%BF%E5%BD%A2%E5%B1%82%E6%AC%A1%E9%81%8D%E5%8E%86

3. 构建

根据二叉树的特性可知:
要想确定一个树,至少需要知道「中序」和 「前序后序中的一种」,即:

  1. 中序 + 前序
  2. 中序 + 后序

3.1 根据前序和中序构建二叉树

思路:

  1. 由前序遍历的特点可知第一个节点一定是根节点
  2. 在中序遍历中,左孩子一定在根节点的前index,后孩子一定在根节点的后面index
    所以:
  3. 遍历前序序列
  4. 获取当前 前序节点在中序遍历上的位置和根节点的在中序遍历的位置,比较
  5. 如果小于,在左树,如果左树根节点为空直接插入即可,如果不为空,继续比较和当前左树根节点的位置,继续找位置
  6. 如果大于,在右树,如果右树根节点为空直接插入即可,如果不为空,继续比较和当前右树根节点的位置,继续找位置

public static TreeNode buildTreeByPreAndMid(int[] preList, int[] midList){
        TreeNode root= null;
	//遍历前序序列
        for (int i = 0; i < preList.length; i++) {
            root = insert(root,preList[i],midList);
        }
        return root;
    }


public static TreeNode insert(TreeNode root, int data, int[] midList){
        //新建立的结点
        TreeNode p = new TreeNode(data);
        if(root == null){
            root = p;
        }else{
            //该结点用来遍历
            TreeNode node = root;
            int pos = getPosInMid(data,midList);
            while(true){
                int rootPos = getPosInMid(node.val,midList);
                //在左树中
                if(pos<rootPos){
                    //如果左树有节点了,继续寻找位置
                    if(node.left!=null){
                        node = node.left;
                    }else{
                        //左结点为null插入。
                        node.left = p;
                        break;
                    }
                }else{
		    //在右树中,右树有节点了,继续寻找位置
                    if(node.right!=null){
                        node = node.right;
                    }else{
			//右结点为null插入。
                        node.right = p;
                        break;
                    }
                }
            }
        }
        return root;
    }

//获取前序节点在中序序列上的index
public static int getPosInMid(int data, int[] midList){
        for (int i = 0; i < midList.length; i++) {
            if(midList[i] == data){
                return i;
            }
        }
        return 0;
    }