当前位置: 首页 > news >正文

INT202 Complexity of Algroithms 算法的复杂度 Pt.2 Search Algorithm 搜索算法

文章目录

  • 1.树的数据结构
    • 1.1 有序数据(Ordered Data)
      • 1.1.1 有序字典(Ordered Dictonary)
        • 1.1.1.1 排序表(Sorted Tables)
    • 1.2 二分查找(Binary Search)
      • 1.2.1 二分查找的时间复杂度
    • 1.3 二叉搜索树(Binary Search Tree)
      • 1.3.1 搜索过程
        • 1.3.1.1 时间复杂度
      • 1.3.2 相关操作
        • 1.3.2.1 插入操作
        • 1.3.2.2 删除操作
      • 1.3.3 复杂度
      • 1.3.4 多路搜索树(Multi-Way Search Tree)
        • 1.3.4.1 多路搜索树的中序遍历(Inoder-Traversal)
        • 1.3.4.2 搜索过程
        • 1.3.4.3 (2, 4) 树/(2, 4) trees
          • 1.3.4.3.1 相关操作
            • 1.3.4.3.1.1 插入操作
            • 1.3.4.3.1.2 分裂操作
            • 1.3.4.3.1.3 删除操作
      • 1.3.5 AVL树(AVL树)
        • 1.3.5.1 相关操作
          • 1.3.5.1.1 插入操作
          • 1.3.5.1.2 删除操作

1.树的数据结构

我们用链式结构(Linked structure)可以实现树形数据结构。在这种结构中,树的每个节点 v 由一个对象表示,该对象包含以下信息:
1.存储在节点 v 中的元素。
2.指向其父节点和子节点的引用(或位置)。

对于有根树来说,特别是当每个节点最多有 t t t个子节点,并且树的深度是有限的情况,我们可以使用数组来存储有根树。
里以二叉树(Binary Tree)为例来说明。在二叉树中,每个节点最多有两个子节点。以下是如何将二叉树存储在数组 A A A中的步骤:
1.根节点:树的根节点存储在数组的第一个位置,即 A [ 0 ] A[0] A[0]
2.子节点的存储:根节点的两个子节点(如果有的话)分别存储在数组的第2和第3个位置,即 A [ 1 ] A[1] A[1] A [ 2 ] A[2] A[2]
3.子节点的子节点:接下来, A [ 1 ] A[1] A[1]的两个子节点(如果有的话)存储在数组的第4和第5个位置,即 A [ 3 ] A[3] A[3] A [ 4 ] A[4] A[4] A [ 2 ] A[2] A[2]的两个子节点存储在数组的第6和第7个位置,即 A [ 5 ] A[5] A[5] A [ 6 ] A[6] A[6]
4.继续递归:这个过程会递归地继续下去,每个节点的子节点都按照从左到右的顺序存储在数组中。

因此对于数组中位置为 A [ i ] A[i] A[i]的节点,其两个子节点分别存储在 A [ 2 i + 1 ] A[2i+1] A[2i+1] A [ 2 i + 2 ] A[2i+2] A[2i+2]的位置。
对于数组中位置为 A [ i ] A[i] A[i]的节点(除了根节点),其父节点存储在位置 A [ ⌊ ( i − 1 ) / 2 ⌋ ] A[⌊(i−1)/2⌋] A[⌊(i1)/2⌋]。这里使用了向下取整的除法 ⌊ ( i − 1 ) / 2 ⌋ ⌊(i−1)/2⌋ ⌊(i1)/2(floor)来计算父节点的位置。
这种方法可以推广到每个节点最多有 t t t个子节点的情况。在这种情况下,对于数组中位置为 A [ i ] A[i] A[i]的节点,其子节点存储在 A [ i × t + 1 ] A[i×t+1] A[i×t+1] A [ i × t + t ] A[i×t+t] A[i×t+t]的位置。同样,其父节点存储在位置 A [ ⌊ ( i − 1 ) / t ⌋ ] A[⌊(i−1)/t⌋] A[⌊(i1)/t⌋]

1.1 有序数据(Ordered Data)

我们经常希望以某种方式(通常是数值顺序或字母顺序,但也可能采用其他排序方式)存储有序的数据。
这里研究了几种存储这类有序数据的方法,并在添加更多数据或删除数据时维护其顺序。
我们将讨论将数据高效排序到这样的有序集合中的有效方法以及存储数据时维护其有序性的方法,对于后者,与在已经接收到汇总数据作为输入后对数据进行排序是不同的。

1.1.1 有序字典(Ordered Dictonary)

有序字典(Ordered Dictionary)是一种数据结构,它不仅像常规字典(Dictionary)那样存储键值对(Key-Value Pairs),而且还维护这些元素的顺序关系。这里的“顺序”是指根据键(Key)来排序元素。
对于有序字典的几个常规操作如下。
1.findElement(k):
这个操作用于查找键为 k 的元素在有序字典中的位置。它返回该元素的位置索引。
2.insertElement(k, e):
这个操作用于在有序字典中插入一个新的键值对。键为 k,值为 e。插入操作会将新的键值对放置在正确的位置,以保持字典中元素的顺序。
3.removeElement(k):
这个操作用于从有序字典中移除键为 k 的元素。移除操作会从字典中删除指定的键值对,并可能需要调整其他元素的位置以维持顺序。

因此有序字典可以用于实现以下功能:
维护一个按时间顺序排列的事件日志。
实现一个能够按顺序访问元素的缓存系统。
提供一个能够按字典顺序列出所有键的配置管理器。

1.1.1.1 排序表(Sorted Tables)

如果一个字典 D D D是有序的,我们可以按照键的非递减顺序将其项存储在一个向量 S S S中。这通常假设我们不会向字典中添加更多的项。
以这种方式将字典存储在向量中,比使用链表存储 S S S可以更快地进行搜索。
查找表是一种通过排序序列实现的字典,由两部分组成。
1.我们将字典的项存储在一个基于数组的序列中,并按键排序。
2.我们使用一个外部比较器来比较键。

1.2 二分查找(Binary Search)

我们现在介绍一种查找方法——二分查找。
它通过元素在数组中的排名(rank)来访问数组 S 中的元素。这里的“排名”指的是元素在有序数组中的位置。
排名为 i i i的元素的键不小于排名从 1 1 1 i − 1 i−1 i1的所有元素的键,并且不大于排名从 i + 1 i+1 i+1 n n n的所有元素的键。这意味着数组是按照键的非递减顺序排序的。
二分查找是在数组 S S S中的一个递减范围内进行的。这意味着随着搜索的进行,搜索的范围会逐渐缩小。
具体过程如下:
在数组 S S S中查找特定元素 k k k时,当前考虑的 S S S的范围被定义为一对排名:low 和 high。
1.初始化:low=1 和 high=n,其中 n n n是数组 S S S的大小。
key(i) 表示排名为 i 的元素的键。elem(i) 表示排名为 i 的元素。
2.计算中间位置:为了减小搜索范围,我们将目标键 k k k与当前搜索范围的中间位置的键进行比较。计算中间位置 mid = ⌊(low + high) / 2⌋。这里使用了向下取整除法来确保得到一个整数索引。
3.三种可能情况:

  1. 如果 k k k等于 key(mid),这意味着我们已经找到了目标元素,搜索成功完成。
  2. 如果 k k k小于 key(mid),这意味着目标元素(如果存在)必须位于中间位置的左侧。因此,我们将搜索范围的上限 high 更新为 mid−1,然后继续搜索。
  3. 如果 k k k大于 key(mid),这意味着目标元素(如果存在)必须位于中间位置的右侧。因此,我们将搜索范围的下限 low 更新为 mid+1,然后继续搜索。
  4. 继续搜索:在每种情况下,我们都更新了搜索范围,使得下一次迭代时搜索范围更小。这个过程会重复进行,直到找到目标元素或者搜索范围为空(即 low 大于 high),后者表示目标元素不在数组中,因为我们已经遍历完了所有数组中的元素。

因此其伪代码如下。

BINARYSEARCH(S, k, low, high)// Input is an ordered array of elements.// Output: Element with key k if it exists, otherwise an error.if low > highreturn NO_SUCH_KEYelsemid ← ⌊(low + high) / 2⌋if k = key(mid)return elem(mid)elseif k < key(mid)return BINARYSEARCH(S, k, low, mid - 1)elsereturn BINARYSEARCH(S, k, mid + 1, high)

下图给出了BINARYSEARCH(S, 19, 1, size(S))的过程。
在这里插入图片描述

1.2.1 二分查找的时间复杂度

在每次递归调用中,执行的是固定数量的操作。因此每次递归调用的运行时间是常数级别的。二分查找的运行时间与执行的递归调用次数成正比。
数组 A A A中仍需搜索的候选项数量为 high − low + 1 。
此外每次递归调用至少将剩余候选项的数量减少一半。

(mid - 1) - low + 1 = ⌊(low + high) / 2⌋ - low ≤ (high - low + 1) / 2 或
high - (mid + 1) + 1 = high - ⌊(low + high) / 2⌋ ≤ (high - low + 1) / 2
这里可能有人想问为什么等式后面不用 + 1 ,因为我们比较了mid元素,所以不需要计算这个元素。这里high − low + 1就是我们理解的输入 n,因此 (high - low + 1) / 2就是 n / 2。

这里我们将使用递归法来描述其运行时间。
最初,候选项的数量是 n n n(数组的长度)。
在第一次调用 BinarySearch 后,候选项的数量最多减少到 n / 2 n/2 n/2
在第二次调用后,候选项的数量最多减少到 n / 4 n/4 n/4
以此类推,每次递归调用后,候选项的数量都会减半。
所以我们可以定义一个函数 T ( n ) T(n) T(n)来表示这个方法的运行时间。
在这里插入图片描述
其中 b b b是一个常数,表示更新 low 和 high 以及其他开销所需的时间。
这个递归方程表明,每次递归调用后剩余的候选项数量最多为 n / 2 i n/2^i n/2i
执行的最大递归调用次数是满足以下条件的最小整数 m m m n / 2 m < 1 n/2^m <1 n/2m<1
换句话说, m > l o g 2 n m>log_2^n m>log2n
因此,我们有 m = ⌈ l o g 2 n ⌉ + 1 m=⌈log_2^n⌉+1 m=log2n+1,其中 ⌈ x ⌉ ⌈x⌉ x 表示对 x x x 向上取整。
这意味着 BinarySearch(A, k, 1, n) 的运行时间是 O ( l o g n ) O(logn) O(logn)
这里补充说明一下为什么在时间复杂度中一般不需要底,因为我们可以使用换底公式修改这里的底,所以我们一般都忽略这里的底。
因此如果现在是三分之一,同样它的运行时间是 O ( l o g n ) O(logn) O(logn)

1.3 二叉搜索树(Binary Search Tree)

我们前面学习了二分查找,它通过元素在数组中的排名(rank)来访问数组 S 中的元素,又学习了有序的数据结构,因此我们可以将它们结合起来,就是下面我们要介绍的二叉搜索树。
二叉搜索树是一种基于树形数据结构的存储结构。
在二叉搜索树中,每个内部节点(Internal Node)存储一个元素 e e e(或更一般地,一个键 k k k,该键定义了排序规则,以及一些元素 e e e)。
给定一个存储元素 e e e的节点 v v v,二叉搜索树具有以下性质:
1.所有在 v v v的左子树(Left Subtree)中的元素都小于或等于 e e e
2.所有在 v v v的右子树(Right Subtree)中的元素都大于或等于 e e e
如下图所示。
在这里插入图片描述
根节点存储元素 3,左子节点存储元素 1,右子节点存储元素 4。左子树中的元素小于根节点的元素,右子树中的元素大于根节点的元素。

1.3.1 搜索过程

得益于这一个特性,我们可以按照以下方式进行搜索。
为了搜索一个键 k k k,我们从根节点开始沿着一条向下的路径进行搜索。
访问的下一个节点取决于当前节点的键与键 k k k的比较结果。
如果我们到达一个叶子节点(即没有子节点的节点),而该键没有找到,则返回 NO_SUCH_KEY 表示没有找到该键。
伪代码如下:

Algorithm findElement(k, v)if T.isExternal(v)return NO_SUCH_KEYif k < key(v)return findElement(k, T.leftChild(v))else if k = key(v)return element(v)else { k > key(v) }return findElement(k, T.rightChild(v))

例如findElement(4),搜索过程如下图所示。
在这里插入图片描述
从根节点6开始,因为4小于6,所以搜索路径向左移动到节点2,因为2小于4,所以搜索路径向右移动到节点4,找到了键为4的节点。

1.3.1.1 时间复杂度

对于这里的时间复杂度我们发现一个有趣的现象,如下图所示,这里有两棵树,但是它们储存的是同样的数据。
在这里插入图片描述
在运行findElement(4)时,上面只需要比较两次就能找到键为4的节点,但是下面需要三次。
首先这里的复杂度与前面的候选项的数量减少的速度有关。
因此时间复杂度与树的高度 h h h相关。每一层的搜索时间是 O ( 1 ) O(1) O(1),即常数时间。
总时间复杂度是 O ( h ) O(h) O(h),即与树的高度成正比。
在这里插入图片描述

在最坏情况下,二叉搜索树可能退化成链表结构,此时树的高度为 n n n(节点数)。
因此,在最坏情况下,搜索的时间复杂度为 O ( n ) O(n) O(n)
下图展示了一个退化的二叉搜索树,其中每个节点只有一个子节点,导致高度为 n n n。前面的例子也是这样的情况。
在这里插入图片描述
在平衡二叉搜索树中,树的高度为 logn(对数级别)。
因此,在平衡情况下,搜索的时间复杂度为 O(logn)。
下图展示了一个平衡的二叉搜索树,其中树的高度远小于节点数 n。
在这里插入图片描述
因此我们可以发现如果我们的二叉搜索树不是平衡情况的话,那我们就不会有高效的搜索性能,就会出现像前面例子那样的情况。
为了保持高效的搜索性能,通常需要通过自平衡机制(如AVL树、红黑树等)来确保二叉搜索树的高度尽可能低,后面会进行介绍。
总结如下:
在二叉搜索树中,搜索操作的时间复杂度取决于树的高度 h h h
在最坏情况下(树退化成链表),时间复杂度为 O ( n ) O(n) O(n)
在平衡情况下(树高度为 l o g n logn logn),时间复杂度为 O ( l o g n ) O(logn) O(logn)

1.3.2 相关操作

我们现在回到数据结构的角度,我们来看看二叉搜索树怎么实现相关操作。

1.3.2.1 插入操作
  1. 首先,insertItem(k, e) 是我们的插入操作。执行这个操作后我们需要在树中搜索键 k。这里的 k 是要插入的元素的键,而 e 是与键 k 关联的值。
  2. 假设键 k 还不在树中,我们继续搜索直到找到一个叶子节点 w。这个叶子节点 w 是搜索键 k 的过程中遇到的最后一个节点,由于 k 不在树中,所以这个节点没有键 k。
  3. 我们在叶子节点 w 处插入键 k。由于 w 之前是一个叶子节点,它没有子节点。
    下图展示了insert 5的例子。
    在这里插入图片描述
    在这里插入图片描述
    注意插入后,键 5 被插入到适当的位置,并创建了两个新的子节点(用红色方框表示)。
    前面是假设键 k 不存在,如果 k 存在呢,如果键 k 已经在二叉搜索树(BST)中存在,通常我们不会在该位置再次插入相同的键,因此忽略这次操作。
    说到这个操作的时间复杂度,这里主要还是寻找插入位置,因此它取决于树的高度。在平均情况下,既如果树是平衡的,插入操作的时间复杂度是 O ( l o g n ) O(log n) O(logn),其中 n 是树中节点的数量。然而,在最坏情况下,如果树退化成链表,插入操作的时间复杂度会退化到 O ( n ) O(n) O(n)
1.3.2.2 删除操作

下列的删除算法旨在尽量减少树结构的重组和失衡。该算法会返回经过删除操作后得到的新的二叉搜索树。

  1. 首先,找到需要被删除的节点,我们称之为 remNode。
  2. 如果 remNode 是一个叶子节点(即没有子节点),则可以直接删除它,通过返回 null 来表示该位置为空。
  3. 如果 remNode 只有一个子节点,可以通过返回其子节点来删除 remNode。这样,子节点将取代 remNode 的位置。
  4. 如果 remNode 有两个子节点,需要找到 remNode 的中序后继(inorder successor)。中序后继是指在中序遍历中出现在 remNode 之后的节点,它具有比 remNode 大的最小键值。将中序后继的键复制到 remNode 中,然后删除中序后继节点。(由于中序后继是 remNode 右子树中键值最小的节点,它最多只有一个左子节点。因此,删除中序后继节点的操作可以简化为步骤2或步骤3。)
    最后,返回更新后的 remNode。

叶子节点的情况,示例如下。
在这里插入图片描述
remNode 有一个子节点的情况。
在这里插入图片描述
remNode 有两个子节点的情况。
在这里插入图片描述

1.3.3 复杂度

当使用二叉搜索树实现一个包含 n n n个元素的字典时,所需的空间是 O ( n ) O(n) O(n)。这意味着存储所有节点(每个节点包含一个元素)所需的空间与元素数量成正比。
查找元素的方法 findElement 的时间复杂度是 O ( h ) O(h) O(h),其中 h h h是树的高度。其中最坏情况是 O ( n ) O(n) O(n),最好情况是 O ( l o g n ) O(logn) O(logn)

因此对于不同的树可能会有不同的效率,尤其是在树不平衡的时候。
在最坏的情况下,二叉搜索树的高度 h h h可能与节点数 n n n一样大。这种情况通常发生在所谓的“退化树(degenerate tree)”中,即每个父节点只有一个关联的子节点,这与链表类似。
二叉搜索的主要优势是其对数时间复杂度的搜索效率(即 O ( l o g n ) O(logn) O(logn)的搜索时间)。然而,如果二叉搜索树没有保持平衡,这种优势可能会丧失,甚至在最坏的情况下,搜索时间可能退化到线性时间 O ( n ) O(n) O(n)

1.3.4 多路搜索树(Multi-Way Search Tree)

多路搜索树是一种有序树,它扩展了二叉搜索树的概念,允许每个内部节点有多个子节点。
每个内部节点至少有两个子节点,并存储 d − 1 d−1 d1个键-元素对 ( k i k_i ki, o i o_i oi),其中 d d d是子节点的数量。
为什么是d-1?因为它是搜索树,它依旧满足左子树中的元素都小于或等于该节点,右子树中的元素都大于或等于该节点。
因此对于 d d d个子节点,就有 d − 1 d−1 d1个空,因此就需要 d − 1 d−1 d1个键-元素对。

因此其的性质如下:
对于一个有子节点 v 1 , v 2 , … , v d ​ v_1,v_2,…,v_d​ v1,v2,,vd的节点,存储键 k 1 , k 2 , … , k d ​ − 1 k_1,k_2,…,k_{d​-1} k1,k2,,kd1

  1. v 1 v_1 v1的子树中的键都小于 k 1 ​ k_1​ k1
  2. v i v_i vi的子树中的键都小于 k i − 1 ​ k_{i-1​} ki1​ k i k_i ki之间 ( i = 2 , . . . , d − 1 ) (i=2,...,d-1) (i=2,...,d1)
  3. v d v_d vd的子树中的键都大于 k d − 1 ​ k_{d-1​} kd1​

其叶子节点不存储任何元素,仅作为占位符。
所以多路搜索树的例子如下。
在这里插入图片描述
我们可以发现30大于27小于32所以位于中间的子树下。

1.3.4.1 多路搜索树的中序遍历(Inoder-Traversal)

我们可以将二叉搜索树的中序遍历概念扩展到多路搜索树。
在访问节点 v v v的项 ( k i k_i ki, o i ​ o_i​ oi) 时,我们是在递归遍历以 v i v_i vi v i + 1 v_{i+1} vi+1​为根的子树之间进行的。这里 v i v_i vi v i + 1 v_{i+1} vi+1是节点 v v v的相邻子节点。
其实就是从左到右按顺序。
前面的中序遍历顺序如下。
在这里插入图片描述

1.3.4.2 搜索过程

与二叉搜索树的搜索过程类似。
考虑一个内部节点,它有 d d d个子节点 v 1 , v 2 , . . . , v d v_1,v_2,...,v_d v1,v2,...,vd d − 1 d−1 d1个键 k 1 , k 2 , . . . , k d − 1 k_1,k_2,...,k_{d-1} k1,k2,...,kd1
四种可能情况:

  1. 如果 k = k i k=k_i k=ki(其中 i = 1 , … , d − 1 i=1,…,d−1 i=1,,d1),则搜索成功终止,因为找到了键 k k k
  2. 如果 k < k 1 k<k_1 k<k1,则在子节点 v 1 v_1 v1中继续搜索,因为所有在 v 1 v_1 v1子树中的键都小于 k 1 k_1 k1
  3. 如果 k i − 1 < k < k i k_{i−1}<k<k_i ki1<k<ki(其中 i = 2 , … , d − 1 i=2,…,d−1 i=2,,d1),则在子节点 v i ​ v_i​ vi中继续搜索,因为所有在 v i ​ v_i​ vi子树中的键都在 k i − 1 k_{i−1} ki1 k i k_i ki之间。
  4. 如果 k > k d − 1 k>k_{d−1} k>kd1,则在子节点 v d v_d vd中继续搜索,因为所有在 v d v_d vd子树中的键都大于 k d − 1 k_{d−1} kd1
    下图展示了搜索30的例子。
    在这里插入图片描述
    由于30大于24,所以选择27 32的节点。又因为30在27和32之间,因此选择30节点,30等于30,所以搜索成功终止。
1.3.4.3 (2, 4) 树/(2, 4) trees

(2,4) 树是一种多路搜索树,具有以下性质:

  1. 节点大小属性(Node-Size Property):每个内部节点最多有四个子节点。
  2. 深度属性(Depth Property):所有外部节点(叶子节点)具有相同的深度。换句话说所有叶子节点都在同一层。

根据子节点的数量,(2,4) 树中的内部节点可以是:

  1. 2-node(2-节点):有恰好两个子节点和一个键。
  2. 3-node(3-节点):有恰好三个子节点和两个键。
  3. 4-node(4-节点):有恰好四个子节点和三个键。

在这里插入图片描述
如图所示,这是一个(2,4)树。其中根节点是一个4-节点,后续省略。

这里我们介绍一个定理:一个存储 n n n个元素的 (2,4) 树的高度是 O ( l o g n ) O(logn) O(logn)
证明如下:
h h h是存储 n n n个元素的 (2,4) 树的高度。
在深度 i = 0 , … , h − 1 i=0,…,h−1 i=0,,h1的每一层至少有 2 i 2^i 2i个元素,而在深度 h h h的层上没有元素。
因此,树中元素的总数 n n n至少是 1 + 2 + 4 + … + 2 h − 1 1+2+4+…+2^{h−1} 1+2+4++2h1
n ≥ 1 + 2 + 4 + … + 2 h − 1 = 2 h − 1 n≥1+2+4+…+2^{h−1}=2^h-1 n1+2+4++2h1=2h1
因此 h ≤ l o g ( n + 1 ) h≤log(n+1) hlog(n+1)

根据这个定理我们可以得到在存储 n n n个元素的 (2,4) 树中进行搜索的时间复杂度是 O ( l o g n ) O(logn) O(logn)
在这里插入图片描述

1.3.4.3.1 相关操作
1.3.4.3.1.1 插入操作

当我们向 (2,4) 树中插入一个新的元素 (k,o) 时,我们首先搜索键 k k k,找到应该插入的位置,这通常是某个叶子节点。
在插入过程中,我们保持 (2,4) 树的深度属性,即所有叶子节点都位于相同的深度。
但是在插入新元素后,可能会引发一个溢出问题(overflow)。具体来说,如果插入新元素导致某个节点 v v v(原本是4-节点)现在有5个子节点,那么这个节点就变成了一个5-节点,这违反了 (2,4) 树的节点大小属性(每个内部节点最多有4个子节点)。
如下图所示。
在这里插入图片描述
这里如果我们尝试插入键 17,可能会在某个节点 v v v处引发溢出。假设节点 v v v原本是一个4-节点,包含键 15、18、24,并且有4个子节点。当我们尝试插入键 17 时,节点 v v v将变成一个5-节点,包含键 15、17、18、24,并且有5个子节点。

1.3.4.3.1.2 分裂操作

因此我们介绍分裂操作。
当一个5-节点 v v v出现时,我们通过分裂操作来处理这个溢出。
v 1 , v 2 , … , v 5 v_1 ,v_2 ,…,v_5 v1,v2,,v5是节点 v v v的子节点, k 1 ​ , k 2 ​ , … , k 4 k_1​ ,k_2​ ,…,k_4 k1,k2,,k4是节点 v v v的键。

  1. 替换节点:节点 v v v被两个新节点 v ′ v ′ v v ′′ v ′′ v′′替换。其中 v ′ v ′ v是一个3-节点,键 k 1 , k 2 k_1 ,k_2 k1,k2和子节点 v 1 ​ , v 2 ​ , v 3 v_1​ ,v_2​ ,v_3 v1,v2,v3 v ′′ v ′′ v′′是一个2-节点,包含键 k 4 ​ k 4​ k4​ 和子节点 v 4 , v 5 v 4 ,v 5 v4,v5
  2. 插入键到父节点:键 k 3 ​ k_3​ k3被插入到父节点 u u u中。这可能会导致父节点 u u u也变成5-节点,从而可能需要进一步的分裂操作。
  3. 可能的进一步分裂:如果父节点 u u u也变成5-节点,这种溢出可能会继续向上传播,直到树的根节点。在某些情况下,可能需要创建一个新的根节点。
    刚刚的问题就如下图所示。
    在这里插入图片描述
    下图给出了一个更为复杂的例子,其中分裂后父节点继续溢出,一直到树的根节点。
    在这里插入图片描述
    这里添加17后因为溢出所以分裂,但是根节点溢出了。
    在这里插入图片描述
    所以这里继续分裂,从而创建了一个新的根节点。
    通过这种方式,(2,4) 树始终保持平衡。虽然分裂操作可能需要递归地进行,但它确保了树的高度不会超过 O ( l o g n ) O(logn) O(logn),其中 n n n是树中元素的数量。这保证了树的操作(如搜索、插入和删除)都能在对数时间内完成。
1.3.4.3.1.3 删除操作

在 (2,4) 树中,删除操作通常被简化为在具有叶子子节点的节点中进行删除。这是因为在这种情况下,删除操作不会影响到树的结构平衡。
如果要删除的元素不在具有叶子子节点的节点中,我们可以用它的中序后继(或等效地,用它的中序前驱)替换要删除的元素,然后删除这个后继(或前驱)元素。
如下图所示,删除键24。
在这里插入图片描述
以删除键24为例,我们首先找到它的中序后继,这里是27。我们将24替换为27,然后删除27。

同样的,删除操作可能会遇到下溢问题(Underflow)。即当从节点 v v v中删除一个元素后,该节点变成了一个 1-节点,即它只有一个子节点且没有键。这违反了 (2,4) 树的节点大小属性,因为内部节点至少应有两个子节点。
为了处理节点 v v v的下溢,我们需要考虑两种情况。
情况1: v v v的相邻兄弟节点(siblings)是2-节点。
我们将节点 v v v与其相邻的兄弟节点 w w w合并,并将父节点 u u u中的一个键移动到合并后的节点 v ′ v′ v中。这种操作称为融合(Fusion),它通过合并两个节点来解决下溢问题,并保持树的平衡。
如下图所示。
在这里插入图片描述

同样它可能会传播到父节点 u u u中,解决方式也与前面分裂类似。
情况2: v v v的相邻兄弟节点(siblings)是3-节点或4-节点。
我们将兄弟节点 w w w的一个子节点移动到节点 v v v,再将父节点 u u u中的一个键移动到节点 v v v,将兄弟节点 w w w中的一个键移动到父节点 u u u
转移操作完成后,不会发生下溢,所有涉及的节点都至少有两个子节点,符合 (2,4) 树的节点大小属性。
如下图所示。
在这里插入图片描述

1.3.5 AVL树(AVL树)

AVL树是一种自平衡的二叉搜索树。
它具有以下性质。
高度平衡属性(Height-Balance Property):对于AVL树中的任何节点 n n n,其左子树和右子树的高度之差最多为1。这个属性确保了树的平衡性,防止树退化成链状结构。

下图展示了一个AVL树。
在这里插入图片描述
因此其也具有以下定理。
定理:存储 n n n个键的AVL树的高度是 O ( l o g n ) O(logn) O(logn)
证明如下:
n ( h ) n(h) n(h)表示高度为 h h h的AVL树中内部节点的最小数量。
如图所示。
在这里插入图片描述
我们可以发现 n ( 1 ) = 1 n(1)=1 n(1)=1 n ( 2 ) = 2 n(2)=2 n(2)=2
对于 n > 2 n>2 n>2,高度为 h h h的AVL树包含根节点、一个高度为 h − 1 h−1 h1的子树和另一个高度为 h − 2 h−2 h2的子树。
所以我们得到 n ( h ) = 1 + n ( h − 1 ) + n ( h − 2 ) n(h)=1+n(h-1)+n(h-2) n(h)=1+n(h1)+n(h2)
已知 n ( h − 1 ) > n ( h − 2 ) n(h-1)>n(h-2) n(h1)>n(h2),则 n ( h ) > 2 n ( h − 2 ) n(h)>2n(h−2) n(h)>2n(h2)
我们可以根据递推关系得到 n ( h − 2 ) > 2 n ( h − 4 ) n(h−2)>2n(h−4) n(h2)>2n(h4) n ( h − 4 ) > 2 n ( h − 6 ) n(h−4)>2n(h−6) n(h4)>2n(h6),最终得到 n ( h ) > 2 i n ( h − 2 i ) n(h)>2^in(h−2i) n(h)>2in(h2i)
解决基础情况得到: n ( h ) > 2 h / 2 − 1 n(h)>2^{h/2−1} n(h)>2h/21 。根据 h h h是偶数还是奇数, h − 2 i = 1 h-2i=1 h2i=1 h − 2 i = 2 h-2i=2 h2i=2,即 i i i ⌈ h / 2 ⌉ − 1 ⌈h/2⌉−1 h/21
两边取对数我们便得到 h < 2 l o g n ( h ) + 2 h<2logn(h)+2 h<2logn(h)+2(左右互换并且常数搬运到另一边)。
因此,AVL树的高度是 O ( l o g n ) O(logn) O(logn)

根据这个定理我们可以保证了以下两点。
1.由于AVL树的高度是 O ( l o g n ) O(logn) O(logn),因此在AVL树中进行搜索操作的时间复杂度也是 O ( l o g n ) O(logn) O(logn)
2.在AVL树中进行插入和删除操作需要更仔细的实现,以维护高度平衡属性,我们将使用旋转操作(rotation)来调整树的平衡属性。

1.3.5.1 相关操作

在进行相关操作的时候要注意保持AVL树的高度平衡属性。

1.3.5.1.1 插入操作

在AVL树中进行插入操作,开始时与在普通的二叉搜索树(BST)中插入类似,即在树中添加一个新的外部节点(叶子节点)。
当树的高度平衡属性被破坏时,从新创建的节点开始,沿着树向上遍历,直到找到第一个不平衡的节点 x x x,其祖父节点 z z z是导致不平衡的节点。
由于 z z z是因为其子节点 y y y的子树中的插入操作而变得不平衡,为了重新平衡以 z z z为根的子树,需要进行结构重组。
我们根据中序遍历的顺序,将节点 x x x y y y z z z重命名为 a a a b b b c c c
然后将节点 b b b替换节点 z z z b b b的子节点现在是 a a a c c c。而 a a a c c c的子节点分别是原来 x x x y y y z z z的四个子树。
因此这里会有两种情况。
1.单旋转(Single Rotation):
这是一个左旋转操作,围绕节点 a a a进行。在这种情况下,节点 z z z被替换为节点 b b b,并且节点 b b b的子节点现在是 a a a c c c,其中 a a a c c c的子节点分别是原来 x x x y y y z z z的四个子树。
2.双旋转(Double Rotation):
这是一个先右旋转后左旋转的操作,围绕节点 c c c进行。首先对 c c c进行右旋转,然后对 a a a进行左旋转。这种操作用于更复杂的不平衡情况,其中直接的单旋转无法恢复平衡。
图例如下。
在这里插入图片描述
其实是按照前面说的将其第一层保持为 b b b,下一层是 a a a c c c,剩余节点位置不动。 a a a b b b c c c是因为它们的大小关系确定的,所以根据左子树小于该节点,右子树大于该节点,这里三者的大小的中间者就是 b b b,也就当作这里最后结果的根节点。

因此重组的算法如下。
输入:二叉搜索树 T T T中的一个节点 x x x,该节点既有父节点 y y y又有祖父节点 z z z
输出:经过涉及节点 x x x y y y z z z的三节点重组(对应于单次或双次旋转)后的树 T T T
步骤如下:

  1. 将节点 x x x y y y z z z按中序遍历的顺序排列为 ( a a a, b b b, c c c)。
    并且将 x x x y y y z z z的四个子树(不包括 x x x y y y z z z本身)按中序遍历的顺序排列为 ( T 0 ​ T_0​ T0 , T 1 ​ T_1​ T1, T 2 ​ T_2​ T2 , T 3 ​ T_3​ T3)。
  2. 用一个新的子树替换以 z 为根的子树,新子树的根是 b b b
  3. a a a设置为 b b b的左子节点,并将 T 0 T_0 T0 T 1 T_1 T1分别设置为 a a a的左子树和右子树。
  4. c c c设置为 b b b的右子节点,并将 T 2 T_2 T2 T 3 T_3 T3分别设置为 c c c的左子树和右子树。

下图给出了一个例子。
在这里插入图片描述
添加54导致了不平衡,所以 62 是 x x x,50 是 y y y,78 是 z z z。根据它们的大小关系我们重组后得出下面的树。

1.3.5.1.2 删除操作

同样删除操作与普通的二叉搜索树(BST)类似,但也可能会遇到AVL树的高度平衡属性被破坏。
如下图所示。
在这里插入图片描述
这里删除 32 导致了不平衡。
我们使用前面的重组算法将树重新平衡。这里选择 x x x的不同会有不同的结果,但是得出来的AVL树重新恢复平衡就行。
下图展示了不同的情况。
在这里插入图片描述
第一种情况是以 78 为 x x x,第二种情况是以 50 为 x x x,两种结果都是有效的AVL树重新平衡操作后的结果。

所有关于 n n n个元素的AVL树的操作的复杂度都是 O ( l o g n ) O(logn) O(logn)
在这里插入图片描述
总结如下:
1.单次重新结构化操作的时间复杂度是 O ( 1 ) O(1) O(1)
2.搜索操作的时间复杂度是 O ( l o g n ) O(logn) O(logn),因为AVL树的高度是 O ( l o g n ) O(logn) O(logn)
3.插入操作的时间复杂度是 O ( l o g n ) O(logn) O(logn),其中初始搜索(找到插入位置)的时间复杂度是 O ( l o g n ) O(logn) O(logn),在树中重新结构化以维持高度平衡的时间复杂度也是 O ( l o g n ) O(logn) O(logn)
4.删除操作的时间复杂度同样是 O ( l o g n ) O(logn) O(logn),其中初始搜索(找到要删除的元素)的时间复杂度是 O ( l o g n ) O(logn) O(logn),在树中重新结构化以维持高度平衡的时间复杂度也是 O ( l o g n ) O(logn) O(logn)

相关文章:

INT202 Complexity of Algroithms 算法的复杂度 Pt.2 Search Algorithm 搜索算法

文章目录 1.树的数据结构1.1 有序数据(Ordered Data)1.1.1 有序字典&#xff08;Ordered Dictonary&#xff09;1.1.1.1 排序表&#xff08;Sorted Tables&#xff09; 1.2 二分查找&#xff08;Binary Search&#xff09;1.2.1 二分查找的时间复杂度 1.3 二叉搜索树&#xff0…...

springmvc中使用interceptor拦截

HandlerInterceptor 是Spring MVC中用于在请求处理之前、之后以及完成之后执行逻辑的接口。它与Servlet的Filter类似&#xff0c;但更加灵活&#xff0c;因为它可以访问Spring的上下文和模型数据。HandlerInterceptor 常用于日志记录、权限验证、性能监控等场景。 ### **1. 创…...

C++编译汇编八股总结

汇编的四个阶段&#xff1f; 预编译&#xff08;预处理&#xff09;&#xff1a; 预编译是源代码在编译之前进行的一些处理&#xff0c;主要包括宏定义展开、条件编译指令处理和头文件展开等。 编译&#xff1a; 编译器根据源代码的语法和语义规则&#xff0c;将源代码进行词法…...

基于ArcGIS和ETOPO-2022 DEM数据分层绘制全球海陆分布

第〇部分 前言 一幅带有地理空间参考、且包含海陆分布的DEM图像在研究区的绘制中非常常见&#xff0c;本文将实现以下图像的绘制 关键步骤&#xff1a; &#xff08;1&#xff09;NOAA-NCEI官方下载最新的ETOPO-2022 DEM数据 &#xff08;2&#xff09;在ArcGIS&#xff08;…...

【LangChain入门 4 Prompts组件】提示词追加示例 FewShotPromptTemplate和示例选择器ExampleSelector

文章目录 一、提示词追加示例 FewShotPromptTemplate二、使用示例选择器 example_selector三、关键类介绍3.1 PromptTemplate3.2 FewShotPromptTemplate3.3 SemanticSimilarityExampleSelector 提示词中包含交互样本的作用是为了帮助模型更好地理解用户的意图&#xff0c;从而更…...

Android Compose 切换按钮深度剖析:从源码到实践(六)

Android Compose 切换按钮深度剖析&#xff1a;从源码到实践 一、引言 在现代 Android 应用开发中&#xff0c;用户交互体验至关重要。切换按钮&#xff08;Toggle Button&#xff09;作为一种常见的交互组件&#xff0c;允许用户在两种状态之间进行切换&#xff0c;例如开 /…...

挖矿病毒应急响应处置手册

挖矿病毒应急响应处置手册 文章目录 挖矿病毒应急响应处置手册0x00 概述0x01 了解基本情况1.1 如何发现1.1.1 异常外联1.1.2 主机异常1.2 事件的时间节点1.3 临时处置情况1.4 网络拓扑情况0x02 判断是否属于挖矿2.1 属于挖矿2.1.1 根据告警和流量信息初步判断挖矿类型2.1.2 win…...

VSCode - 查看 PDF 文件

VSCode 原生并不支持 查看 PDF 文件&#xff0c;需要额外安装插件。 这里我使用 vscode-pdf&#xff0c;效果还不错&#xff0c;有需要的可以搜索安装。 效果&#xff1a; 2025-03-18&#xff08;二&#xff09;...

vue3:八、登录界面实现-忘记密码

该文章实现登录界面的忘记密码功能&#xff0c;点击忘记密码文本&#xff0c;打开dialog对话框 一、页面效果 加入忘记密码&#xff0c;在记住密码的同一行中&#xff0c;实现flex-between 二、对话框实现 1、新建组件页面 2、引入dialog组件到组件页面 参考路径 Dialog 对…...

Python Django入门(创建其他网页)

在本章中&#xff0c;你将学习如何使用 Django&#xff08;http://djangoproject.com/ &#xff09;来开发一个名为“学习笔记”&#xff08;Learning Log&#xff09;的项目&#xff0c;这是一个在线日志系统&#xff0c;让你能够记录所学习的有关特定主题的知识。 我们将为这…...

Windows安装MySQL5.7.26教程图解

Windows安装MySQL5.7.26教程图解 零、准备工作 下载MySQL软件包 ①、官网下载:程序员 常用 软件汇总 - 超人那个超~ - 博客园 ②、百度云下载:链接:百度网盘 请输入提取码 提取码:chao 一、彻底删除MySQL 从电脑里卸载旧的MYSQL数据库服务时,首先先在WINDOWS服务里…...

FreGS: 3D Gaussian Splatting with Progressive Frequency Regularization论文学习记录

3. 提出的方法 我们提出了FreGS&#xff0c;一种具有渐进频率正则化的新型3D高斯溅射方法&#xff0c;它是首个从频率角度缓解3D高斯溅射过度重建问题的方法。图2展示了FreGS的概览。第3.1节简要介绍了原始的3D高斯溅射方法&#xff08;3D-GS&#xff09;&#xff0c;包括高斯…...

汽车行业敏捷开发实践:基于Atlassian工具链的全流程解决方案(Jira、Confluence、Jira Service Management等)

直播回顾 在数字化浪潮席卷全球的今天&#xff0c;各行各业都在积极寻求转型与突破&#xff0c;汽车行业也不例外。 近日&#xff0c;在“Atlassian助力企业破局&#xff1a;数字化协作与全球市场拓展”的线上直播活动中&#xff0c;龙智资深顾问张晓乐深入探讨了汽车行业数字…...

遇到一个奇怪问题,页面请求不到后端

背景 页面有两个请求,第一个接口获取令牌,第二个接口根据令牌去获取数据, 突然发现获取数据接口校验令牌的时候一直报错 而且报错的时候服务器没有获取令牌请求 而且发现偶尔是正常的,正常的发现服务器ip和异常的不一样,同事定位可能是域名解析问题 解决 最后定位是腾讯cdn解…...

【C++】:C++11详解 —— 线程库

目录 线程库&#xff08;thread&#xff09; 线程对象的构造函数 构造函数的用法示例 参数传递的关键细节 构造函数的异常行为 线程对象的使用 互斥量库&#xff08;mutex&#xff09; 互斥量类型 锁管理类&#xff08;RAII 封装&#xff09; 条件变量&#xff08;…...

招聘面试季--一文顿悟,Java中字节流和字符流的区别及使用场景上的差异

‌一、核心区别‌ ‌特性‌‌字节流‌‌字符流‌‌数据单位‌以字节&#xff08;8-bit&#xff09;为单位处理数据&#xff08;如0xA1&#xff09;以字符&#xff08;16-bit Unicode&#xff09;为单位处理数据&#xff08;如A, 你&#xff09;‌基类‌InputStream / OutputSt…...

在 ARM 嵌入式 Linux 下使用 C/C++ 实现 MQTT

在 ARM 嵌入式 Linux 下使用 C/C 实现 MQTT 通信是一个常见的需求&#xff0c;尤其是在资源受限的环境中。以下是一个详细的教程&#xff0c;使用 Eclipse Paho C Client 库来实现 MQTT 客户端。 1. 安装 Eclipse Paho C Client 库 Eclipse Paho C Client 是一个轻量级的 MQTT…...

C++20 中 `constexpr` 的强大扩展:算法、工具与复数库的变革

文章目录 一、constexpr 在 <algorithm> 中的应用1. 编译时排序2. 编译时查找 二、constexpr 在 <utility> 中的应用1. 编译时交换2. 编译时条件交换 三、constexpr 在 <complex> 中的应用1. 编译时复数运算 四、总结 C20 对 constexpr 的增强是其最引人注目…...

C++ 介绍STL底层一些数据结构

c 标准模板库中&#xff0c;set和map的底层实现通常基于红黑树&#xff0c;然们都是平衡二叉搜索树(Balanceed Binary Serach Tree&#xff09;的一种,这种结构保证了 插入&#xff0c;删除&#xff0c;查找的时间复杂度为O(log n)比普通二叉搜索树更高效。 set set<T>…...

算法2--两数相加

题目描述 解题思路 题目说的很详细了&#xff0c;也就是把每个数倒序写成链表进行输入&#xff0c;然后让你计算两个倒序数组的和&#xff0c;要保证跟预期的结果一样。 首先应该考虑的是两个数组的长度问题&#xff0c;对于链表的每一位进行加法运算&#xff0c;如果两个列表…...

Docker搭建Testlink教程

1.拉取镜像 打开终端输入命令&#xff1a; #拉取mariadb镜像 docker pull bitnami/mariadb #拉取testlink镜像 docker pull bitnami/testlink-archived 执行结果&#xff1a; 2.运行容器 打开终端输入命令&#xff1a; #创建容器网络 docker network create testlink #查…...

安卓7.0以上App抓包

安卓7.0以上App抓包 导出BurpSuite证书 设置本机IP的8080端口监听 证书转换 将这个der证书下载到kali上&#xff0c;并使用以下命令进行证书转换 openssl x509 -inform der -in cacert.der -out burp.pem openssl x509 -inform PEM -subject_hash_old -in burp.pem转换成功…...

CCBCISCN复盘

AWDP – ccfrum 自己搭了一下环境, 复现一下这道题目, 之前比赛的时候完全没想到这个漏洞要怎么打, 修也不知道要怎么修, 就仅仅是对用户名的账号和密码进行了一下过滤, 完全没起到作用, 唉, 实在太菜 如果想要尝试复现的话可以尝试拉取这个镜像, 我打完之后就直接把这个容器给…...

【C++】八大常见的设计模式的实现与实践指南

目录 创建型模式 单例模式工厂方法模式抽象工厂模式 结构型模式 适配器模式装饰者模式代理模式 行为型模式 观察者模式策略模式命令模式 高级主题 现代C特性影响模式性能对比典型应用案例 设计模式分类 一、创建型模式 1. 单例模式&#xff08;Singleton&#xff09; 现代…...

OpenEMMA: 基于多模态大语言模型的端到端开源自动驾驶框架

OpenEMMA: 基于多模态大语言模型的端到端开源自动驾驶框架 创新点 OpenEMMA 将前置摄像头图像和车辆历史文本状态作为输入。驾驶任务被构建为视觉问答&#xff08;VQA&#xff09;问题&#xff0c;利用思维链推理来指导模型生成关键物体的详细描述、行为洞察和元驾驶决策。这…...

kali,NTFS,用户管理,文件共享,本地安全策略,计算机基础

kali更新源 vim /etc/apt/sources.list 优质源 中科大Kali镜像源​deb http://mirrors.ustc.edu.cn/kali kali-rolling main non-free contribdeb-src http://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib​阿里云Kali镜像源​deb http://mirrors.aliyun.com…...

零基础上手Python数据分析 (7):Python 面向对象编程初步

写在前面 回顾一下,我们已经学习了 Python 的基本语法、数据类型、常用数据结构和文件操作、异常处理等。 到目前为止,我们主要采用的是 面向过程 (Procedural Programming) 的编程方式,即按照步骤一步一步地编写代码,解决问题。 这种方式对于简单的任务已经足够,但当程序…...

基于深度学习的皮肤癌智能检测与语音提示系统【python源码+Pyqt5界面+数据集+训练代码】

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…...

脚本一键式启动Nginx、Mysql、Redis

此脚本包含拉取镜像、数据卷挂载、容器启动三大部分&#xff0c;可一键式安装三大环境 新建一个depoy.sh文件在服务器上&#xff0c;然后复制以下内容。 给脚本文件添加执行权限 chmod x depoy.sh # 文件的当前目录下 如果需要修改数据库MYSQL密码和Reids密码 MYSQL_ROO…...

蓝桥杯备赛-DFS-有奖问答

问题描述 小蓝正在参与一个现场问答的节目。活动中一共有 3030 道题目, 每题只有答对和答错两种情况, 每答对一题得 1010 分&#xff0c;答错一题分数归零。 小蓝可以在任意时刻结束答题并获得目前分数对应的奖项&#xff0c;之后不能再答任何题目。最高奖项需要 100100 分, …...

[AI速读]CHISEL vs. SystemVerilog:用RISC-V核心对比两种硬件设计语言

在硬件设计领域,选择合适的语言对开发效率、维护成本和最终性能都至关重要。最近,一项研究对比了两种硬件描述语言——CHISEL(基于Scala的嵌入式语言)和传统的SystemVerilog,它们分别实现了同一款RISC-V核心(SweRV-EL2)。以下是关键发现和结论。 为什么选择CHISEL? CHI…...

PHP PSR(PHP Standards Recommendations)介绍

PHP PSR&#xff08;PHP Standards Recommendations&#xff09;是 PHP 社区制定的一系列标准化规范&#xff0c;旨在统一 PHP 代码的编写方式、接口设计和开发实践&#xff0c;以提高代码的可读性、可维护性和互操作性。以下是核心 PSR 标准的解读和具体使用方法&#xff1a; …...

字节跳动实习生主导开发强化学习算法,助力大语言模型性能突破

目录 禹棋赢的背景与成就 主要成就 DAPO算法的技术细节 算法优势 禹棋赢的研究历程 关键时间节点 字节跳动的“Top Seed人才计划” 计划特点 小编总结 在大模型时代&#xff0c;经验不再是唯一的衡量标准&#xff0c;好奇心、执行力和对新技术的敏锐洞察力成为推动技术…...

Java并发编程面试题:锁(17题)

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…...

各类神经网络学习:(四)RNN 循环神经网络(下集),pytorch 版的 RNN 代码编写

上一篇下一篇RNN&#xff08;中集&#xff09;待编写 代码详解 pytorch 官网主要有两个可调用的模块&#xff0c;分别是 nn.RNNCell 和 nn.RNN &#xff0c;下面会进行详细讲解。 RNN 的同步多对多、多对一、一对多等等结构都是由这两个模块实现的&#xff0c;只需要将对输入…...

【python】OpenCV—Hand Landmarks Detection

文章目录 1、功能描述2、代码实现3、效果展示4、完整代码5、涉及到的库函数6、参考 更多有趣的代码示例&#xff0c;可参考【Programming】 1、功能描述 基于 opencv-python 和 mediapipe 实现手部关键点的检测&#xff08;无法检测出手&#xff0c;不过可以根据关键点的信息外…...

C++和标准库速成(十)——类型别名、类型定义、类型推断和标准库简介

目录 1. 类型别名2. 类型定义(不建议)3. 类型推断3.1 auto3.1.1 auto&3.1.2 auto*3.1.3 拷贝列表初始化和直接列表初始化 3.2 decltype 4. 标准库简介参考 1. 类型别名 类型别名为现有的类型声明提供新名称。可以将类型别名视为用于为现有类型声明引入同义词而无须创建新类…...

Java JMX 未授权访问漏洞分析与修复指南

#作者&#xff1a;张桐瑞 文章目录 一、漏洞背景二、漏洞描述三、漏洞影响四、修复方案1. 禁用远程JMX访问&#xff1a;2. 配置JMX访问权限&#xff1a; 一、漏洞背景 Java管理扩展&#xff08;Java Management Extensions&#xff0c;简称JMX&#xff09;是Java平台的管理和…...

挂谷问题与挂谷猜想:从平面转针到高维拓扑

挂谷问题与挂谷猜想&#xff1a;从平面转针到高维拓扑 目录 挂谷问题的起源数学定义与基本性质研究进展挂谷集合与挂谷猜想王虹与Joshua Zahl的突破意义与影响 挂谷问题的起源 1917年&#xff0c;日本数学家挂谷宗一(かけや そういち Soichi Kakeya&#xff0c;1886-1947)提…...

区块链 智能合约安全 | 整型溢出漏洞

目录&#xff1a; 核心概念 溢出类型 上溢 原理 案例 下溢 原理 案例 练习 漏洞修复 使用 SafeMath 库&#xff08;旧版本&#xff09; 升级 Solidity 版本&#xff08;≥0.8.0&#xff09; 地址&#xff1a;zkanzz 整型溢出漏洞&#xff08;Integer Overflow/Underflow Vulne…...

C# HTTP 文件上传、下载服务器

程序需要管理员权限&#xff0c;vs需要管理员打开 首次运行需要执行以下命令注册URL&#xff08;管理员命令行&#xff09; netsh advfirewall firewall add rule name"FileShare" dirin actionallow protocolTCP localport8000 ipconfig | findstr "IPv4&quo…...

IDEA导入jar包后提示无法解析jar包中的类,比如无法解析符号 ‘log4j‘

IDEA导入jar包后提示无法解析jar包中的类 问题描述解决方法 问题描述 IDEA导入jar包的Maven坐标后&#xff0c;使用jar中的类比如log4j&#xff0c;仍然提示比如无法解析符号 log4j。 解决方法 在添加了依赖和配置文件后&#xff0c;确保刷新你的IDE项目和任何缓存&#xff…...

C++前缀和

个人主页&#xff1a;[PingdiGuo_guo] 收录专栏&#xff1a;[C干货专栏] 大家好&#xff0c;今天我们来了解一下C的一个重要概念&#xff1a;前缀和 目录 1.什么是前缀和 2.前缀和的用法 1.前缀和的定义 2.预处理前缀和数组 3.查询区间和 4.数组中某个区间的和是否为特定…...

kafka压缩

最近有幸公司参与kafka消息压缩&#xff0c;背景是日志消息量比较大。kafka版本2.4.1 一、确认压缩算法 根据场景不同选择不同。如果是带宽敏感患者推荐高压缩比的zstd&#xff0c;如果是cpu敏感患者推荐lz4 lz4和zstd底层都使用的是lz77算法&#xff0c;具体实现逻辑不同&am…...

C 语 言 --- 扫 雷 游 戏(初 阶 版)

C 语 言 --- 扫 雷 游 戏 初 阶 版 代 码 全 貌 与 功 能 介 绍扫雷游戏的功能说明游 戏 效 果 展 示游 戏 代 码 详 解game.htest.cgame.c 总结 &#x1f4bb;作 者 简 介&#xff1a;曾 与 你 一 样 迷 茫&#xff0c;现 以 经 验 助 你 入 门 C 语 言 &#x1f4a1;个 人 主…...

黑鲨外设2025春季新品发布会:全球首款“冷暖双控”鼠标亮相!

据可靠消息称&#xff0c;电竞外设领域的创新引领者——黑鲨外设&#xff0c;正式官宣将于2025年3月28日17:00召开主题为“究极体验&#xff0c;竞在其中”春季新品发布会。据悉&#xff0c;此次新品发布会将于黑鲨游戏外设和黑鲨游戏手机官方平台同步直播&#xff0c;&#xf…...

SpringBoot-MVC配置类与 Controller 的扫描

文章目录 前言一、自动配置类位置二、自动配置类解析2.1 WebMvcAutoConfiguration2.1.1 EnableWebMvcConfiguration 2.2 DispatcherServletAutoConfiguration 三、RequestMapping 的扫描过程3.1 RequestMappingHandlerMapping#afterPropertiesSet3.2 RequestMappingHandlerMapp…...

Nexus L2 L3基本配置

接口基本配置 N7K上所有端口默认处于shutdown状态; N5K上所有端口默认处于no shutdown状态(所有端口都是switchport) 默认所有接口都是三层route模式, 只有当线卡不支持三层的时候, 接口才会处于二层switchport模式 show run all | in “system default” 创建SVI口需要提前打…...

asp.net 4.5在医院自助系统中使用DeepSeek帮助医生分析患者报告

环境&#xff1a; asp.net 4.5Visual Studio 2015本地已经部署deepseek-r1:1.5b 涉及技术 ASP.NET MVC框架用于构建Web应用程序。使用HttpWebRequest和HttpWebResponse进行HTTP请求和响应处理。JSON序列化和反序列化用于构造和解析数据。SSE&#xff08;服务器发送事件&#xf…...

LCCI ESG 中英联合认证国际分析师适合的岗位

LCCI ESG中英联合认证国际分析师领域热门岗位大揭秘&#xff01;&#x1f30d; 大家好&#xff01;今天我们来探讨LCCI ESG中英联合认证国际分析师领域的热门岗位&#xff0c;看看是否有适合你的选择。 1️⃣ LCCI ESG中英联合认证国际分析师报告专员&#xff1a;主要负责编制…...