树的算法应用

树(Tree)

树的结构十分直观, 而叔的很多概念都有一个相同的特点: 递归, 也就是说, 一棵树要满足某种性质, 往往要求每个节点都必须满足. 例如: 在定义一颗二叉树搜索树时, 每个节点也都必须是一颗二叉树搜索树

正因为树有这样的特质, 大部分关于树的面试题都与递归有关, 换句话说, 面试官希望通过一道关于树的问题来考察你对于递归算法掌握的熟练程度.

树的形状

在面试中常考的树的形状有: 普通二叉树, 平衡二叉树, 完全二叉树, 二叉搜索树, 四叉树(Quadtree), 多叉树(N-ary Tree)

对于树的考题, 无非就是要考察树的遍历以及序列化(serialization)

1. 前序遍历

跟左右

方法: 总是访问根节点, 然后访问左子树, 最后访问右子树. 在访问左. 右子树的时候, 同样, 先访问子树的根节点, 在访问子树根节点的左子树和右子树, 这是一个不断递归的过程.

遍历顺序是: 1 ->2->3->4->5->6->7->8->9->10

2. 中序遍历

左根右, 就是从小到大

方法: 先访问左子树, 然后访问根节点, 最后访问右子树. 在访问左, 右子树的时候, 同样先访问子树的左边, 在访问子树的根节点, 最后在访问子树的右边.

上图中的遍历顺序是:4->3->5->2->7->6->1->9->8->10

应用场景: 最常见的是二叉搜索树, 由于二叉搜索树的性质就是左孩子小于根节点, 根节点小于右孩子, 对二叉树搜索树进行中序遍历的时候, 被访问到的节点大小是按顺序进行的.

3. 后序遍历

方法: 先访问左子树, 然后访问右子树, 最后访问根节点

左右根

上图中的遍历顺序为: 4->5->3->7->6->2->9->10->8->1

例题分析:

230. 二叉搜索树中第K小的元素

给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。

说明:你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。

示例 1:

输入: root = [3,1,4,null,2], k = 1
   3
  / \
 1   4
  \
   2
输出: 1

示例 2:

输入: root = [5,3,6,2,4,null,null,1], k = 3
       5
      / \
     3   6
    / \
   2   4
  /
 1
输出: 3

解题思路

这道题考察了两个知识点:

  1. 二叉搜索树的性质

  1. 二叉搜索树的遍历

二叉搜索树的性质:对于每个节点来说,该节点的值比左孩子大,比右孩子小,而且一般来说,二叉搜索树里不出现重复的值。

二叉搜索树的中序遍历是高频考察点,节点被遍历到的顺序是按照节点数值大小的顺序排序好的. 即, 中序遍历当中遇到的元素都是按照从小到大的顺序出现的.

因此, 我们只需要对这颗树进行中序遍历的操作, 当访问第k个元素的时候返回结果就好了.

方法一: 递归

中序遍历序列,则第 k-1 个元素就是第 k 小的元素。

class Solution {
  public ArrayList<Integer> inorder(TreeNode root, ArrayList<Integer> arr) {
    if (root == null) return arr;
    inorder(root.left, arr);
    arr.add(root.val);
    inorder(root.right, arr);
    return arr;
  }
  
  public int kthSmallest(TreeNode root, int k) {
    ArrayList<Integer> nums = inorder(root, new ArrayList<Integer>());
    return nums.get(k - 1);
  }
}

// 自己定义的树的结构
public class TreeNode {

    int val;
    TreeNode left;
    TreeNode right;

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

方法二: 迭代

算法:

在栈的帮助下,可以将方法一的递归转换为迭代,这样可以加快速度,因为这样可以不用遍历整个树,可以在找到答案后停止。

class Solution {
  public int kthSmallest(TreeNode root, int k) {
    LinkedList<TreeNode> stack = new Linkedlist<TreeNode>();
    
    while(true) {
      // 第一次循环, 首先先找到最小的元素
      while (root != null) {
        stack.add(root);
        root = root.left;
      }
      // 第一次循环, root = 2
      // 第二次, root = 3
      root = stack.removeLast();
      if (--k == 0) return root.val;
      // 第一次 2的右节点为null,
      root = root.right;
    }
  }
}

说说自己的理解吧:

while (root != null) {
        stack.add(root);
        root = root.left;
      }
 root = stack.removeLast();

上面的代码是找到该跟节点的最小左节点, 即最小的节点

而我们从最小的节点开始, 我们先遍历最小节点的右节点. 然后把该节点作为新的节点, 进行遍历, 还是使用循环, 把最小节点的右节点上的所有节点压入栈中, 如果右节点为空, 直接退回到最小节点的根节点. 然后在进行循环

所以以下的代码是中序遍历树的算法, 大家记住就好了

class Tree {
  public void sort(TreeNode root) {
    LinkedList<TreeNode> stack = new Linkedlist<TreeNode>();
    
    while(true) {
      // 第一次循环, 首先先找到最小的元素
      while (root != null) {
        stack.add(root);
        root = root.left;
      }
      root = stack.removeLast();
      root = root.right;
    }
    while(!stack.isEmpty()) {
      System.out.println(stack.removeLast().val());
    }
  }
}

如果你觉得我写的不错, 就点个赞吧,点赞是一种态度.如果你想跟我多交流, 或者给我投稿, 也非常支持, 但只支持原创啊: 本人公众号stormling

据说大家都在点"在看", 动动你的小手点一下吧! 感谢!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值