二叉查找树(1)-二叉树-数据结构和算法(Java)

news2024/11/29 10:34:07

文章目录

    • 1 前言
      • 1.1 二叉查找树定义
      • 1.2 二叉查找树的性质
    • 2 基本实现
      • 2.1 API
      • 2.2 实现代码
        • 2.2.1 数据表示
        • 2.2.2 查找
        • 2.2.3 插入
    • 3 分析
    • 4 有序性相关方法与删除操作
      • 4.1 最大键和最小键
      • 4.2 向上取整和向下取整
        • 4.2.1 向下取整floor()
        • 4.2.2 向上取整ceiling()
      • 4.3 选择select()
      • 4.4 排名rank()
      • 4.5 删除最大键和最小键
      • 4.6 删除操作
      • 4.7 后续方法

1 前言

树是一种数据结构,树的相关基础知识的和体系介绍可以查看百度百科关于树的介绍或者文章最后的文献索引,这里不再详述。

我们会陆续讲解二叉查找树,2-3查找树,红黑树,B树,B+树和B*树。

1.1 二叉查找树定义

二叉查找树(Binary Search Tree),简称BST(又称二叉搜索树),是二叉树的一种,其中每个节点的值大于左子树的值(如果左子树存在)而小于右子树的值(如果右子树存在)。

1.2 二叉查找树的性质

  • 若左子树不为空,则左子树节点的值小于其根节点的值。
  • 若右子树不为空,则右子树节点的值大于其根节点的值。
  • 左右子树均为二叉查找树。
  • 所有节点的键值不重复。

2 基本实现

相对更简单的理解和实现使用递归方式,但是如果数据量大的话递归方式就不适用了,所有呢这里实现不采用递归方式。如果想要了解递归实现的话,可以查看文章末尾算法第4版或者视频。

2.1 API

API同之前有序符号表的API一致,可以查看之前的文章基础-符号表(一)-数据结构和算法(Java)

2.2 实现代码

2.2.1 数据表示

树节点代码如下:

/**
     * 内部节点类
     * @param <K>
     * @param <V>
     */
    static class Node<K, V> {
        /**
         * 键
         */
        K key;

        /**
         * 值
         */
        V value;

        /**
         * 左子树
         */
        Node<K, V> left;

        /**
         * 右子树
         */
        Node<K, V> right;

        /**
         * 该节点为根的树中节点总数
         */
        int size;

        Node(K key, V value, Node<K, V> left, Node<K, V> right, int size) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
            this.size = 1;
        }

        Node(K key, V value) {
            this(key, value, null, null, 1);
        }
    }

2.2.2 查找

查找一个键有两种结果,如果包含键的节点存在与表中,我们的查找就命中,返回相应的值。否则未命中,返回null。

  • 如果表为空,直接返回null

  • 根节点root赋值给当前节点变量cur,只要当前节点不为null,循环执行一下操作

    • 比较当前节点的键和目标键key的大小

    • 如果目标键小于当前节点的键,说明目标节点在当前节点的左子树中,把当前节点的左子节点cur.left赋值给cur,即当前节点的左子节点变为了下次循环的当前节点

    • 如果目标键大于当前节点的键,说明目标节点在当前节点的右子树中,把当前节点的右子节点cur.right赋值给cur,即当前节点的右子节点变为了下次循环的当前节点

    • 如果目标键等于当前节点的键,说明命中目标节点,直接返回当前节点的值。

  • 如果循环结束即查找到某个空节点,仍然为命中目标,说明该键不在表中,返回null。

代码2.2.2-1如下:

/**
 * 获取key对应的值
 * @param key   键
 * @return  指定键对应的值
 */
@Override
public V get(K key) {
    return get(root ,key);
}

/**
 * 从根结点开始查找键对应的值
 * @param root  根结点
 * @param key   指定的键
 * @return      键对应的值
 */
private V get(Node<K,V> root, K key) {
    if (size == 0) {
        return null;
    }
    Node<K, V>  cur = root;
    while (cur != null) {
        if (key.compareTo(cur.key) < 0) {
            cur = cur.left;
        } else if (key.compareTo(cur.key) > 0) {
            cur = cur.right;
        } else {
            return cur.value;
        }
    }
    return null;
}

2.2.3 插入

实现代码2.2.3-1如下:

/**
     * 插入键值对,如果key存在则替换旧值;如果不存在,在合适位置插入新的键值对
     * @param key       键
     * @param value     值
     */
@Override
public void put(K key, V value) {

    // 判断表是否为空
    if (root == null) {
        root = new Node<>(key, value);
        return;
    }
    // 父节点
    Node<K, V> f = null;
    // 当前节点
    Node<K, V> cur = root;
    // 新插入节点是否为左节点:true-左节点,false-右节点
    boolean left = true;
    // 查找key是否在表中
    while (cur != null) {
        cur.size++;
        if (key.compareTo(cur.key) < 0) {
            // 给定的键小于当前节点的键,继续在左子树查找
            f = cur;
            cur = cur.left;
            left = true;
        } else if (key.compareTo(cur.key) > 0) {
            // 给定的键大于当前节点的键,继续在右子树查找
            f = cur;
            cur = cur.right;
            left = false;
        } else {
            // 给定的键等于当前节点的键,新值替换旧值
            cur.value = value;
            return;
        }
    }
    // key不在表中,插入新节点
    Node<K, V> newNode = new Node<>(key, value);
    if (left) {
        f.left = newNode;
    } else {
        f.right = newNode;
    }
}

算法分析:代码给的注释如果明了可跳过以下解释

  • 判断表是否为空
    • 为空,新建节点,且指定为根节点,return。
    • 不为空,先查找给的键是否在表中
      • 设置3个变量
        • f:当前节点的父节点,初始值null
        • cur:当前节点,初始值root根节点
        • left:当前节点是否是父节点的左子节点,初始值true
      • 循环以下操作:循环条件当前节点不为空,表示键有可能在表中
        • 路径上每个节点的计数+1
        • 判断key小于当前节点的键,则继续在当前节点的左子树中查找
          • 当前节点变为下次循环的父节点
          • 当前节点的左节点变为下次循环的当前节点
          • left设置为true表示当前节点是父节点的左子节点
        • 判断key大于当前节点的键,则继续在当前节点的右子树中查找
          • 当前节点变为下次循环的父节点
          • 当前节点的右节点变为下次循环的当前节点
          • left设置为false表示当前节点是父节点的右子节点
        • 否则key等于当前节点的键,命中目标,直接新值替换旧值,return。
      • 循环结束未命中目标,说明key不在表中,需要插入新的节点,插入位置就是cur,父节点就是f
      • 新建节点
      • 判断left
        • true,把新节点设置为父节点的左子节点
        • false,把新节点设置为父节点的右子节点。

执行流程图2.2.3-1如下所示:在这里插入图片描述

命中节点,未命中,插入为左子树,未命中,插入为右子树如下2.2.3-2所示:

在这里插入图片描述

3 分析

使用二叉查找树的算法的运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。在最好的情况下,一棵含义N个节点的树是完全平衡的,叶子结点和根节点的距离都为 ∼ lg ⁡ N \sim\lg N lgN。在最坏情况下,搜索路径上可能有N个节点。

我们假设键的分布是(均匀)随机的,或者说它们的插入顺序是随机的。二叉查找树和快速排序几乎一样,树的根节点就是快速排序中的第一个切分元素(左侧的键都比它小,右侧的键都比它大),而这对于所有的子树同样适用。

命题C:在由N个随机键构造的二者查找树中,查找命中平均所需的比较次数为 ∼ ln ⁡ N ( 约 为 1.39 lg ⁡ N ) \sim\ln N(约为1.39\lg N) lnN(1.39lgN)

证明:暂时不证明,因为本人没研究明白呢😢

4 有序性相关方法与删除操作

二叉查找树得以广泛应用的一个重要的原因就是它能够保持键的有序性,因此它可以做为实现有序符号表API中的众多方法的基础。这使得符号表的用例不仅能够通过键还能同键的相对顺序来访问键值对。

4.1 最大键和最小键

  • 查找最小键算法
    • 如果根节点为空,直接返回null;
    • 根节点设置为当前节点,循环判断当前节点是否为空
      • 不为空,极限查找当前节点的左子树
      • 为空那么当前节点的父节点就是最小键节点。
  • 查找最大键算法
    • 如果根节点为空,直接返回null;
    • 根节点设置为当前节点,循环判断当前节点是否为空
      • 不为空,极限查找当前节点的→子树
      • 为空那么当前节点的父节点就是最大键节点。

非递归实现代码如下4.1-1所示:

/**
* 获取最小键
* @return  最小键
*/
@Override
public K min() {
    if (isEmpty()) {
        return null;
    }
    Node<K, V> f = null;
    Node<K, V> cur = root;
    while (cur != null) {
        f = cur;
        cur = cur.left;
    }
    return f.key;
}

/**
* 获取最大键
* @return  最大键
*/
@Override
public K max() {
    if (isEmpty()) {
    	return null;
    }
    Node<K, V> f = null;
    Node<K, V> cur = root;
    while (cur != null) {
        f = cur;
        cur = cur.right;
    }
    return f.key;
}

4.2 向上取整和向下取整

如果目标键在树中,向上取整和向下取整都是目标键本身;否则,向下取整就是取小于给定值的键的最大键,即寻找目标节点的前驱节点。向上取整就是取大于给定键的最小键,即寻找目标节点的后继节点。

关于前驱和后继节点不了解的自己去搜索一下。

4.2.1 向下取整floor()

算法:

  • 如果根节点root为空,直接返回null
  • 否则root赋值给当前节点,prev为前驱节点默认null;
  • 循环判断当前节点不为空
    • 如果给定key小于当前节点的key,继续查找当前节点的左子树
    • 如果给定key大于当前节点的key,cur赋值给prev,继续查找右子树
    • 如果给定key等于当前节点的key直接返回当前节点的key
  • 判断prev是否为空
    • 为空,返回null
    • 不为空,返回prev.key

逻辑就是如果目标key小于当前节点的key,那么小于等于key最大键一定(如果有)出现在当前节点的左子树;

如果目标key大于当前节点的key,那么只有当前节点右子树中有小于等于目标key的节点存在时,小于等于目标key的最大键才会出现在当前节点的右子树中,否则当前节点就是小于等于目标key的最大键。

代码4.2.1如下:

/**
 * 向下取整,获取小于给定键的最大键
 * @param key   目标键
 * @return  小于给定键的最大键
 */
@Override
public K floor(K key) {
    if (isEmpty()) {
        return null;
    }
    Node<K, V>  cur = root;
    Node<K, V> prev = null;
    while (cur != null) {
        if (key.compareTo(cur.key) < 0) {
            cur = cur.left;
        } else if (key.compareTo(cur.key) > 0) {
            prev = cur;
            cur = cur.right;
        } else {
            return cur.key;
        }
    }
    return prev == null ? null: prev.key;
}

4.2.2 向上取整ceiling()

直接给代码,可对比向下取整,代码。4.2.2-1如下:

/**
 * 向上取整,获取大于给定键的最小值
 * @param key   目标键
 * @return  大于给定键的最小值
 */
@Override
public K ceiling(K key) {
    if (isEmpty()) {
        return null;
    }
    Node<K, V>  cur = root;
    Node<K, V> next = null;
    while (cur != null) {
        if (key.compareTo(cur.key) < 0) {
            next = cur;
            cur = cur.left;
        } else if (key.compareTo(cur.key) > 0) {
            cur = cur.right;
        } else {
            return cur.key;
        }
    }
    return next == null ? null: next.key;
}

4.3 选择select()

代码4.3.1如下:

/**
 * 返回排序为k的键
 * @param k 排名
 * @return 排序为k的键
 */
@Override
public K select(int k) {
    if (isEmpty()) {
        return null;
    }
    int i = k;
    Node<K, V>  cur = root;
    while (cur != null) {
        int l     /**
     * 返回排序为k的键
     * @param k 排名
     * @return 排序为k的键
     */
    @Override
    public K select(int k) {
        if (isEmpty()) {
            return null;
        }
        int n = k;
        Node<K, V>  cur = root;
        while (cur != null) {
            int nl = cur.left == null ? 0: cur.left.size;
            if (nl  > n) {
                cur = cur.left;
            } else if (nl < n) {
                n = n - nl - 1;
                cur = cur.right;
            } else {
                return cur.key;
            }
        }
        return null;
    }= cur.left == null ? 0: cur.left.size;
        if (l  > i) {
            cur = cur.left;
        } else if (l < i) {
            i = i - l - 1;
            cur = cur.right;
        } else {
            return cur.key;
        }
    }
    return null;
}

排序为k,因为键有序,就是我们说的索引,返回索引为k的节点对应的键,算法如下:

  • 如果树为空,直接返回null
  • 根节点置为当前节点,目标索引赋值为n
  • 循环判断当前节点不为空
    • 取当前节点左子节点的size赋值为nl,nl表示当前节点的索引值
      • 每个节点左子树节点上的size就代表当前节点在以当前节点为根的树的索引
    • 把当前节点索引值和目标索引比较
      • 如果当前节点索引大于目标索引n,那么继续在左子树中查找目标索引
      • 如果当前节点索引大于目标索引n,那么我们在右子树中查找索引为n=n-nl-1
        • 在右子树中查找,即相当于在以右子节点为根的新的树中查找,索引需要减去当前节点索引+1
      • 如果当前节点索引等于n,命中目标节点,返回当前节点的键
  • 循环结束,索引超出范围,返回null

4.4 排名rank()

代码如下:

/**
 * 小于key的键的数量
 * @param key    目标key
 * @return  小于key的键的数量
 */
@Override
public int rank(K key) {
    if (isEmpty()) {
        return 0;
    }
    Node<K, V>  cur = root;
    int n = 0;
    while (cur != null) {
        if (key.compareTo(cur.key) < 0) {
            cur = cur.left;
        } else if (key.compareTo(cur.key) > 0) {
            n += 1 + (cur.left == null ?  0: cur.left.size);
            cur = cur.right;
        } else {
            break;
        }
    }
    return n;
}

算法如下:

  • 如果树为空,直接返回0。
  • root赋值给当前节点cur,初始计数n=0。
  • 循环判断当前节点不为空
    • 如果key小于当前节点的key,继续在当前节点的左子树中查找
    • 如果key大于当前节点key,计数需要加上当前节点1+当前节点左子树中节点个数
      • 如果当前节点左子树为空+0
    • 如果命中目标,break结束循环
  • 返回n

4.5 删除最大键和最小键

以删除最小键为例,算法逻辑

  • 从根结点开始循环查找左子节点,直到左子节点为空,路径上节点size–
  • 为空的左子节点的父节点即是要删除的目标节点
  • 如果目标节点的父节点有右子节点,直接把父节点左连接指向该节点
  • 没有直接情况目标节点断开连接

代码如下:

/**
 * 删除最小的键对应的节点
 * @return  要删除的最小键对应的值
 */
@Override
public V deleteMin() {
    if (isEmpty()) {
        // 树为空
        return null;
    }
    // 祖父节点
    Node<K, V> s = null;
    // 父节点
    Node<K, V> f = null;
    // 当前节点
    Node<K, V> cur = root;
    while (cur != null) {
        s = f;
        f = cur;
        cur = cur.left;
    }
    if (s == null) {
        // 根节点为最小键
        root = root.right;
    } else if (f.right == null){
        s.left = null;
    } else {
        s.left = f.right;
    }
    V oldVal = f.value;
    // 清空最小键节点,等待GC
    f.key = null;
    f.value = null;
    f.right = null;
    return oldVal;
}

树为空和根节点为最小键的情况,这里不再画示意图。最小键节点左连接肯定是null,下面给出有链接为null和不为空的示意图:

  • 最小键节点右链接为空和不为空示意图4.5-1:在这里插入图片描述

删除最大键参考删除最小键,不再详述,代码4.5-2如下:

/**
 * 删除最大键对应的键值对
 * @return  最大键对应的值
 */
@Override
public V deleteMax() {
    if (isEmpty()) {
        // 树为空
        return null;
    }
    // 祖父节点
    Node<K, V> s = null;
    // 父节点
    Node<K, V> f = null;
    // 当前节点
    Node<K, V> cur = root;
    while (cur != null) {
        s = f;
        f = cur;
        cur = cur.right;
    }
    if (s == null) {
        // 根节点为最小键
        root = root.left;
    } else if (f.left == null){
        s.right = null;
    } else {
        s.right = f.left;
    }
    V oldVal = f.value;
    // 清空最小键节点,等待GC
    f.key = null;
    f.value = null;
    f.left = null;
    return oldVal;
}

4.6 删除操作

算法逻辑:

  • 如果目标key节点存在树中,删除目标节点后,为了维护二叉查找树性质,需要调整。
  • 调整过程,先查找目标节点在右子树中的后继节点,如果有用后继节点替换;
  • 如果没有后继节点,查找目标节点在左子树中是否有前驱节点,如果有用前驱节点替换目标节点;
  • 如果即没有前驱也没有后继即目标节点无子节点,直接断开连接。
  • 过程中记录查找路径上的节点,在执行返回之前把存储的节点size–;
  • 最后执行清除操作,等待GC,返回key对应的值。
  • 目标key不在树中直接返回null

目标key节点存在于树中,且在子树有后继或者前驱,最终效果是用其后继或者前驱的键值对替换了目标节点的键值对,然后删除相应的后继或者前驱节点。所以我们的删除操作采用这样方式,而不是先删除目标节点在对树做调整。

代码4.6-1如下:

/**
 * 删除键对应的键值对
 * @param key   键
 * @return      要删除键对应的值
 */
@Override
public V delete(K key) {
    if (root == null) {
        return null;
    }
    // 父节点
    Node<K, V>  xp = null;
    // 当前节点
    Node<K, V>  x = root;
    // 路径上的节点
    Stack<Node<K, V>> minus = new Stack<>();
    while (x != null) {
        minus.push(x);
        if (key.compareTo(x.key) < 0) {
            xp = x;
            x = x.left;
        } else if (key.compareTo(x.key) > 0) {
            xp = x;
            x = x.right;
        } else {
            if (x.right != null) {
                // 查找后继节点
                Node<K, V> xrp = x;
                Node<K, V> xr = x.right;
                while (xr != null) {
                    minus.push(xr);
                    xp = xrp;
                    xrp = xr;
                    xr = xr.left;
                }
                // 交换内容
                K xk = x.key;
                V xv = x.value;
                x.key = xrp.key;
                x.value = xrp.value;
                xrp.key = xk;
                xrp.value =xv;
                x = xrp;
            } else if (x.left != null) {
                // 查找前驱节点
                Node<K, V> xlp = x;
                Node<K, V> xl = x.left;
                while (xl != null) {
                    minus.push(xl);
                    xp = xlp;
                    xlp = xl;
                    xl = xl.right;
                }
                // 交换内容
                K xk = x.key;
                V xv = x.value;
                x.key = xlp.key;
                x.value = xlp.value;
                xlp.key = xk;
                xlp.value =xv;
                x = xlp;
            }
            // 删除目标节点
            if (xp != null) {
                if (x.left != null) {
                    if (x == xp.left) {
                        xp.left = x.left;
                    } else {
                        xp.right = x.left;
                    }
                } else if (x.right != null) {
                    if (x == xp.left) {
                        xp.left = x.right;
                    } else {
                        xp.right = x.right;
                    }
                } else{
                    if (x == xp.left) {
                        xp.left = null;
                    } else {
                        xp.right = null;
                    }
                }
            } else {
                // 树只有一个根节点,删除的节点也是根节点
                root = null;
            }
             // 记录key对应的值,清空目标节点,等待GC
            V oldVal = x.value;
            x.key = null;
            x.value = null;
            x.left = null;
            x.right = null;
            // 路径上所有节点计数-1
            for (Node<K, V> m : minus) {
                m.size--;
            }
            return oldVal;
        }
    }
    return null;
}

4.7 后续方法

后面涉及树的遍历问题,我们会单独写一篇讲解,讲解玩树的遍历问题后,在讲解后续方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/41534.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微服务框架 SpringCloud微服务架构 3 Eureka 3.1 提供者与消费者

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构3 Eureka3.1 提供者与消费者3.1.1 一些概念3.1.2 一个问题3.1.3 总结3 Eur…

【Hack The Box】linux练习-- Pit

HTB 学习笔记 【Hack The Box】linux练习-- Pit &#x1f525;系列专栏&#xff1a;Hack The Box &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年11月27日&#x1f334; &#x1f36d;…

单片机如何控制外设?

单片机如何控制外设&#xff1f; 单片机不是直接控制外设的&#xff0c;而是通过配置片上外设寄存器来控制其输出和检测高低电平&#xff0c;进而控制外围器件。 单片机如何配置寄存器的&#xff1f; 下图是单片机驱动 点亮发光二极管 内核从flash里面加载读取指令。内核根据指…

将DataFrame中符合指定条件的数据替换为指定的值:mask()函数

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 将DataFrame中符合指定条件的数据 替换为指定的值&#xff1a;mask()函数 选择题 下列说法错误的是? import pandas as pd myDF pd.DataFrame({"A":[1,2,3], "B":[4,…

java EE初阶 — 如何进行多线程编程

文章目录1.java如何进行多线程编程1.1 最基础的多线程代码1.2 线程的优势2.java中创建线程的方法2.1 继承Thread 重写run2.2 实现Runnable 接口2.3 使用匿名的内部类&#xff0c;继承 Thread2.4 使用匿名类。继承 Runnable2.5 使用 Lambda 表达式&#xff08;最简答、最推荐&am…

2000-2020年迪博上市公司内部控制指数

2000-2020年迪博上市公司内部控制指数 1、时间&#xff1a;2000-2020年 2、指标包括&#xff1a; 证券代码、证券简称、辖区、证监会行业、申万行业、内部控制指数、报告期 3、指标解释&#xff1a; 上市公司内部控制指数是结合国内上市公司实施内部控制体系的现状&#x…

java——电商购物平台

1.java编译环境的创建&#xff0c;与所需要用到的插件 (1) 选择的编译器为2022版本的intellij idea 首先新建一个空项目 同时,创建完之后,我们点击 文件 -> 项目结构 进入项目结构,点击项目,选择好你想要的jdk版本,若没有,可以在idea中下载,这里我使用openjdk-19, 点击模…

Python适合0基础菜鸟学吗

前言 经常有小伙伴问&#xff1a;Python适合0基础初学编程的人学吗&#xff1f;今天我们就来从Python的功能和特性方面看一下&#xff0c;Python是否能让新人快速上手。 1、非常适合。我觉得刚开始学编程&#xff0c;负担越少越好&#xff0c;应该尽快能做出东西来。刚开始学…

day02 linux常用命令

day02 linux 第一章 Linux常用命令 第一节 进程相关命令 1. 查看进程状态 1.1 命令的使用 ps命令是用于查看进程状态的命令&#xff0c;它常和e参数(对应单词entire&#xff0c;表示全部。具体指显示系统中全部的进程信息。)&#xff0c;以及f参数(对应单词full-formate&a…

Node.js 入门教程 13 在 Node.js 中从命令行接收输入

Node.js 入门教程 Node.js官方入门教程 Node.js中文网 本文仅用于学习记录&#xff0c;不存在任何商业用途&#xff0c;如侵删 文章目录Node.js 入门教程13 在 Node.js 中从命令行接收输入13 在 Node.js 中从命令行接收输入 如何使 Node.js CLI 程序具有交互性&#xff1f; 从…

卡塔尔世界杯--程序员的诗和远方

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; 卡塔尔世界杯–程序员的诗和远方 2022年卡塔尔世界杯&#xff08;FIFA World Cup Qatar 2022&#xff09;第二十二届世界杯足球赛&#xff0c;历史上首次在卡塔尔和中…

【学习笔记44】JavaScript的事件传播

JavaScript的事件传播一、事件传播1、事件传播的说明2、阻止事件传播二、目标冒泡捕获1、冒泡2、捕获三、事件委托1、事件委托的说明2、为什么要用事件委托四、阻止默认事件1、方法一2、方法二一、事件传播 在触发子元素的事件时, 会将行为传播给父级的同类型事件触发了子元素的…

机器学习1综述

文章目录一、综述学习环境&#xff1a;二、机器学习方法的分类1、监督学习&#xff1b;2、非监督学习&#xff1b;3、半监督学习&#xff1b;4、增强学习&#xff1b;三、机器学习方法分类2、批量学习&#xff08;离线学习&#xff09;Batch Learing&#xff1b;3、参数学习&am…

二叉树的递归问题

目录 一、相同的树 二、另一棵树的子树 三、翻转二叉树 四、平衡二叉树 五、对称二叉树 一、相同的树 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是…

【毕业设计-课程设计】-超声波测距

资源链接在文章最后,订阅查看获取全部内容及资料,如需可私信提供硬件。 目 录 1 绪论 2 1.1 项目研究背景及意义 2 2 总体设计方案及论证 2 2.1 总体方案设计 2 3 硬件实现及单元电路设计 3 3.1 主控制模块 3 3.2 电源设计 4 3.3 超声波测试模块 4 3.3.1 超声波的特性 5 3.3…

【Python】顺序、条件、循环语句

文章目录一. 顺序语句二. 条件语句1. 什么是条件语句2. 缩进和代码块3. 空语句 pass4. 练习三. 循环语句1. while 循环2. for 循环一. 顺序语句 默认情况下&#xff0c;Python 的代码执行顺序&#xff0c;是从上到下依次执行的&#xff1a; 执行结果一定为 “123”&#xff0…

【工具门户】Backstage配置使用PostgreSQL(三)

先决条件 If the database is not hosted on the same server as the Backstage app, the PostgreSQL port needs to be accessible (the default is 5432 or 5433) PostgreSQL数据库默认端口为5432或5433,如果数据库与Backstage应用不在同一台机器上,需开放PostgreSQL端口…

MyBatis框架一二级缓存含代码演示

目录 1.什么是缓存? 2. Mybatis的一级缓存 2.1实验一: 2.2实验二: 3.Mybatis的二级缓存 3.1 二级缓存需要配置才可以使用: 3.2 实验开始&#xff01;&#xff01; 4.总结 1.什么是缓存? 缓存就是数据交换的缓冲区&#xff08;称作Cache&#xff09;&#xff0c;当某一…

学习Python中turtle模块的基本用法(2:基本绘图函数测试)

个人感觉turtle模块中的绘图函数是按人手拿着画笔一笔一画地画图的思路定义的&#xff0c;这与C#中的GDI函数、html5中canvas的绘图函数及Tkinter中Canvas的绘图函数的定义思路存在差异&#xff0c;但也能完成后面绝大部分的绘图功能&#xff08;目前看到的turtle文章及帮助文档…

安装OpenGL

提示错误信息&#xff1a; (base) C:\Users\Tina\PycharmProjects\FunnyToys-main>conda install opengl Collecting package metadata (current_repodata.json): done Solving environment: failed with initial frozen solve. Retrying with flexible solve. Collecting…