算法 | 二分搜索树前中后遍历

简介: 又是来自我的好朋友 EvilSay 的投稿,以下是原文:

1、基本定义

  • 二分搜索树的每个子节点最多有两个叶子节点
  • 二分搜索树的每个节点最多有一个根节点
  • 存储的元素必须具有可比较性
  • 二分搜索树每个子节点的值
  • 大于其左子节的所有节点的值
  • 小于其右子节点的所有节点的值
  • 二分搜索树不一定是满的


2、二分搜索树 java 实现

/**
 * @Author: EvilSay
 * @Date: 2019/8/6 19:00
 */
public class BSTMain <E extends Comparable<E>> {
    private class Node {
        public E e;
        private Node left, right;
        public Node(E e) {
            this.e = e;
            left = null;
            right = null;
        }
    }
    //根节点
    private Node root;
    private int size;
    public BSTMain() {
        root = null;
        size = 0;
    }
    public int size() {
        return size;
    }
    public boolean isEmpty() {
        return size == 0;
    }
    public void add(E e){
        root = add(this.root, e);
    }
    // 向node为根的二分搜索树中插入元素E,递归算法
    // 返回插入新节点后二分搜索树的根
    private Node add(Node node, E e){
        if (node == null){
            size ++;
            return new Node(e);
        }
        if (e.compareTo(node.e) < 0)
            node.left = add(node.left, e);
        else if (e.compareTo(node.e) > 0)
            node.right = add(node.right,e);
        return node;
    }
    // 看二分搜索树中是否包含元素e
    public boolean contains(E e){
        return contains(root,e);
    }
    // 看以node为根的二分搜索树中是否包含元素e,递归算法
    private boolean contains(Node node, E e){
        if (node == null)
            return false;
        if (e.compareTo(node.e) == 0)
            return true;
        else if (e.compareTo(node.e) < 0)
            return contains(node.left, e);
        else
            return contains(node.right,e);
    }
    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        generateBSTSString(root,0,res);
        return res.toString();
    }
    // 生成以node为根节点,深度为depth的描述二叉树的字符串
    private void generateBSTSString(Node root, int depth, StringBuilder res) {
        if (root == null){
            res.append(generateDepthString(depth) + "null\n");
            return;
        }
        res.append(generateDepthString(depth) + root.e + "\n");
        generateBSTSString(root.left, depth + 1 ,res);
        generateBSTSString(root.right, depth + 1, res);
    }
    private String generateDepthString(int depth) {
        StringBuilder res = new StringBuilder();
        for (int i = 0; i < depth; i++)
            res.append("--");
        return res.toString();
    }
}


3、图解二分搜索树


640.png


从图上我们看出二分搜索树每个节点的值大于其左子节的所有节点的值小于其右子节点的所有节点的值


4、前序遍历前序遍历也叫先序遍历,访问顺序是根左右,也就是先访问根节点,再到左子树,最后才到右子树。所以上图所示的访问顺序是 5、3、2、4、8、7、9。


二分搜索树前序遍历递归版与非递归版


//前序遍历以node为根的二分搜索树,递归算法
    private void preOrder(Node node){
        if (node == null)
            return;
        System.out.println(node.e);
        preOrder(node.left);
        preOrder(node.right);
    }
    //二分搜索树的前序遍历递归调用
    public void preOrder(){
        preOrder(root);
    }
    //二分搜索树的前序遍历非递归写法
    public void preOrderNG(){
        Stack<Node> stack = new Stack<>();
        //根节点
        stack.push(root);
        while (!stack.isEmpty()){
            Node cur = stack.pop();
            System.out.println(cur.e);
            //判断是否还有叶子节点
            if (cur.right != null)
                stack.push(cur.right);
            if (cur.left != null)
                stack.push(cur.left);
        }
    }


理解非递归的实现逻辑、推导出前序递归的实现


  • 创建一个堆栈,我们把根节点 5 推入栈中,接下来我们就要访问 5 这个根节点了,所有把 5 从栈中推出 。
  • 推出的元素有 {5},栈中的元素有 [] 。
  • 在推入 5 的子节点就是 3,8,我们先入后出,先推入 8 再推入 3,现在堆栈的元素有 [8,3],栈顶的 3 就是我们下一次要访问的节点所以把 3 推出 。
  • 推出的元素有 {5,3},栈中的元素有 [8] 。
  • 在推入 3 的子节点就是 2,4 继续先入后出,先推入 4 再推入 2,现在堆栈的元素有 [8,4,2],栈顶的 2 就是我们下一次要访问的节点所以把 2 推出 。
  • 推出的元素有 {5,3,2},栈中的元素有 [8,4] 。
  • 访问栈顶 4,由于 2 和 4 没有子节点。所以我们直接把栈顶中的 4 推出 。
  • 推出的元素有 {5,3,2,4},栈中的元素有 [8] 。
  • 访问栈顶 8 把 8 推出栈堆,再把 8 的子节点 7、9 推入栈中,先推入 9 后推入 7 。
  • 推出的元素有 {5,3,2,4,8},栈中的元素有 [9,7] 。
  • 访问 7,没有子节点,推出。
  • 推出的元素有 {5,3,2,4,8,7},栈中的元素有 [9] 。
  • 访问 9,没有子节点,推出。
  • 推出的元素有 {5,3,2,4,8,7,9},栈中的元素有 [] 。


5、中序遍历中序遍历,访问顺序是左根右,也就是先访问左子树,再到根节点,最后才到右子树。所以上图所示的访问顺序是 2、3、4、5、7、8、9。


二分搜索树中序遍历递归版与非递归版


 

//递归调用
    public void inOrder(){
        inOrder(root);
    }
    //二分搜索树的中序遍历递归写法
    private void inOrder(Node root){
        if (root == null)
            return;
        inOrder(root.left);
        System.out.println(root.e);
        inOrder(root.right);
    }
    //二分搜索树中序遍历给递归写法
    public void preInOrderNG(){
        // 创建栈,和前序遍历类似
        Stack<Node> stack = new Stack<>();
        Node node = root;
        //添加暂时完毕,开始pop元素
        while(node!=null || stack.size()>0 ){
            while(node!=null){
                stack.push(node);
                node = node.left;
            }
            //一边pop并且一边进行判断,右结点不会null的,右子树,继续按照添加方法,将最左结点全部添加进去
            if(stack.size()>0){
                Node pop = stack.pop();
                System.out.print(pop.e+"  ");
                if(pop.right!=null){
                    node = pop.right;
                }
            }
        }


理解非递归的实现逻辑、推导出中序递归的实现


  • 首先我们把 5 这个节点推入栈中,再把 5 的左子节点 3 推入,再把 3的 左子节点 2 推入,再把 2 的左子节点推入(此时 2 的左子节点为空,node==null while 循环退出)。
  • 推出的元素有 {},栈中的元素有 [5,3,2]。
  • node 为空,但我们栈中还有元素,访问栈顶元素 2,并查看 2 是否有右子节点。没有则推出栈并结束循环。
  • 推出的元素有 {2},栈中的元素有 [5,3]。
  • 访问栈顶元素 3,把 3 推出栈中,并把 3 的右子节点 4 推入栈中,结束循环。
  • 推出的元素有 {2,3},栈中的元素有 [5]。
  • 访问栈顶元素5,把5推出栈中。把5的右子节点8推入栈中,并把8的左子节点7推入栈中,结束循环。
  • 推出的元素有 {2,3,5},栈中的元素有 [8,7]
  • 访问栈顶元素 7,并查看 2 是否有右子节点。没有则推出栈并结束循环。
  • 推出的元素有 {2,3,5,7},栈中的元素有 [8]。
  • 访问栈顶元素 8,把 8 推出栈中。把 8 的右子节点 9 推入栈中
  • 推出的元素有 {2,3,5,7,8},栈中的元素有 [9]。
  • 访问栈顶元素 9,并查看 2 是否有右子节点。没有则推出栈并结束循环。
  • 推出的元素有 {2,3,4,5,7,8,9},栈中的元素有 []。


6、后续遍历


中序遍历,访问顺序是左右根,也就是先访问左子树,再到右子树,最后才到根节点。所以上图所示的访问顺序是 2、4、3、7、9、8、5。二分搜索树后序遍历递归版与非递归版


//递归调用
public void postOrder() {
    postOrder(root);
} 
//二分搜索树的后序遍历递归方法
private void postOrder(Node node){
    if (node == null)
        return;
    postOrder(node.left);
    postOrder(node.right);
    System.out.println(node.e);
} 
public void postOrderNG(){
    Stack<Node> stack = new Stack<>();
    //利用一个list集合记录已将被遍历过的根节点,防止产生死循环
    ArrayList<Node> list = new ArrayList<>();
    Node node = root;
    Node proud; 
    int flag; 
    //首页检查完树的左子树,再右子数,最后将根节点输出
    while (node != null || stack.size() > 0){
        //将最左子树添加完毕
        while (node != null){
            stack.push(node);
            node = node.left;
        } 
        //和中序遍历相似,为先输出左子节点,但是做节点输出完毕之后,不能直接将根节点弹出,而是必须先将右节点弹出,
        //最后再将根节点弹出来,就会牵扯到一个根节点的访问状态的问题,是否已经被遍历过了
        if (stack.size() > 0){
            Node peek = stack.peek();
            if (peek.right != null){
                boolean con = list.contains(peek);
                if (con){
                    Node pop = stack.pop();
                    System.out.println(pop.e);
                }else{
                    list.add(peek);
                    node = peek.right;
                }
            }else {
                Node pop = stack.pop();
                System.out.println(pop.e);
            }
        }
    }
}


理解非递归的实现逻辑、推导出后序递归的实现


  • 把 5 这个节点推入栈中,再把 5 的左子节点 3 推入,再把 3 的左子节点 2 推入,再把 2 的左子节点推入(此时 2 的左子节点为空,node==null while 循环退出)。
  • 推出的元素有 {},栈中的元素有 [5,3,2]。
  • node 为空但我们栈中还有元素,访问栈顶元素 2,并查看 2 是否有左子节点。没有则推出栈并结束循环。
  • 推出的元素有 {2},栈中的元素有 [5,3]。
  • 访问栈顶元素 3,3 的右子节为 4,判断 list 中是否有 3,没有则把 3 放入 list 中并把 node 赋值为 4 结束循环。
  • 推出的元素有 {2},栈中的元素有 [5,3]。
  • node 为 4,把 4 推入栈中,并访问栈顶元素 4,并查看 4 是否有右子节点。没有则推出栈并结束循环。
  • 推出的元素有 {2,4},栈中的元素有 [5,3]。
  • 访问栈顶元素 3,list 中有 3,把 3 的推出栈中并结束循环。
  • 推出的元素有 {2,4,3},栈中的元素有 [5]。
  • 访问栈顶元素 5,5 的右子节为 8,判断 lis t中是否有 8,没有则把 5 放入 list 中并把 node 赋值为 8 结束循环。
  • 推出的元素有 {2,4,3},栈中的元素有 [5]。
  • node 为 8,把 8 推入栈中,并访问栈顶元 素8,8 有左子节点为 7。把 7 推入栈中。
  • 推出的元素有 {2,4,3},栈中的元素有 [5,8,7]。
  • 访问栈顶元素 7,并查看 7 是否有右子节点。没有则推出栈并结束循环。
  • 推出的元素有 {2,4,3,7},栈中的元素有 [5,8]。
  • 访问栈顶元素 8,8 的右子节点为 9。判断 list 中是否有 9,没有则把 8 放入 list 中并把 node 并把 node 赋值为 9 结束循环。
  • 推出的元素有 {2,4,3,7},栈中的元素有 [5,8]。
  • node 为 9,把 9 推入栈中,并访问栈顶元素 9,并查看 9 是否有右子节点。没有则推出栈并结束循环。
  • 推出的元素有 {2,4,3,7,9},栈中的元素有 [5,8]。
  • 访问栈顶元素 8,list 中有 8,把 8 的推出栈中并结束循环。
  • 推出的元素有 {2,4,3,7,9,8},栈中的元素有 [5]。
  • node 为空栈中还有元素,访问栈顶元素 5,list 中有 5,把 5 的推出栈中并结束循环。
  • 推出的元素有 {2,4,3,7,9,8,5},栈中的元素有 []。
相关文章
|
1月前
|
算法
分享一些提高二叉树遍历算法效率的代码示例
这只是简单的示例代码,实际应用中可能还需要根据具体需求进行更多的优化和处理。你可以根据自己的需求对代码进行修改和扩展。
|
1月前
|
存储 缓存 算法
如何提高二叉树遍历算法的效率?
选择合适的遍历算法,如按层次遍历树时使用广度优先搜索(BFS),中序遍历二叉搜索树以获得有序序列。优化数据结构,如使用线索二叉树减少空指针判断,自定义节点类增加辅助信息。利用递归与非递归的特点,避免栈溢出问题。多线程并行遍历提高速度,注意线程安全。缓存中间结果,避免重复计算。预先计算并存储信息,提高遍历效率。综合运用这些方法,提高二叉树遍历算法的效率。
58 5
|
1月前
|
算法
树的遍历算法有哪些?
不同的遍历算法适用于不同的应用场景。深度优先搜索常用于搜索、路径查找等问题;广度优先搜索则在图的最短路径、层次相关的问题中较为常用;而二叉搜索树的遍历在数据排序、查找等方面有重要应用。
38 2
|
1月前
|
机器学习/深度学习 JSON 算法
二叉树遍历算法的应用场景有哪些?
【10月更文挑战第29天】二叉树遍历算法作为一种基础而重要的算法,在许多领域都有着不可或缺的应用,它为解决各种复杂的问题提供了有效的手段和思路。随着计算机科学的不断发展,二叉树遍历算法也在不断地被优化和扩展,以适应新的应用场景和需求。
42 0
|
1月前
|
算法 搜索推荐 数据库
二分搜索:高效的查找算法
【10月更文挑战第29天】通过对二分搜索的深入研究和应用,我们可以不断挖掘其潜力,为各种复杂问题提供高效的解决方案。相信在未来的科技发展中,二分搜索将继续发挥着重要的作用,为我们的生活和工作带来更多的便利和创新。
50 1
|
2月前
|
存储 算法 关系型数据库
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
这篇文章主要介绍了多路查找树的基本概念,包括二叉树的局限性、多叉树的优化、B树及其变体(如2-3树、B+树、B*树)的特点和应用,旨在帮助读者理解这些数据结构在文件系统和数据库系统中的重要性和效率。
31 0
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
|
2月前
|
存储 算法
数据结构与算法学习十六:树的知识、二叉树、二叉树的遍历(前序、中序、后序、层次)、二叉树的查找(前序、中序、后序、层次)、二叉树的删除
这篇文章主要介绍了树和二叉树的基础知识,包括树的存储方式、二叉树的定义、遍历方法(前序、中序、后序、层次遍历),以及二叉树的查找和删除操作。
31 0
|
2月前
|
存储 算法 Java
数据结构和算法--分段树
数据结构和算法--分段树
23 0
|
17天前
|
算法
基于WOA算法的SVDD参数寻优matlab仿真
该程序利用鲸鱼优化算法(WOA)对支持向量数据描述(SVDD)模型的参数进行优化,以提高数据分类的准确性。通过MATLAB2022A实现,展示了不同信噪比(SNR)下模型的分类误差。WOA通过模拟鲸鱼捕食行为,动态调整SVDD参数,如惩罚因子C和核函数参数γ,以寻找最优参数组合,增强模型的鲁棒性和泛化能力。