【数据结构查找算法篇】----二分查找【实战项目】

简介: 【数据结构查找算法篇】----二分查找【实战项目】

作为一名对技术充满热情的学习者,我一直以来都深刻地体会到知识的广度和深度。在这个不断演变的数字时代,我远非专家,而是一位不断追求进步的旅行者。通过这篇博客,我想分享我在某个领域的学习经验,与大家共同探讨、共同成长。请大家以开放的心态阅读,相信你们也会在这段知识之旅中找到启示。



前言

想象一下,你在一座图书馆里的一排排整齐排列的书架前,这些书按照字母顺序摆放着,你需要找到一本名字以字母"M"开头的书。

使用二分查找的方法,你会:

  1. 先站在书架的中间位置,这大致相当于字母顺序的“M”附近。
  2. 如果你手上的书以字母“S”开头,那么你知道“M”开头的书应该在你左边的上。
  3. 你忽略右半部分的书架,将范围缩小到左半部分。
  4. 然后你去左边书架的中间位置,重复进行查找。
  5. 如果这次拿到的书以字母“G”开头,意味着“M”会在你右手边的书架上。
  6. 再次缩小范围,只考虑“G”和“S”之间的部分,并在这个缩小后的范围内继续寻找。
    重复以上步骤,直到你找到以“M”开头的书或者确定书架上没有这本书为止。每一次查找,你都把搜索范围减半,直至找到所需的书或缩小到无法再分割的范围。

一、什么是二分查找

二分查找(Binary Search)是一种在有序数组中查找特定元素的高效搜索算法。二分查找的关键是每一次比较都可以排除掉一半的搜索范围,它利用了数据的有序性,这个算法的时间复杂度是 O(log n),其中 n 是数组中的元素数量。

二分查找算法的基本步骤如下:

  1. 确定数组的中间点索引(mid),通常是 (low + high) / 2,这里 low 是搜索范围的下界,high 是上界。
  2. 将待查找的值(target)与中间点的元素值进行比较:
  • 如果 target 等于中间点的元素,则找到了这个元素,返回其索引。
  • 如果 target 小于中间点的元素,则表示 target 应该在数组的左侧(较低的半部分),则设置 high 为 mid - 1,然后回到第一步。
  • 如果 target 大于中间点的元素,则表示 target 应该在数组的右侧(较高的半部分),则设置 low 为 mid + 1,然后回到第一步。
  1. 如果 low 超过 high,表示搜索范围内没有找到目标元素,返回一个指示未找到的特殊值,例如 -1。

下面是一个简单的二分查找实现的伪代码:

function binarySearch(array, target) {
    let low = 0;
    let high = array.length - 1;
    while (low <= high) {
        let mid = low + (high - low) / 2;  // 更安全的计算方式,可以防止溢出
        if (array[mid] == target) {
            return mid;  // 找到目标,返回索引
        } else if (array[mid] < target) {
            low = mid + 1;  // 继续在右侧查找
        } else {
            high = mid - 1;  // 继续在左侧查找
        }
    }
    return -1;  // 没有找到目标元素
}

使用二分查找时,你的数据必须是有序的,否则这种搜索方法将不起作用。它主要应用于静态数据或者不经常变更的情况,因为如果数据经常变动,维护排序的成本可能会很高。

二、示例

序数组是 [1, 3, 5, 7, 9, 11, 13, 15, 17, 19],我们要查找的目标值是 11。

步骤如下:

  1. 初始化: low = 0, high = 9(数组的最后一个索引)
  2. 步骤一: 计算中间点索引
    mid = (low + high) / 2 = (0 + 9) / 2 = 4.5,取整数部分 mid = 4
    查找数组索引为4的元素,发现 array[4] = 9
  3. 步骤二: 比较中间点的元素和目标值
  • 因为 9 < 11,我们知道如果11在数组里,它应该在中间点的右边。
  • 因此,我们调整 low = mid + 1,即 low = 4 + 1 = 5。
  1. 步骤三: 重复步骤一
  • 我们更新了low,现在的搜索范围变成了索引5到索引9。
  • 再次计算新的中间点 mid = (low + high) / 2 = (5 + 9) / 2 = 7
  1. 查找数组索引为7的元素,发现 array[7] = 15
  2. 步骤四: 再次比较中间点的元素和目标值
  • 因为 15 > 11,我们知道如果11在数组里,它应该在中间点的左边。
  • 因此,我们调整 high = mid - 1,即 high = 7 - 1 = 6。
  1. 步骤五: 再次重复步骤一
  • 我们更新了high,现在的搜索范围缩小到了索引5到索引6。
  • 再次计算中间点 mid = (low + high) / 2 = (5 + 6) / 2 = 5.5,取整数部分 mid = 5
  1. 查找数组索引为5的元素,发现 array[5] = 11
  2. 步骤六: 比较中间点的元素和目标值
  • 发现 array[5] = 11 正是我们要查找的目标值。

因此,最终我们发现目标值11位于数组的索引5处。通过以上步骤,我们使用二分查找在数组 [1, 3, 5, 7, 9, 11, 13, 15, 17, 19] 中找到了数字11。

三、应用场景

二分查找算法在实际应用中非常广泛,一些典型的应用场景包括:

  1. 查找元素: 在有序数组中快速查找特定的元素。
  2. 计算机科学: 编译器在解析到排序后的符号表时,用以快速查找变量和函数的地址。
  3. 优化问题: 在具有单调性质的实际问题中,如在一定范围内寻找满足特定条件的最小(或最大)值。
  4. 数学问题:在数值分析中,二分查找可用于求解处于有界区间的根问题,如二分法求解方程的根。
  5. 数据库: SQL 数据库在索引结构(如B树等)中使用二分查找来快速定位记录。
  6. 机器学习: 一些机器学习算法在训练过程中对超参数进行优化时会使用二分查找,尤其是在超参数有明确边界的情况下。
  7. 游戏开发: 在游戏中对一系列有序的水平阈值进行检索,以确定一个特定的游戏状态或触发事件。
  8. 软件工程: 在版本控制时使用二分查找来快速识别引入错误的代码版本(比如git中的bisect命令)。
  9. 网页开发: 对工作中的时间排序日志进行搜索,以查找事件发生的具体时间点。
  10. 文件系统: 在文件系统中,二分查找可以用来搜索大型有序文件列表,例如查找目录项。

二分查找算法之所以受欢迎,是因为它的效率很高,其时间复杂度为O(log n),远低于简单查找的O(n)复杂度。因此,在处理大数据集时,如果数据已排序,二分查找或其变体通常是首选的查找算法。

四、底层逻辑

二分查找的底层逻辑是基于分而治之(Divide and Conquer)的策略。其核心思想是将一个大问题分解成小问题来解决,并且利用数据的有序性缩减搜索范围,从而大幅度提高查找效率。具体逻辑如下:

1.有序数据: 二分查找要求数据是有序的,无论是升序还是降序。

2.选择中点: 在当前查找范围内选择一个中点。数字比较通常是对当前查找范围的最低索引和最高索引进行求和后除以二取整得到。

3.比较中点值: 将中点的值与目标值进行比较。

  • 如果中点的值等于目标值,则查找成功,返回中点索引。
  • 如果中点的值小于目标值,则说明目标值应该在当前中点的右边(对于升序数组)。那么将查找范围调整为中点右侧的子区间,并且再次进行中点选择和比较的操作。
  • 如果中点的值大于目标值,则说明目标值应该在当前中点的左边(对于升序数组)。那么将查找范围调整为中点左侧的子区间,并且再次进行中点选择和比较的操作。

4.迭代或递归: 这个过程可以通过迭代(循环)或递归的方式重复进行,直到找到目标值或者当前查找范围为空(即开始索引大于结束索引)为止,后者意味着目标值不存在于数组中。

五、Java实战项目

  • 使用二分查找解决类似于在一个有序的商品价格列表中查找特定价格或者价格区间的问题,或者在游戏开发中搜索特定分数的排名等。以下是一个简单的Java代码示例,用于在一个有序数组中查找一个值,并包含了详细的代码解释和问题解决思路。
public class BinarySearchExample {
    /**
     * 二分查找算法
     * 
     * @param arr 有序数组
     * @param key 要查找的值
     * @return key在数组中的位置,若不在数组中返回-1
     */
    public static int binarySearch(int[] arr, int key) {
        int low = 0;
        int high = arr.length - 1;
        while (low <= high) {
            // 计算中间索引
            int mid = low + (high - low) / 2; // 避免直接相加导致的整数溢出
            int midVal = arr[mid];
            if (midVal < key) {
                low = mid + 1; // 如果中间值小于查找值,在中间值的右侧继续查找
            } else if (midVal > key) {
                high = mid - 1; // 如果中间值大于查找值,在中间值的左侧继续查找
            } else {
                return mid; // 找到目标值,直接返回其索引
            }
        }
        return -1; // 目标值不在数组中,返回-1作为没有找到的标记
    }
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19}; // 有序数组
        int key = 11; // 要查找的值
        int index = binarySearch(arr, key); // 调用二分查找方法
        if (index != -1) {
            System.out.println("元素 " + key + " 在数组中的位置是: " + index);
        } else {
            System.out.println("元素 " + key + " 不在数组中");
        }
    }
}

解决思路:

  1. 确定查找范围: 初始化两个指针,lowhigh 分别指向数组的起始位置和结束位置。
  2. 循环查找: 当 low 小于或者等于 high 时,执行下面的步骤。否则跳出循环,表示没有找到。
  3. 计算中点: 计算 lowhigh 的中间位置 mid,为了防止lowhigh 相加时整数溢出,使用 low+(high - low)/2 来代替 (low + high) / 2
  4. 比较: 获取中间位置的值midVal,和目标值key进行比较:
  • 如果 midVal 小于 key,说明 key 应该在 mid 右边的区域,更新 low = mid + 1
  • 如果 midVal 大于 key,说明 key 应该在 mid 左边的区域,更新 high = mid - 1
  • 如果相等,则说明找到目标值,返回该位置 mid
  1. 返回结果: 如果循环结束,意味着没有找到目标值,返回 -1

以上是使用二分查找在Java项目中解决问题的一般方法和步骤。这个例子中的原理和代码结构可以适用于任何需要快速查找的场景。


总结

二分查找,分而治之。

感谢大家抽出宝贵的时间来阅读博主的博客,新人博主,感谢大家关注点赞,祝大家未来的学习工作生活一帆风顺,加油!!!

目录
相关文章
|
13天前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
23 1
|
16天前
|
机器学习/深度学习 算法 数据挖掘
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构。本文介绍了K-means算法的基本原理,包括初始化、数据点分配与簇中心更新等步骤,以及如何在Python中实现该算法,最后讨论了其优缺点及应用场景。
58 4
|
2月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
90 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
2月前
|
存储 Java 开发者
Java Map实战:用HashMap和TreeMap轻松解决复杂数据结构问题!
【10月更文挑战第17天】本文深入探讨了Java中HashMap和TreeMap两种Map类型的特性和应用场景。HashMap基于哈希表实现,支持高效的数据操作且允许键值为null;TreeMap基于红黑树实现,支持自然排序或自定义排序,确保元素有序。文章通过具体示例展示了两者的实战应用,帮助开发者根据实际需求选择合适的数据结构,提高开发效率。
70 2
|
14天前
|
存储 算法 搜索推荐
Python 中数据结构和算法的关系
数据结构是算法的载体,算法是对数据结构的操作和运用。它们共同构成了计算机程序的核心,对于提高程序的质量和性能具有至关重要的作用
|
13天前
|
数据采集 存储 算法
Python 中的数据结构和算法优化策略
Python中的数据结构和算法如何进行优化?
|
21天前
|
算法
数据结构之路由表查找算法(深度优先搜索和宽度优先搜索)
在网络通信中,路由表用于指导数据包的传输路径。本文介绍了两种常用的路由表查找算法——深度优先算法(DFS)和宽度优先算法(BFS)。DFS使用栈实现,适合路径问题;BFS使用队列,保证找到最短路径。两者均能有效查找路由信息,但适用场景不同,需根据具体需求选择。文中还提供了这两种算法的核心代码及测试结果,验证了算法的有效性。
82 23
|
21天前
|
算法
数据结构之蜜蜂算法
蜜蜂算法是一种受蜜蜂觅食行为启发的优化算法,通过模拟蜜蜂的群体智能来解决优化问题。本文介绍了蜜蜂算法的基本原理、数据结构设计、核心代码实现及算法优缺点。算法通过迭代更新蜜蜂位置,逐步优化适应度,最终找到问题的最优解。代码实现了单链表结构,用于管理蜜蜂节点,并通过适应度计算、节点移动等操作实现算法的核心功能。蜜蜂算法具有全局寻优能力强、参数设置简单等优点,但也存在对初始化参数敏感、计算复杂度高等缺点。
56 20
|
13天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
37 1
|
21天前
|
机器学习/深度学习 算法 C++
数据结构之鲸鱼算法
鲸鱼算法(Whale Optimization Algorithm,WOA)是由伊朗研究员Seyedali Mirjalili于2016年提出的一种基于群体智能的全局优化算法,灵感源自鲸鱼捕食时的群体协作行为。该算法通过模拟鲸鱼的围捕猎物和喷出气泡网的行为,结合全局搜索和局部搜索策略,有效解决了复杂问题的优化需求。其应用广泛,涵盖函数优化、机器学习、图像处理等领域。鲸鱼算法以其简单直观的特点,成为初学者友好型的优化工具,但同时也存在参数敏感、可能陷入局部最优等问题。提供的C++代码示例展示了算法的基本实现和运行过程。
43 0