二叉树代码练习

本文详细介绍了非递归方法实现二叉树的先序、中序和后序遍历,以及完全二叉树的判断、最近公共祖先的查找、根据遍历序列构造二叉树、二叉搜索树转双向链表和根据二叉树创建字符串等操作。这些方法主要利用栈数据结构辅助实现。
摘要由CSDN通过智能技术生成

非递归实现二叉树的先序、中序、后序遍历

1.先序遍历

先序遍历的迭代写法借助“栈”来实现

public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> ret = new ArrayList<>();
    if (root == null) {
        return ret;
    }
    Deque<TreeNode> stack = new LinkedList<>();
    stack.push(root);
    while (!stack.isEmpty()) {
        TreeNode cur = stack.pop();
        ret.add(cur.val);
        if (cur.right != null) {
            stack.push(cur.right);
        }
        if (cur.left != null) {
            stack.push(cur.left);
        }
    }
    return ret;
}

2.中序遍历

中序遍历迭代写法同样借助“栈”来实现

“左 根 右”,一路先向左走到根,碰到第一个左子树为空的节点出栈

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> ret = new ArrayList<>();
    if (root == null) {
        return ret;
    }
    Deque<TreeNode> stack = new LinkedList<>();
    TreeNode cur = root;
    while (cur != null || !stack.isEmpty()) {
        while (cur != null) {
            stack.push(cur);
            cur = cur.left;
        }
        cur = stack.pop();
        ret.add(cur.val);
        cur = cur.right;
    }
    return ret;
}

3.后序遍历

“左 右 根”,第三次访问到跟节点才能“遍历”

1.继续先一路向左走到根,此时栈中栈顶就是第一个左子树为空的节点

那么如何知道这个栈顶元素是第三次访问到?

当左右子树都遍历完成时,证明元素是第三次访问

左树都遍历过了简单,

//1.先一路向左走到底

//2.cur == null时,左树走完了

//3.如何判断右树?

引入一个新的变量prev,称为上一个完全访问结束的节点

public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> ret = new ArrayList<>();
    if (root == null) {
        return ret;
    }
    TreeNode cur = root;
    Deque<TreeNode> stack = new ArrayDeque<>();
    //上一个完全处理完毕的节点
    TreeNode prev = null;
    while (cur != null || ! stack.isEmpty()) {
        //1.先一路向左走到底
        while (cur != null) {
            stack.push(cur);
            cur = cur.left;
        }
        //2.查看当前栈顶元素的情况
        cur = stack.pop();
        //检查右子树的情况
        if (cur.right == null || cur.right == prev) {
            //此时右子树为空或者已经处理完毕
            //此时可以放心访问cur节点 - 根
            ret.add(cur.val);
            //cur节点就是最新的处理结果的节点
            prev = cur;
            //继续从栈中取出栈顶元素
            cur = null;
        } else {
            //此时cur.right != null 且没被访问过,继续访问右子树
            stack.push(cur);
            //继续访问右子树
            cur = cur.right;
        }
    }
    return ret;
}

完全二叉树判断

找反例

若二叉树中某个节点只有右子树没有左子树–> return false

二叉树中存储一个节点的度为1,且该节点只有左子树没有右子树,这样的节点在完全二叉树中有其中有一个!

将判断完全二叉树分为两个阶段:

第一阶段: 在该阶段下,每个节点都有左右子树,当碰到第一个只有左子树没有右子树的节点或第一个叶子结点,就切换状态,进入第二阶段。当碰到一个节点只有右树没有左树,直接return false。

第二阶段: 在该阶段下,每个节点都是叶子节点,若第二阶段发现有节点有子树,直接return false。

遍历完成return true。

public boolean isCompleteTree(TreeNode root) {
    Deque<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    //用来判断是否进入第二阶段
    boolean isSecondStep = false;
    while (!queue.isEmpty()) {
        TreeNode cur = queue.poll();
        if (!isSecondStep) {
            //在第一阶段,每个节点都有左右子树
            if (cur.left != null && cur.right != null) {
                queue.offer(cur.left);
                queue.offer(cur.right);
            }else if (cur.left != null) {
                //只有左子树没有右子树,转阶段
                isSecondStep = true;
                queue.offer(cur.left);
            }else if (cur.right != null) {
                //只有右树
                return false;
            }else {
                //叶子节点,转阶段
                isSecondStep = true;
            }
        }else {
            //来到第二阶段,所有节点都为叶子节点
            if (cur.left != null || cur.right != null) {
                return false;
            }
        }
    }
    return true;
}

二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

如何找到公共祖先?

从根节点开始向下走,某个节点后序遍历可以同时找到p和q两个节点,这个节点就是p和q的祖先。

最近公共祖先:公共祖先中深度最大的节点

p和q可能出现在以下三个位置中的两个:

  • 要么出现在左子树
  • 要么有一个在根
  • 要么出现在右子树

此时当前节点就是p和q的公共祖先。

思路:就将当前祖先和他的深度保存在Map集合中。遍历这个树的每个节点,遍历结束后Map就保存了公共祖先和他的深度。返回深度最大的那个节点就是–>最近公共祖先。

//最近公共祖先
private TreeNode lca;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    //从树中每个节点开始遍历找p和q这两个点
    findNode (root,p,q);
    return lca;
}

/**
 * 从当前以root为树根的节点开始出发能否找到p或者q,知道一个就return true
 */
private boolean findNode(TreeNode root, TreeNode p, TreeNode q) {
    if (root == null) {
        return false;
    }
    int left = findNode(root.left,p,q) ? 1 : 0;
    int right = findNode(root.right,p,q) ? 1 : 0;
    int mid = (root == p || root == q) ? 1 : 0;
    if (left + right + mid == 2) {
        //此时p和q出现在以root为根的两个位置,则这个root一定是lca
        lca = root;
    }
    //大于0说明至少找到一个节点
    return (left + right + mid) > 0;
}

输入前序遍历,输出中序遍历

编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。

import java.util.Scanner;

class TreeNode {
    char val;
    TreeNode right;
    TreeNode left;

    public TreeNode(char val) {
        this.val = val;
    }
}

public class Main {
    //表示当前处理到先序遍历的哪个字符了
    private static int index = 0;
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            //外部获取的一个先序遍历结果的字符串
            String str = scanner.nextLine();
            //按照先序遍历顺序还原二叉树,返回根节点
            TreeNode root = preOrderBuild(str);
            //按照中序便利的方式输出二叉树的节点值
            inOrder(root);
            System.out.println();
            index = 0;
        }
    }

    /**
     * 按照中序遍历遍历二叉树,打印节点值
     * @param root
     */
    private static void inOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        inOrder(root.left);
        System.out.print(root.val + " ");
        inOrder(root.right);
    }

    /**
     * 传入一个字符串str,就能按照先序遍历的方式还原二叉树,返回二叉树跟节点
     * @param str
     * @return
     */
    private static TreeNode preOrderBuild(String str) {
        //先从字符串中读取一个字符
        char cur = str.charAt(index);
        if (cur == '#') {
            return null;
        }
        TreeNode root = new TreeNode(cur);
        index++;
        root.left = preOrderBuild(str);
        index++;
        root.right = preOrderBuild(str);
        return root;
    }
}

从前序与中序遍历序列构造二叉树

前序遍历的第一个结果一定是当前的树根

中序遍历的左子树都在跟的左侧,右子树都在跟的右侧

  1. 先从前序遍历中取出当前树的树根

  2. 在中序遍历找到树根所在的位置pos

    所有左子树节点值为[ left , pos )

    所有右子树节点值为[ pos + 1 , right )

  3. 递归上述过程。

引入left,right标志位解决问题

/**
 * 通过前序与中序遍历构造二叉树
 */
public class Num105_BuildTree {
    //表示当前处理到前序遍历的哪个元素
    private int index = 0;
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return buildTreeHelper(preorder,inorder,0,inorder.length-1);
    }

    /**
     * 每次从前序遍历中提取出树根值,借助中序遍历的 [left ... right) 还原二叉树,返回树的树根
     * @param preorder
     * @param inorder
     * @param left
     * @param right
     * @return
     */
    private TreeNode buildTreeHelper(int[] preorder, int[] inorder, int left, int right) {
        //边界
        if (left > right) {
            return null;
        }
        if (index == preorder.length) {
            //前序遍历元素处理完毕,没有新节点
            return null;
        }
        //从前序遍历中取出当前树的树根节点
        TreeNode root = new TreeNode(preorder[index]);
        index ++;
        int pos = find(root.val,inorder);
        root.left = buildTreeHelper(preorder,inorder,left,pos - 1);
        root.right = buildTreeHelper(preorder,inorder,pos + 1 , right);
        return root;
    }

    /**
     * 在中序遍历中找到val对应的索引位置
     * @param val
     * @param inorder
     * @return
     */
    private int find(int val, int[] inorder) {
        for (int i = 0; i < inorder.length; i++) {
            if (inorder[i] == val) {
                return i;
            }
        }
        return -1;
    }
}

二叉搜索树转为双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表

/**
 * 传入一棵树的树根节点就能将这棵树转为排序后的双向链表并返回链表头结点
 * @param pRootOfTree
 * @return
 */
public TreeNode Convert(TreeNode pRootOfTree) {
    if (pRootOfTree == null) {
        return null;
    }
    TreeNode leftHead = Convert(pRootOfTree.left);
    TreeNode leftTail = leftHead;
    while (leftTail != null && leftTail.right != null) {
        leftTail = leftTail.right;
    }

    if (leftTail != null) {
        pRootOfTree.left = leftTail;
        leftTail.right = pRootOfTree;
    }

    TreeNode rightHead = Convert(pRootOfTree.right);
    if (rightHead != null) {
        rightHead.left = pRootOfTree;
        pRootOfTree.right = rightHead;
    }
    return leftHead == null ? pRootOfTree : leftHead;
}

根据二叉树创建字符串

给一个二叉树的根节点 root ,采用前序遍历的方式,将二叉树转化为一个由括号和整数组成的字符串,返回构造出的字符串。

空节点使用一对空括号对 “()” 表示,转化后需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vChgZBWn-1654700792090)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220608203158676.png)]

省略括号:

当左子树不为空,右子树为空可以省略右子树的括号

当树的左右子树都为空,左右子树的括号都能省略

不能省略括号:

左树为空,右树不为空,则左子树的括号不能省略

private StringBuilder sb = new StringBuilder();
public String tree2str(TreeNode root) {
    if (root == null) {
        return "";
    }
    sb.append(root.val);
    if (root.left != null){
        sb.append('(');
        tree2str(root.left);
        sb.append(')');
    }else {
        if (root.right != null) {
            sb.append("()");
        }
    }
    if (root.right != null ) {
        sb.append('(');
        tree2str(root.right);
        sb.append(')');
    }
    return sb.toString();
}
(root == null) {
        return "";
    }
    sb.append(root.val);
    if (root.left != null){
        sb.append('(');
        tree2str(root.left);
        sb.append(')');
    }else {
        if (root.right != null) {
            sb.append("()");
        }
    }
    if (root.right != null ) {
        sb.append('(');
        tree2str(root.right);
        sb.append(')');
    }
    return sb.toString();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值