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

常用查找算法整理(顺序查找、二分查找、哈希查找、二叉排序树查找、平衡二叉树查找、红黑树查找、B树和B+树查找、分块查找)

常用的查找算法:

  1. 顺序查找:最简单的查找算法,适用于无序或数据量小的情况,逐个元素比较查找目标值。
  2. 二分查找:要求数据有序,通过不断比较中间元素与目标值,将查找范围缩小一半,效率较高。
  3. 哈希查找:利用哈希函数将键值映射到特定存储位置,能在较短时间内实现查找,常用于数据量较大且对查找速度要求高的场景。
  4. 二叉排序树查找:基于二叉排序树数据结构,左子树节点值小于根节点值,右子树节点值大于根节点值,通过比较和递归进行查找。
  5. 平衡二叉树查找:如AVL树、红黑树等,在二叉排序树基础上保持平衡,提高查找效率,适用于数据动态变化频繁的场景。
  6. 红黑树查找:红黑树是一种自平衡的二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是红色或黑色。经常用于编程语言的标准库、操作系统的内存管理、数据库索引。
  7. B树和B+树查找:常用于文件系统和数据库系统的索引结构,能高效处理大量数据的查找和范围查询。
  8. 分块查找:将数据分块,块内无序但块间有序,先确定块再在块内查找,性能介于顺序查找和二分查找之间。
  9. 斐波那契查找和插值查找在特定场景下有其独特的优势,但整体而言,它们不如顺序查找、二分查找等方法常用,

顺序查找

基本概念
顺序查找,也称为线性查找,是在一个数据集合中从第一个元素开始,逐个比较元素,直到找到目标元素或遍历完整个集合为止的查找方法。它适用于无序的线性表,也可用于有序的线性表。

算法实现

  • 一般实现步骤
    • 从数据集合的第一个元素开始。
    • 将当前元素与要查找的目标元素进行比较。
    • 如果当前元素等于目标元素,则查找成功,返回当前元素的位置。
    • 如果当前元素不等于目标元素,则继续比较下一个元素。
    • 重复上述步骤,直到找到目标元素或者遍历完整个数据集合。如果遍历完整个集合仍未找到目标元素,则查找失败,返回特定的标识(如-1)。
  • Python代码示例
def sequential_search(lst, target):for i, element in enumerate(lst):if element == target:return ireturn -1# 测试
lst = [10, 20, 30, 40, 50]
target = 30
print(sequential_search(lst, target))  

时间复杂度

  • 最好情况:目标元素在数据集合的第一个位置,只需比较1次,时间复杂度为 O ( 1 ) O(1) O(1)
  • 最坏情况:目标元素在数据集合的最后一个位置,或者数据集合中根本不存在目标元素,需要比较 n n n次,时间复杂度为 O ( n ) O(n) O(n),其中 n n n是数据集合中元素的个数。
  • 平均情况:假设目标元素在数据集合中的任何位置出现的概率相等,平均需要比较 n + 1 2 \frac{n + 1}{2} 2n+1次,时间复杂度也为 O ( n ) O(n) O(n)

优缺点

  • 优点
    • 算法简单,易于理解和实现。
    • 对数据集合的存储结构没有特殊要求,无论是顺序存储还是链式存储都可以使用。
    • 对于规模较小的数据集合,顺序查找的效率不会有明显的问题,并且在某些特定情况下(如数据集合动态变化频繁,无法进行其他更高效的预处理时),顺序查找是一种可行的选择。
  • 缺点
    • 对于规模较大的数据集合,查找效率较低,因为平均需要比较大量的元素。

适用场景

  • 数据量较小:当数据集合中的元素数量较少时,顺序查找的简单性和直接性使其成为一种合适的选择,因为在这种情况下,查找操作的成本相对较低,不会对性能产生太大影响。
  • 数据无序且动态变化:如果数据是无序的,并且经常需要进行插入和删除操作,导致数据难以进行有效的组织和索引,那么顺序查找可以在不进行额外维护操作的情况下进行查找。
  • 一次性查找:对于只进行偶尔的、一次性的查找操作,而不考虑对整体查找性能进行优化的情况,顺序查找可以快速实现查找功能,而无需为了提高查找效率而引入复杂的算法和数据结构。

二分查找

基本原理
二分查找也称为折半查找,其基本思想是:每次将待查找区间缩小为原来的一半,通过不断比较中间元素与目标元素的大小关系,来确定下一步的查找区间,直到找到目标元素或者确定目标元素不存在为止。

算法实现

  • 一般实现步骤
    • 首先,确定待查找区间的左右边界,左边界left初始化为0,右边界right初始化为数组长度减1。
    • 进入循环,只要left <= right,就继续查找。
    • 在循环中,计算中间位置mid,通常使用mid = left + (right - left) // 2的方式计算,以避免整数溢出。
    • 将中间位置的元素与目标元素进行比较。
      • 如果中间元素等于目标元素,则查找成功,返回中间位置mid
      • 如果中间元素大于目标元素,说明目标元素在中间元素的左侧,更新右边界right = mid - 1
      • 如果中间元素小于目标元素,说明目标元素在中间元素的右侧,更新左边界left = mid + 1
    • 如果循环结束后仍未找到目标元素,则查找失败,返回特定的标识(如-1)。

Python代码示例

def binary_search(lst, target):left, right = 0, len(lst) - 1while left <= right:mid = left + (right - left) // 2if lst[mid] == target:return midelif lst[mid] > target:right = mid - 1else:left = mid + 1return -1# 测试
lst = [10, 20, 30, 40, 50]
target = 30
print(binary_search(lst, target))  

时间复杂度

  • 最好情况:目标元素正好是中间元素,只需比较1次,时间复杂度为 O ( 1 ) O(1) O(1)
  • 最坏情况:需要不断地将区间缩小一半,直到只剩下一个元素,时间复杂度为 O ( l o g 2 n ) O(log_2n) O(log2n),其中 n n n是数组中元素的个数。
  • 平均情况:平均时间复杂度也为 O ( l o g 2 n ) O(log_2n) O(log2n)

优缺点

  • 优点
    • 查找速度快,相比于顺序查找,在大规模的有序数据中,二分查找的效率要高得多。
    • 时间复杂度低,对数级别的时间复杂度使得随着数据规模的增大,查找时间的增长相对缓慢。
  • 缺点
    • 要求数据必须是有序的,因此在数据插入或删除操作频繁的情况下,维护数据的有序性可能会带来额外的开销。
    • 对数据的存储结构有一定要求,通常需要使用数组这种支持随机访问的数据结构,对于链表等不支持随机访问的数据结构,二分查找的效率会大大降低。

适用场景

  • 数据量较大且有序:当处理大规模的有序数据集合时,二分查找能够充分发挥其高效的优势,快速定位目标元素。
  • 静态数据:对于相对稳定、不经常进行插入和删除操作的数据,二分查找是一种理想的查找算法,因为不需要频繁地调整数据的顺序来维护有序性。
  • 需要频繁查找:在需要多次进行查找操作的场景下,二分查找的高效性能够显著提高整体的性能,减少查找的总时间。

哈希查找

基本原理

  • 哈希函数:哈希查找的核心是哈希函数,它是一个将数据元素的键值key映射为一个固定大小的整数(通常称为哈希值或哈希码)的函数,用hash(key)表示。理想情况下,哈希函数应该具有良好的均匀性,即对于不同的键值,能够均匀地分布在哈希表的各个位置上,以减少冲突的发生。
  • 哈希表:哈希表是用于存储数据元素的数据结构,它的大小通常是固定的。通过哈希函数计算出的哈希值作为索引,将数据元素存储在哈希表的相应位置上。当需要查找某个数据元素时,再次使用哈希函数计算其键值的哈希值,然后直接访问哈希表中对应的位置,就可以快速找到该元素。

解决冲突的方法

  • 开放定址法:当有新的数据要插入到哈希表中,发现目标位置已经被占用时,就按照一定的规则寻找下一个空闲的位置来插入。常见的探测序列有线性探测、二次探测等。线性探测就是依次探测下一个位置,即(hash(key)+i) % m,其中i = 1,2,3,...m为哈希表的大小。二次探测则是按照(hash(key)+i^2) % m的方式探测。
  • 链地址法:将所有哈希值相同的数据元素存储在一个链表中。当插入一个新元素时,计算其哈希值,然后将其插入到对应的链表头部或尾部。查找时,先计算哈希值找到对应的链表,然后在链表中顺序查找目标元素。

算法实现
以下是使用链地址法实现哈希查找的Python代码示例:

class HashTable:def __init__(self, size):self.size = sizeself.table = [[] for _ in range(size)]def hash_function(self, key):return key % self.sizedef insert(self, key, value):hash_value = self.hash_function(key)self.table[hash_value].append((key, value))def search(self, key):hash_value = self.hash_function(key)for k, v in self.table[hash_value]:if k == key:return vreturn None# 测试
hash_table = HashTable(10)
hash_table.insert(1, 'apple')
hash_table.insert(11, 'banana')
print(hash_table.search(11))  

时间复杂度

  • 理想情况:如果哈希函数设计得非常好,所有的数据元素都均匀地分布在哈希表中,那么每次查找只需要访问一次哈希表,时间复杂度为 O ( 1 ) O(1) O(1)
  • 最坏情况:当所有的数据元素都映射到同一个哈希值时,哈希表退化为一个链表,此时查找的时间复杂度为 O ( n ) O(n) O(n),其中n是数据元素的个数。
  • 平均情况:在合理的哈希函数和适当的负载因子(即哈希表中已存储元素的数量与哈希表大小的比值)下,哈希查找的平均时间复杂度为 O ( 1 ) O(1) O(1)或接近 O ( 1 ) O(1) O(1)

优缺点

  • 优点
    • 查找速度快:在理想情况下,哈希查找能够在常数时间内完成查找操作,这使得它在大规模数据处理中具有很高的效率。
    • 对数据无顺序要求:与二分查找等算法不同,哈希查找不需要数据是有序的,数据可以以任意顺序插入到哈希表中。
  • 缺点
    • 哈希函数设计困难:要设计一个完美的哈希函数是非常困难的,需要考虑数据的分布特点等因素,以避免冲突过多导致性能下降。
    • 空间开销较大:为了减少冲突,通常需要分配比实际数据量更大的哈希表空间,这可能会造成一定的空间浪费。

适用场景

  • 数据快速查找和插入:在需要频繁进行查找和插入操作的场景中,如数据库索引、缓存系统等,哈希查找能够提供高效的性能。
  • 数据唯一性判断:可以利用哈希查找来快速判断数据是否已经存在,例如在查重系统中,通过计算数据的哈希值并在哈希表中查找,可以快速确定数据是否重复。
  • 海量数据处理:在处理海量数据时,哈希查找可以将数据分散到不同的位置,便于进行分布式存储和处理。

树查找

二叉排序树

定义
二叉排序树(Binary Search Tree,BST),也称为二叉查找树、二叉搜索树,它是一种特殊的二叉树。对于树中的任意一个节点,都满足以下性质:

  • 若该节点的左子树不为空,则左子树上所有节点的值均小于该节点的值。
  • 若该节点的右子树不为空,则右子树上所有节点的值均大于该节点的值。
  • 该节点的左子树和右子树也分别是二叉排序树。

示例
以下是一个简单的二叉排序树示例,树中节点的值分别为 50、30、70、20、40、60、80:

        50/  \30    70/  \   / \20   40 60  80

在这个二叉排序树中,节点 50 的左子树(30、20、40)中的所有节点值都小于 50,右子树(70、60、80)中的所有节点值都大于 50。并且节点 30 的左子树(20)节点值小于 30,右子树(40)节点值大于 30,以此类推。

插入操作
插入新节点时,从根节点开始比较。如果新节点的值小于当前节点的值,则在左子树中继续查找插入位置;如果新节点的值大于当前节点的值,则在右子树中继续查找插入位置,直到找到一个空位置插入新节点。

例如,要在上述二叉排序树中插入节点 35,从根节点 50 开始,35 小于 50,进入左子树;35 大于 30,进入 30 的右子树;此时 30 的右子树节点 40 存在,35 小于 40,进入 40 的左子树,发现为空,将 35 插入该位置。

删除操作
删除操作相对复杂,需要分三种情况:

  • 要删除的节点是叶子节点:直接删除该节点即可。
  • 要删除的节点只有一个子节点:用该子节点替换要删除的节点。
  • 要删除的节点有两个子节点:找到该节点右子树中的最小节点(或左子树中的最大节点),用这个最小节点的值替换要删除节点的值,然后删除右子树中的最小节点。

二叉排序树搜索

搜索过程
在二叉排序树中搜索一个特定值的过程如下:

  1. 从根节点开始,将待搜索的值与根节点的值进行比较。
  2. 如果待搜索的值等于根节点的值,则搜索成功,返回该节点。
  3. 如果待搜索的值小于根节点的值,由于二叉排序树的性质,该值只可能在左子树中,因此在左子树中继续进行搜索。
  4. 如果待搜索的值大于根节点的值,该值只可能在右子树中,因此在右子树中继续进行搜索。
  5. 重复上述步骤,直到找到目标节点或者遇到空节点(表示搜索失败)。

代码实现(Python)

class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = rightdef searchBST(root, val):if root is None or root.val == val:return rootif val < root.val:return searchBST(root.left, val)return searchBST(root.right, val)# 构建示例二叉排序树
root = TreeNode(50)
root.left = TreeNode(30)
root.right = TreeNode(70)
root.left.left = TreeNode(20)
root.left.right = TreeNode(40)
root.right.left = TreeNode(60)
root.right.right = TreeNode(80)# 搜索值为 60 的节点
result = searchBST(root, 60)
if result:print(f"找到了值为 {result.val} 的节点")
else:print("未找到该节点")

复杂度分析

  • 时间复杂度:平均情况下,二叉排序树的高度为 O ( l o g n ) O(log n) O(logn)(其中 n n n 是树中节点的数量),搜索操作的时间复杂度为 O ( l o g n ) O(log n) O(logn)。但在最坏情况下,即二叉排序树退化为链表(例如插入的节点值是有序的),树的高度为 O ( n ) O(n) O(n),搜索操作的时间复杂度会变为 O ( n ) O(n) O(n)
  • 空间复杂度:主要取决于递归调用栈的深度,平均情况下为 O ( l o g n ) O(log n) O(logn),最坏情况下为 O ( n ) O(n) O(n)。如果使用迭代方式实现搜索,空间复杂度可以优化到 O ( 1 ) O(1) O(1)

应用场景
二叉排序树适用于需要频繁进行插入、删除和搜索操作的场景,并且数据集合的规模适中。例如,在一些小型的数据库系统中,用于快速查找特定记录;在编程语言的符号表管理中,用于存储和查找变量名及其相关信息等。

平衡二叉树

定义
平衡二叉树(AVL树)是一种自平衡的二叉搜索树。它满足二叉搜索树的基本性质:对于树中的任意节点,其左子树中所有节点的值都小于该节点的值,右子树中所有节点的值都大于该节点的值。同时,AVL树还额外要求每个节点的左右子树的高度差(平衡因子)的绝对值不超过 1。平衡因子定义为该节点的左子树高度减去右子树高度。

平衡的重要性
通过保持树的平衡,AVL树能够保证在进行插入、删除和查找操作时的时间复杂度始终为 O ( l o g n ) O(log n) O(logn),其中 n n n 是树中节点的数量。如果不进行平衡操作,二叉搜索树可能会退化为链表,此时这些操作的时间复杂度会变为 O ( n ) O(n) O(n)

平衡二叉树查找过程
平衡二叉树的查找过程与普通二叉搜索树的查找过程基本一致:

  1. 从根节点开始,将待查找的值 target 与当前节点的值进行比较。
  2. 如果 target 等于当前节点的值,则查找成功,返回该节点。
  3. 如果 target 小于当前节点的值,则在当前节点的左子树中继续查找。
  4. 如果 target 大于当前节点的值,则在当前节点的右子树中继续查找。
  5. 重复上述步骤,直到找到目标节点或者遇到空节点(表示查找失败)。

示例代码(Python 实现)

# 定义 AVL 树的节点类
class TreeNode:def __init__(self, key):self.key = keyself.left = Noneself.right = Noneself.height = 1  # 节点的高度,初始为 1# 定义 AVL 树类
class AVLTree:# 获取节点的高度def get_height(self, node):if not node:return 0return node.height# 获取节点的平衡因子def get_balance(self, node):if not node:return 0return self.get_height(node.left) - self.get_height(node.right)# 右旋操作def right_rotate(self, y):x = y.leftT2 = x.rightx.right = yy.left = T2y.height = max(self.get_height(y.left), self.get_height(y.right)) + 1x.height = max(self.get_height(x.left), self.get_height(x.right)) + 1return x# 左旋操作def left_rotate(self, x):y = x.rightT2 = y.lefty.left = xx.right = T2x.height = max(self.get_height(x.left), self.get_height(x.right)) + 1y.height = max(self.get_height(y.left), self.get_height(y.right)) + 1return y# 插入节点def insert(self, root, key):if not root:return TreeNode(key)elif key < root.key:root.left = self.insert(root.left, key)else:root.right = self.insert(root.right, key)root.height = 1 + max(self.get_height(root.left), self.get_height(root.right))balance = self.get_balance(root)# 左左情况if balance > 1 and key < root.left.key:return self.right_rotate(root)# 右右情况if balance < -1 and key > root.right.key:return self.left_rotate(root)# 左右情况if balance > 1 and key > root.left.key:root.left = self.left_rotate(root.left)return self.right_rotate(root)# 右左情况if balance < -1 and key < root.right.key:root.right = self.right_rotate(root.right)return self.left_rotate(root)return root# 查找节点def search(self, root, key):if root is None or root.key == key:return rootif key < root.key:return self.search(root.left, key)return self.search(root.right, key)# 测试代码
if __name__ == "__main__":avl_tree = AVLTree()root = Nonekeys = [9, 5, 10, 0, 6, 11, -1, 1, 2]for key in keys:root = avl_tree.insert(root, key)target = 6result = avl_tree.search(root, target)if result:print(f"找到了节点: {result.key}")else:print(f"未找到节点: {target}")

代码解释

  1. TreeNode 类:定义了 AVL 树的节点结构,包含节点的值 key、左右子节点 leftright 以及节点的高度 height
  2. AVLTree 类
    • get_height 方法:用于获取节点的高度。
    • get_balance 方法:用于计算节点的平衡因子。
    • right_rotateleft_rotate 方法:分别实现了右旋和左旋操作,用于调整树的平衡。
    • insert 方法:插入新节点,并在插入后检查树的平衡情况,必要时进行旋转操作以保持树的平衡。
    • search 方法:实现了查找功能,通过递归的方式在树中查找目标节点。
  3. 测试部分:创建一个 AVL 树,插入一系列节点,然后查找目标节点 6,并输出查找结果。

通过这种方式,我们可以在平衡二叉树中高效地进行查找操作。

复杂度分析

  • 时间复杂度
    • 平均情况:在平衡二叉树(AVL树)中,由于它始终保持着左右子树高度差不超过 1 的平衡特性,树的高度 h h h 始终维持在 O ( log ⁡ n ) O(\log n) O(logn) 级别,其中 n n n 是树中节点的数量。而查找操作需要从根节点开始,沿着一条路径向下搜索,经过的节点数最多为树的高度。因此,平均情况下平衡二叉树查找操作的时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)。例如,当树中有 1024 个节点时,树的高度大约为 log ⁡ 2 1024 = 10 \log_2{1024}=10 log21024=10,查找操作最多只需比较 10 次。
    • 最坏情况:由于平衡二叉树严格保证了树的平衡性,即使在最坏情况下,树的高度依然是 O ( log ⁡ n ) O(\log n) O(logn),所以查找操作的时间复杂度仍然是 O ( log ⁡ n ) O(\log n) O(logn)。这与普通二叉搜索树不同,普通二叉搜索树在最坏情况下(退化为链表)查找时间复杂度会达到 O ( n ) O(n) O(n)

空间复杂度

  • 平衡二叉树查找操作的空间复杂度主要取决于递归调用栈的深度。在递归查找过程中,每次递归调用会在栈上分配一定的空间。由于查找路径的长度最长为树的高度,而树的高度为 O ( log ⁡ n ) O(\log n) O(logn),所以查找操作的空间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)。如果采用迭代方式实现查找,空间复杂度可以优化到 O ( 1 ) O(1) O(1),因为只需要使用常数级的额外空间来记录当前节点的位置。

适合使用的场景

  • 当数据集合需要频繁进行插入、删除和查找操作时,平衡二叉树是一个不错的选择。例如,在数据库的索引系统中,数据会不断地被插入和删除,同时也需要快速地查找特定的数据记录。平衡二叉树能够在保证插入和删除操作相对高效的同时,确保查找操作的时间复杂度始终为 O ( log ⁡ n ) O(\log n) O(logn)

  • 对于一些对查找速度要求极高的应用,如搜索引擎的缓存系统、实时数据处理系统等,平衡二叉树的高效查找性能能够满足这些场景的需求。以搜索引擎的缓存系统为例,需要快速地查找缓存中是否存在某个网页的信息,平衡二叉树可以在较短的时间内完成查找操作,提高系统的响应速度。

  • 如果只需要快速查找特定的数据,而不需要数据始终保持严格的有序性,平衡二叉树可以很好地满足需求。例如,在一些游戏的物品管理系统中,需要快速查找某个物品的属性信息,平衡二叉树可以高效地实现这一功能,而不需要像排序数组那样始终保持数据的严格有序。

红黑树

定义
红黑树是一种自平衡的二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是红色或黑色。通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。红黑树必须满足以下五个性质:

  1. 每个节点要么是红色,要么是黑色。
  2. 根节点是黑色。
  3. 每个叶子节点(NIL节点,空节点)是黑色。
  4. 如果一个节点是红色的,则它的两个子节点都是黑色的。
  5. 对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。

示例
以下是一个简单的红黑树示例(为方便表示,这里用括号内的 R 表示红色节点,B 表示黑色节点):

        (50B)/      \(30R)      (70R)/    \      /    \
(20B)  (40B) (60B)  (80B)

在这个红黑树中,根节点 50 是黑色,红色节点 30 和 70 的子节点都是黑色,并且从每个节点到其所有后代叶节点的简单路径上黑色节点的数量相同。

红黑树查找

查找过程
红黑树的查找过程与普通二叉搜索树的查找过程基本相同,具体步骤如下:

  1. 从根节点开始,将待查找的值与根节点的值进行比较。
  2. 如果待查找的值等于根节点的值,则查找成功,返回该节点。
  3. 如果待查找的值小于根节点的值,由于红黑树也是二叉搜索树,该值只可能在左子树中,因此在左子树中继续进行查找。
  4. 如果待查找的值大于根节点的值,该值只可能在右子树中,因此在右子树中继续进行查找。
  5. 重复上述步骤,直到找到目标节点或者遇到空节点(表示查找失败)。
代码实现(Python)
class TreeNode:def __init__(self, val=0, color='R', left=None, right=None):self.val = valself.color = colorself.left = leftself.right = rightdef searchRedBlackTree(root, val):if root is None or root.val == val:return rootif val < root.val:return searchRedBlackTree(root.left, val)return searchRedBlackTree(root.right, val)# 构建简单红黑树示例(这里只是简单构建,未完整体现插入调整逻辑)
root = TreeNode(50, 'B')
root.left = TreeNode(30, 'R')
root.right = TreeNode(70, 'R')
root.left.left = TreeNode(20, 'B')
root.left.right = TreeNode(40, 'B')
root.right.left = TreeNode(60, 'B')
root.right.right = TreeNode(80, 'B')# 查找值为 60 的节点
result = searchRedBlackTree(root, 60)
if result:print(f"找到了值为 {result.val} 的节点")
else:print("未找到该节点")

复杂度分析

  • 时间复杂度:由于红黑树是接近平衡的,其高度始终保持在 O ( l o g n ) O(log n) O(logn) 级别(其中 n n n 是树中节点的数量),因此查找操作的时间复杂度为 O ( l o g n ) O(log n) O(logn)。这意味着在大规模数据集合中,红黑树查找能够保持较高的效率。
  • 空间复杂度:主要取决于递归调用栈的深度,平均情况下为 O ( l o g n ) O(log n) O(logn),最坏情况下也为 O ( l o g n ) O(log n) O(logn)。如果使用迭代方式实现查找,空间复杂度可以优化到 O ( 1 ) O(1) O(1)

应用场景
红黑树在许多领域都有广泛的应用,以下是一些常见的场景:

  • 编程语言的标准库:如 Java 的 TreeMap 和 TreeSet、C++ 的 STL 中的 map 和 set 等,利用红黑树的特性实现了有序的键值对存储和快速查找功能。
  • 操作系统的内存管理:在操作系统中,红黑树可用于管理内存块的分配和释放,通过红黑树可以快速查找合适的内存块。
  • 数据库索引:在数据库系统中,红黑树可以作为索引结构,提高数据的查找效率,特别是对于需要频繁进行插入、删除和查找操作的场景。

B 树和B+数查询

B树

定义
B 树是一种自平衡的多路搜索树,它能够保持数据有序,并且允许在对数时间内进行插入、删除和查找操作。B 树的每个节点可以有多个子节点(通常大于 2),并且所有叶子节点都在同一层。一个 m 阶的 B 树需要满足以下条件:

  • 每个节点最多有 m 个子节点。
  • 除根节点和叶子节点外,每个节点至少有 ⌈ m / 2 ⌉ \lceil m/2 \rceil m/2 个子节点。
  • 根节点至少有 2 个子节点(除非它是叶子节点)。
  • 所有叶子节点都在同一层。
  • 每个节点中的键值按升序排列,并且键值的数量比子节点数量少 1。

示例
下面是一个 3 阶 B 树的简单示例:

          [30, 60]/    |    \[10, 20] [40, 50] [70, 80]

在这个 3 阶 B 树中,根节点包含两个键值 30 和 60,有三个子节点。每个子节点包含两个键值,并且键值是有序排列的。

B 树查找过程

  1. 从根节点开始,将待查找的值与根节点中的键值进行比较。
  2. 如果待查找的值等于根节点中的某个键值,则查找成功,返回该键值所在的位置。
  3. 如果待查找的值小于根节点中的某个键值,则进入该键值左侧的子节点继续查找。
  4. 如果待查找的值大于根节点中的所有键值,则进入最右侧的子节点继续查找。
  5. 重复上述步骤,在子节点中继续比较和查找,直到找到目标键值或者到达叶子节点。如果到达叶子节点仍未找到目标键值,则查找失败。

代码实现(Python 伪代码)

class BTreeNode:def __init__(self, is_leaf=False):self.is_leaf = is_leafself.keys = []self.child = []class BTree:def __init__(self, m):self.root = BTreeNode(is_leaf=True)self.m = mdef search(self, key):return self._search(self.root, key)def _search(self, node, key):i = 0while i < len(node.keys) and key > node.keys[i]:i += 1if i < len(node.keys) and key == node.keys[i]:return nodeif node.is_leaf:return Nonereturn self._search(node.child[i], key)# 示例使用
b_tree = BTree(3)
# 这里省略插入节点的代码
result = b_tree.search(40)
if result:print("找到了键值 40")
else:print("未找到键值 40")

复杂度分析

  • 时间复杂度:B 树的高度 h h h 与节点数量 n n n 的关系为 h = O ( log ⁡ ⌈ m / 2 ⌉ n ) h = O(\log_{\lceil m/2 \rceil} n) h=O(logm/2n),其中 m m m 是 B 树的阶数。因此,B 树查找操作的时间复杂度为 O ( log ⁡ ⌈ m / 2 ⌉ n ) O(\log_{\lceil m/2 \rceil} n) O(logm/2n),在实际应用中,由于 m m m 通常较大,查找效率较高。
  • 空间复杂度:主要取决于树中节点的数量,空间复杂度为 O ( n ) O(n) O(n)

应用场景
B 树常用于文件系统和数据库系统中,因为它可以有效减少磁盘 I/O 次数。在这些系统中,数据通常存储在磁盘上,磁盘 I/O 操作的时间开销较大,B 树的多路特性使得每次磁盘 I/O 可以读取或写入多个键值,从而提高了系统的性能。

B + 树

定义
B + 树是 B 树的一种变体,它与 B 树的主要区别在于:

  • 所有的数据都存储在叶子节点中,非叶子节点只存储索引信息。
  • 叶子节点之间通过指针相连,形成一个有序链表。

示例
以下是一个简单的 B + 树示例:

         [30, 60]/      \[10, 20]    [40, 50, 70, 80]|         /   |   \10       40   50   70 - 80 (叶子节点相连)

在这个 B + 树中,非叶子节点只包含索引键值 30 和 60,所有的数据(10、20、40、50、70、80)都存储在叶子节点中,并且叶子节点通过指针形成了有序链表。

B + 树查找过程

  • 精确查找:从根节点开始,按照与 B 树类似的方式,将待查找的值与非叶子节点中的键值进行比较,确定进入哪个子节点继续查找,直到到达叶子节点。在叶子节点中线性查找目标键值。
  • 范围查找:先通过精确查找找到范围的起始键值所在的叶子节点,然后利用叶子节点之间的指针顺序遍历,直到找到范围的结束键值或超出范围。

代码实现(Python 伪代码)

class BPlusTreeNode:def __init__(self, is_leaf=False):self.is_leaf = is_leafself.keys = []self.child = []self.next = Noneclass BPlusTree:def __init__(self, m):self.root = BPlusTreeNode(is_leaf=True)self.m = mdef search(self, key):node = self.rootwhile not node.is_leaf:i = 0while i < len(node.keys) and key > node.keys[i]:i += 1node = node.child[i]for k in node.keys:if k == key:return Truereturn False# 示例使用
b_plus_tree = BPlusTree(3)
# 这里省略插入节点的代码
result = b_plus_tree.search(50)
if result:print("找到了键值 50")
else:print("未找到键值 50")

复杂度分析

  • 时间复杂度:与 B 树类似,B + 树的查找操作时间复杂度也为 O ( log ⁡ ⌈ m / 2 ⌉ n ) O(\log_{\lceil m/2 \rceil} n) O(logm/2n)。对于范围查找,由于叶子节点之间有指针相连,时间复杂度为 O ( k + log ⁡ ⌈ m / 2 ⌉ n ) O(k + \log_{\lceil m/2 \rceil} n) O(k+logm/2n),其中 k k k 是范围内键值的数量。
  • 空间复杂度:同样为 O ( n ) O(n) O(n),主要用于存储树中的节点。

应用场景
B + 树在数据库系统中应用广泛,如 MySQL 的 InnoDB 存储引擎默认使用 B + 树作为索引结构。因为 B + 树的结构非常适合范围查询,并且由于所有数据都存储在叶子节点,使得数据库的插入、删除和查找操作更加高效和稳定。同时,叶子节点的有序链表结构也方便进行顺序访问,提高了数据的读取效率。

分块查找

也称为索引顺序查找,它是一种介于顺序查找和二分查找之间的查找方法。以下从基本思想、实现步骤、复杂度分析、优缺点和适用场景等方面详细介绍:

基本思想
分块查找将一个线性表分成若干个块,每个块内的元素可以是无序的,但块与块之间是有序的,即后一个块中所有元素的值都大于前一个块中所有元素的值。同时,还需要建立一个索引表,索引表中记录了每个块的最大关键字以及该块在原线性表中的起始位置,索引表是按关键字有序排列的。

实现步骤
分块查找主要分为两步:

  1. 确定待查元素所在的块:使用二分查找或顺序查找在索引表中查找,根据索引表中记录的块的最大关键字,确定待查元素可能存在于哪个块中。
  2. 在块内查找元素:在确定的块中使用顺序查找的方法查找待查元素。

示例及代码实现
假设我们有一个线性表 [22, 12, 13, 8, 9, 20, 33, 42, 44, 38, 24, 48],将其分成 3 个块,每个块有 4 个元素。
索引表为 [(22, 0), (44, 4), (48, 8)],其中每个元组的第一个元素是块的最大关键字,第二个元素是块在原线性表中的起始位置。

以下是 Python 代码实现:

def block_search(arr, index_table, target):# 第一步:在索引表中确定所在块block_index = -1for i in range(len(index_table)):if target <= index_table[i][0]:block_index = ibreakif block_index == -1:return -1# 第二步:在块内进行顺序查找start = index_table[block_index][1]if block_index == len(index_table) - 1:end = len(arr)else:end = index_table[block_index + 1][1]for i in range(start, end):if arr[i] == target:return ireturn -1# 线性表
arr = [22, 12, 13, 8, 9, 20, 33, 42, 44, 38, 24, 48]
# 索引表
index_table = [(22, 0), (44, 4), (48, 8)]
target = 24
result = block_search(arr, index_table, target)
if result != -1:print(f"元素 {target} 在数组中的索引是 {result}")
else:print(f"未找到元素 {target}")

复杂度分析

  • 时间复杂度:设线性表长度为 n n n,分成 b b b 个块,每个块有 s s s 个元素( n = b × s n = b \times s n=b×s)。在索引表中查找块的时间复杂度,如果使用顺序查找为 O ( b ) O(b) O(b),使用二分查找为 O ( log ⁡ 2 b ) O(\log_2b) O(log2b);在块内进行顺序查找的时间复杂度为 O ( s ) O(s) O(s)。所以,分块查找的平均时间复杂度为 O ( b + s ) O(b + s) O(b+s) O ( log ⁡ 2 b + s ) O(\log_2b + s) O(log2b+s)。当 s = n s = \sqrt{n} s=n 时,时间复杂度达到最优,为 O ( n ) O(\sqrt{n}) O(n )
  • 空间复杂度:主要是索引表占用的额外空间,为 O ( b ) O(b) O(b),其中 b b b 是块的数量。

优缺点

  • 优点
    • 对数据的要求相对较低,不需要像二分查找那样数据必须完全有序,只需要块间有序、块内可以无序,这在数据动态变化时比较方便维护。
    • 查找效率比顺序查找高,尤其是在数据量较大时。
  • 缺点:需要额外的存储空间来存储索引表,增加了空间开销。

适用场景

  • 数据量较大且分布不均匀:当数据量较大,且数据的分布不太规则时,分块查找可以通过合理分块,提高查找效率。
  • 数据动态变化:由于分块查找只要求块间有序,在数据插入、删除时,只需要调整相应块内的数据,对整体的块间顺序影响较小,相比于完全有序的数据结构更易于维护。

斐波那契查找和插值查找在特定场景下有其独特的优势,但整体而言,它们不如顺序查找、二分查找等方法常用,以下为你详细分析:

斐波那契查找和插值查找

斐波那契查找

  • 原理:斐波那契查找是在二分查找的基础上,利用斐波那契数列来确定分割点。它通过斐波那契数列的特性,将有序数组分成两部分进行查找,使得分割点更偏向于数据分布较密集的一侧。
  • 不常用的原因
    • 实现复杂度相对较高:需要对斐波那契数列有一定的了解和处理,要生成斐波那契数列并根据其规律进行查找区间的划分,相较于二分查找,实现起来更复杂,代码编写和理解成本更高。
    • 适用场景受限:它主要适用于对数据存储在磁盘等外部存储设备上的查找场景,因为它在某些情况下可以减少磁盘 I/O 次数。但在现代计算机系统中,内存访问速度有了极大提升,且磁盘存储设备的性能也不断改进,这种减少磁盘 I/O 的优势不再那么明显。
  • 常用场景:在一些对内存使用和数据分布有特殊要求的嵌入式系统或特定算法中可能会使用斐波那契查找。例如,在某些资源受限的嵌入式设备中,当需要对有序数据进行查找时,斐波那契查找可以在一定程度上平衡查找效率和资源消耗。

插值查找

  • 原理:插值查找是对二分查找的一种改进,它根据待查找关键字在数据集中的大致位置,动态地计算分割点。通过估计目标值在数组中的位置,插值查找可以更快地缩小查找范围,尤其适用于数据均匀分布的有序数组。
  • 不常用的原因
    • 对数据分布要求高:插值查找的效率依赖于数据的均匀分布。如果数据分布不均匀,插值查找的性能可能会大幅下降,甚至不如二分查找。在实际应用中,很多数据集的数据分布并不均匀,这限制了插值查找的广泛使用。
    • 边界情况处理复杂:当数据分布极端不均匀时,插值查找计算出的分割点可能会超出合理范围,需要进行额外的边界检查和处理,增加了算法的复杂度。
  • 常用场景:在数据均匀分布且数据量较大的场景下,插值查找能发挥出明显的优势。例如,在一些科学计算、地理信息系统等领域,数据可能具有较好的均匀性,此时使用插值查找可以显著提高查找效率。

相关文章:

常用查找算法整理(顺序查找、二分查找、哈希查找、二叉排序树查找、平衡二叉树查找、红黑树查找、B树和B+树查找、分块查找)

常用的查找算法&#xff1a; 顺序查找&#xff1a;最简单的查找算法&#xff0c;适用于无序或数据量小的情况&#xff0c;逐个元素比较查找目标值。二分查找&#xff1a;要求数据有序&#xff0c;通过不断比较中间元素与目标值&#xff0c;将查找范围缩小一半&#xff0c;效率…...

Express 中 res 响应方法详解

一、res.send() 1. 功能 该方法用于发送各种类型的响应&#xff0c;包括字符串、对象、数组、Buffer 等。它会自动设置响应的 Content-Type 头。 2. 示例代码 const express require("express");const app express();app.get("/", (req, res) > {…...

DeepAR:一种用于时间序列预测的深度学习模型

介绍 DeepAR是一种基于递归神经网络&#xff08;RNN&#xff09;的时间序列预测模型&#xff0c;由亚马逊在2017年提出。它特别适用于处理多变量时间序列数据&#xff0c;并能够生成概率预测。DeepAR通过联合训练多个相关时间序列来提高预测性能&#xff0c;从而在实际应用中表…...

权限模型深度解析:RBAC vs ABAC vs PBAC vs TBAC,如何选择最适合的方案?

在数字化系统的安全架构中&#xff0c;权限管理如同一把“隐形钥匙”&#xff0c;既需精准控制访问边界&#xff0c;又要灵活适配复杂多变的业务需求。从传统的角色划分到动态属性策略&#xff0c;从合规驱动的集中管控到任务流程的临时授权&#xff0c;RBAC、ABAC、PBAC、TBAC…...

Windows逆向工程入门之堆栈结构与信息获取

公开视频 -> 链接点击跳转公开课程博客首页 -> ​​​链接点击跳转博客主页 目录 1. 堆栈结构基础 堆栈的主要操作&#xff1a; 2. 代码功能解析 2.1 加载 ntdll.dll 2.2 获取 NtQueryInformationThread 函数指针 2.3 调用 NtQueryInformationThread 获取线程信息…...

【c++初阶】类和对象②默认成员函数以及运算符重载初识

目录 ​编辑 默认成员函数&#xff1a; 构造函数 构造函数的特性&#xff1a; 析构函数&#xff1a; 拷贝构造函数&#xff1a; 1. 拷贝构造函数是构造函数的一个重载形式。 2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用&#xff0c;使用传值方式编译器直接报…...

【做一个微信小程序】校园地图页面实现

前言 上一个教程我们实现了小程序的一些的功能&#xff0c;有背景渐变色&#xff0c;发布功能有的呢&#xff0c;已支持图片上传功能&#xff0c;表情和投票功能开发中&#xff08;请期待&#xff09;。下面是一个更高级的微信小程序实现&#xff0c;包含以下功能&#xff1a;…...

成熟开发者需具备的能力

精业务 • 指深入理解和熟悉所开发软件的业务逻辑和需求。 • 开发者需要明确软件要解决的问题、面向的用户群体以及核心功能等。 • 精业务有助于开发者更好地设计系统架构、编写符合业务需求的代码&#xff0c;并能根据业务变化灵活调整开发计划。 懂原理 • 指掌握编程的基…...

计算机毕业设计--基于深度学习技术(Yolov11、v8、v7、v5)算法的高效人脸检测模型设计与实现(含Github代码+Web端在线体验界面)

基于深度学习技术&#xff08;Yolov11、v8、v7、v5&#xff09;算法的高效人脸检测模型 Yolo算法应用之《基于Yolo的花卉识别算法模型设计》&#xff0c;请参考这篇CSDN作品&#x1f447; 计算机毕业设计–基于深度学习技术&#xff08;Yolov11、v8、v7、v5&#xff09;算法的…...

力扣做题记录 (二叉树)

二叉树 打算先来了解二叉树基础&#xff0c;都是简单题&#xff0c;目的是熟悉代码格式和解题基础思路。 1、二叉树最大深度 二叉树最大深度 方法一、深度搜索 直接用原函数做递归&#xff0c;比较简单 /*** Definition for a binary tree node.* struct TreeNode {* …...

机试刷题_字符串的排列【python】

题目&#xff1a;字符串的排列 from os import dup # # 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可 # # # param str string字符串 # return string字符串一维数组 # class Solution:def backtrack(self,res,state,choi…...

百度智能云—千帆 ModelBuilder API的简单调用(Java)

百度简介 百度&#xff08;Baidu&#xff09;是拥有强大互联网基础的领先AI公司。百度愿景是&#xff1a;成为最懂用户&#xff0c;并能帮助人们成长的全球顶级高科技公司。 “百度”二字&#xff0c;来自于八百年前南宋词人辛弃疾的一句词&#xff1a;众里寻他千百度。这句话…...

unity学习43:子状态机 sub-state machine

目录 1sub-state machine子状态机 1.1 创建 sub-state machine 1.2 sub-state machine 内容 1.3 子状态机的应用 2 子状态机不同于blend tree的嵌套 3 应用例子&#xff1a;若角色拿不同武器的动画设计&#xff0c;可以使用2种方法 3.1 在1个图层layer里&#xff0c;使用…...

Qt MainWindow

文章目录 0. 概述1. 菜单栏 QMenuBar1.1 例子1&#xff0c;使用图形化界面1.2 例子2&#xff0c;使用代码创建1.3 例子3&#xff0c;添加快捷键1.4 例子4&#xff0c;添加子菜单1.5 例子5&#xff0c;添加分割线和图标1.6 内存泄漏问题 2. 工具栏 QToolBar2.1 例子1&#xff0c…...

GDB QUICK REFERENCE (GDB 快速参考手册)

GDB QUICK REFERENCE {GDB 快速参考手册} References GDB QUICK REFERENCE GDB Version 4 https://users.ece.utexas.edu/~adnan/gdb-refcard.pdf 查看方式&#xff1a;在新标签页中打开图片 References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/ [2] gdb-refc…...

【数据结构】 栈和队列

在计算机科学的世界里&#xff0c;数据结构是构建高效算法的基础。栈&#xff08;Stack&#xff09;和队列&#xff08;Queue&#xff09;作为两种基本且重要的数据结构&#xff0c;在软件开发、算法设计等众多领域都有着广泛的应用。今天&#xff0c;我们就来深入探讨一下栈和…...

AI视频创作教程:如何用AI让古画动起来。

事情缘由&#xff1a; 如果是简单的图&#xff0c;找原图直接写提示词即可。 如果碰到多人多活动的图&#xff0c;直接出的效果会很不好&#xff0c;那么该怎么做呢&#xff1f; 图片分模块 首先&#xff0c;复杂部分的图&#xff0c;把长图分多个模块。 比如这张图&#xff0…...

撕碎QT面具(1):Tab Widget转到某个Tab页

笔者未系统学过C语法&#xff0c;仅有Java基础&#xff0c;具体写法仿照于大模型以及其它博客。自我感觉&#xff0c;如果会一门对象语言&#xff0c;没必要先刻意学C&#xff0c;因为自己具有对象语言的基础&#xff0c;等需要用什么再学也不迟。毕竟不是专门学C去搞算法。 1…...

DeepSeek24小时写作机器人,持续创作高质量文案

内容创作已成为企业、自媒体和创作者的核心竞争力。面对海量的内容需求&#xff0c;人工创作效率低、成本高、质量参差不齐等问题日益凸显。如何在有限时间内产出高质量内容&#xff1f;DeepSeek写作机器人&#xff0c;一款24小时持续创作的智能工具&#xff0c;为企业和个人提…...

npm安装依赖(npm install)时遇到认证错误的解决方案

问题描述 在使用 npm install 安装依赖时遇到以下错误&#xff1a; npm error code E401 npm error Incorrect or missing password.解决方案 方案一&#xff1a;使用淘宝&#xff08;或其它国内公共&#xff09;镜像&#xff08;如果已经是淘宝镜像跳过此步&#xff09; 设…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

多种风格导航菜单 HTML 实现(附源码)

下面我将为您展示 6 种不同风格的导航菜单实现&#xff0c;每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

.Net Framework 4/C# 关键字(非常用,持续更新...)

一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...