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

简介: 又是来自我的好朋友 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},栈中的元素有 []。
相关文章
|
3月前
|
机器学习/深度学习 算法 安全
【无人机三维路径规划】基于非支配排序的鲸鱼优化算法NSWOA与多目标螳螂搜索算法MOMSA求解无人机三维路径规划研究(Matlab代码实现)
【无人机三维路径规划】基于非支配排序的鲸鱼优化算法NSWOA与多目标螳螂搜索算法MOMSA求解无人机三维路径规划研究(Matlab代码实现)
250 5
|
3月前
|
机器学习/深度学习 算法 安全
【无人机三维路径规划】多目标螳螂搜索算法MOMSA与非支配排序的鲸鱼优化算法NSWOA求解无人机三维路径规划研究(Matlab代码实现)
【无人机三维路径规划】多目标螳螂搜索算法MOMSA与非支配排序的鲸鱼优化算法NSWOA求解无人机三维路径规划研究(Matlab代码实现)
190 0
|
2月前
|
算法 数据可视化 测试技术
HNSW算法实战:用分层图索引替换k-NN暴力搜索
HNSW是一种高效向量检索算法,通过分层图结构实现近似最近邻的对数时间搜索,显著降低查询延迟。相比暴力搜索,它在保持高召回率的同时,将性能提升数十倍,广泛应用于大规模RAG系统。
246 10
HNSW算法实战:用分层图索引替换k-NN暴力搜索
|
2月前
|
存储 机器学习/深度学习 监控
网络管理监控软件的 C# 区间树性能阈值查询算法
针对网络管理监控软件的高效区间查询需求,本文提出基于区间树的优化方案。传统线性遍历效率低,10万条数据查询超800ms,难以满足实时性要求。区间树以平衡二叉搜索树结构,结合节点最大值剪枝策略,将查询复杂度从O(N)降至O(logN+K),显著提升性能。通过C#实现,支持按指标类型分组建树、增量插入与多维度联合查询,在10万记录下查询耗时仅约2.8ms,内存占用降低35%。测试表明,该方案有效解决高负载场景下的响应延迟问题,助力管理员快速定位异常设备,提升运维效率与系统稳定性。
233 4
|
4月前
|
机器学习/深度学习 算法 文件存储
神经架构搜索NAS详解:三种核心算法原理与Python实战代码
神经架构搜索(NAS)正被广泛应用于大模型及语言/视觉模型设计,如LangVision-LoRA-NAS、Jet-Nemotron等。本文回顾NAS核心技术,解析其自动化设计原理,探讨强化学习、进化算法与梯度方法的应用与差异,揭示NAS在大模型时代的潜力与挑战。
1182 6
神经架构搜索NAS详解:三种核心算法原理与Python实战代码
|
3月前
|
存储 算法 数据可视化
基于禁忌搜索算法的TSP问题最优路径搜索matlab仿真
本程序基于禁忌搜索算法解决旅行商问题(TSP),旨在寻找访问多个城市的最短路径。使用 MATLAB 2022A 编写,包含城市坐标生成、路径优化及结果可视化功能。通过禁忌列表、禁忌长度与藐视准则等机制,提升搜索效率与解的质量,适用于物流配送、路径规划等场景。
|
4月前
|
机器学习/深度学习 并行计算 算法
MATLAB实现利用禁忌搜索算法解决基站选址问题
MATLAB实现利用禁忌搜索算法解决基站选址问题
182 0
|
5月前
|
监控 算法 安全
基于 C# 基数树算法的网络屏幕监控敏感词检测技术研究
随着数字化办公和网络交互迅猛发展,网络屏幕监控成为信息安全的关键。基数树(Trie Tree)凭借高效的字符串处理能力,在敏感词检测中表现出色。结合C#语言,可构建高时效、高准确率的敏感词识别模块,提升网络安全防护能力。
148 2
|
7月前
|
监控 算法 数据处理
基于 C++ 的 KD 树算法在监控局域网屏幕中的理论剖析与工程实践研究
本文探讨了KD树在局域网屏幕监控中的应用,通过C++实现其构建与查询功能,显著提升多维数据处理效率。KD树作为一种二叉空间划分结构,适用于屏幕图像特征匹配、异常画面检测及数据压缩传输优化等场景。相比传统方法,基于KD树的方案检索效率提升2-3个数量级,但高维数据退化和动态更新等问题仍需进一步研究。未来可通过融合其他数据结构、引入深度学习及开发增量式更新算法等方式优化性能。
207 17
|
5月前
|
存储 搜索推荐 算法
加密算法、排序算法、字符串处理及搜索算法详解
本文涵盖四大类核心技术知识。加密算法部分介绍了对称加密(如 AES)、非对称加密(如 RSA)、哈希摘要(如 SHA-2)、签名算法的特点及密码存储方案(加盐、BCrypt 等)。 排序算法部分分类讲解了比较排序(冒泡、选择、插入、归并、快排、堆排序)和非比较排序(计数、桶、基数排序)的时间复杂度、适用场景及实现思路,强调混合排序的工业应用。 字符串处理部分包括字符串反转的双指针法,及项目中用正则进行表单校验、网页爬取、日志处理的实例。 搜索算法部分详解了二分查找的实现(双指针与中间索引计算)和回溯算法的概念(递归 + 剪枝),以 N 皇后问题为例说明回溯应用。内容全面覆盖算法原理与实践
205 0

热门文章

最新文章