(笔记整合)二分法查找

本文详细介绍了二分查找算法的基本概念、时间复杂度分析、实现方法及其适用场景,并提供了四种常见变形问题的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、什么是二分查找?

二分查找针对的是一个有序的数据集合,每次通过跟区间中间的元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间缩小为0。
在这里插入图片描述

二、时间复杂度分析?

1.时间复杂度
假设数据大小是n,每次查找后数据都会缩小为原来的一半,最坏的情况下,直到查找区间被缩小为空,才停止。所以,每次查找的数据大小是:n,n/2,n/4,…,n/(2k),…,这是一个等比数列。当n/(2k)=1时,k的值就是总共缩小的次数,也是查找的总次数。而每次缩小操作只涉及两个数据的大小比较,所以,经过k次区间缩小操作,时间复杂度就是O(k)。通过n/(2k)=1,可求得k=log2n,所以时间复杂度是O(logn)。
在这里插入图片描述

2.认识O(logn)
①这是一种极其高效的时间复杂度,有时甚至比O(1)的算法还要高效。为什么?
②因为logn是一个非常“恐怖“的数量级,即便n非常大,对应的logn也很小。比如n等于2的32次方,也就是42亿,而logn才32。
③由此可见,O(logn)有时就是比O(1000),O(10000)快很多。

三、如何实现二分查找?

1.循环实现
代码实现:

    public int binarySearch1(int[] a, int val) {
        int start = 0;
        int end = a.length - 1;
        while (start <= end) {
            int mid = start + (end - start) / 2;
            if (a[mid] > val) {
                end = mid - 1;
            } else if (a[mid] < val) {
                start = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }

注意事项:
①循环退出条件是:start<=end,而不是start<end。
②mid的取值,使用mid=start + (end - start) / 2,而不用mid=(start + end) / 2,因为如果start和end比较大的话,求和可能会发生int类型的值超出最大范围。为了把性能优化到极致,可以将除以2转换成位运算,即start + ((end - start) >> 1),因为相比除法运算来说,计算机处理位运算要快得多。
③start和end的更新:start = mid - 1,end = mid + 1,若直接写成start = mid,end = mid,就可能会发生死循环。

2.递归实现

	public int binarySearch(int[] a, int val) {
        return bSear(a, val, 0, a.length - 1);
    }

    private int bSear(int[] a, int val, int start, int end) {
        if (start > end) { return -1; }
        int mid = start + (end - start) / 2;
        if (a[mid] == val) {
            return mid;
        } else if (a[mid] > val) {
            end = mid - 1;
        } else {
            start = mid + 1;
        }
        return bSear(a, val, start, end);
    }

四、使用条件(应用场景的局限性)

1.二分查找依赖的是顺序表结构,即数组。
2.二分查找针对的是有序数据,因此只能用在插入、删除操作不频繁,一次排序多次查找的场景中。
3.数据量太小不适合二分查找,与直接遍历相比效率提升不明显。但有一个例外,就是数据之间的比较操作非常费时,比如数组中存储的都是长度超过300的字符串,那这是还是尽量减少比较操作使用二分查找吧。
4.数据量太大也不是适合用二分查找,因为数组需要连续的空间,若数据量太大,往往找不到存储如此大规模数据的连续内存空间。

五、四种常见的二分查找变形问题

“十个二分九个错”。二分查找虽然原理极其简单,但是想要写出没有 Bug 的二分查找并不容易。
1.查找第一个值等于给定值的元素
2.查找最后一个值等于给定值的元素
3.查找第一个大于等于给定值的元素
4.查找最后一个小于等于给定值的元素

public class BinarySearch2 {
    public int bsearch1(int[] a, int value) {
        int low = 0;
        int high = a.length - 1;
        while (low <= high) {
            int mid = low + ((high - low) >> 1);
            if (a[mid] > value) {
                high = mid - 1;
            } else if (a[mid] < value) {
                low = mid + 1;
            } else {
                if ((mid == 0) || (a[mid - 1] != value)) {
                    return mid;
                } else {
                    high = mid - 1;
                }
            }
        }
        return -1;
    }

    public int bsearch2(int[] a, int value) {
        int low = 0;
        int high = a.length - 1;
        while (low <= high) {
            int mid = low + ((high - low) >> 1);
            if (a[mid] > value) {
                high = mid - 1;
            } else if (a[mid] < value) {
                low = mid + 1;
            } else {
                if ((mid == a.length - 1) || (a[mid + 1] != value)) {
                    return mid;
                } else {
                    low = mid + 1;
                }
            }
        }
        return -1;
    }

    public int bsearch3(int[] a, int value) {
        int low = 0;
        int high = a.length - 1;
        while (low <= high) {
            int mid = low + ((high - low) >> 1);
            if (a[mid] >= value) {
                if ((mid == 0) || (a[mid - 1] < value)) {
                    return mid;
                } else {
                    high = mid - 1;
                }
            } else {
                low = mid + 1;
            }
        }
        return -1;
    }

    public int bsearch4(int[] a, int value) {
        int low = 0;
        int high = a.length - 1;
        while (low <= high) {
            int mid = low + ((high - low) >> 1);
            if (a[mid] > value) {
                high = mid - low;
            } else {
                if ((mid == a.length - 1) || (a[mid + 1] > value)) {
                    return mid;
                } else {
                    low = mid + 1;
                }
            }
        }
        return -1;
    }
}

六、适用性分析

1.凡事能用二分查找解决的,绝大部分我们更倾向于用散列表或者二叉查找树,即便二分查找在内存上更节省,但是毕竟内存如此紧缺的情况并不多。
2.求“值等于给定值”的二分查找确实不怎么用到,二分查找更适合用在”近似“查找问题上。比如上面讲几种变体。

七、思考

如何在 1000 万个整数中快速查找某个整数?
这个问题并不难。我们的内存限制是 100MB,每个数据大小是 8 字节,最简单的办法就是将数据存储在数组中,内存占用差不多是 80MB,符合内存的限制。我们可以先对这 1000 万数据从小到大排序,然后再利用二分查找算法,就可以快速地查找想要的数据了。

看起来这个问题并不难,很轻松就能解决。实际上,它暗藏了“玄机”。如果你对数据结构和算法有一定了解,知道散列表、二叉树这些支持快速查找的动态数据结构。你可能会觉得,用散列表和二叉树也可以解决这个问题。实际上是不行的。

虽然大部分情况下,用二分查找可以解决的问题,用散列表、二叉树都可以解决。但是,不管是散列表还是二叉树,都会需要比较多的额外的内存空间。如果用散列表或者二叉树来存储这 1000 万的数据,用 100MB 的内存肯定是存不下的。而二分查找底层依赖的是数组,除了数据本身之外,不需要额外存储其他信息,是最省内存空间的存储方式,所以刚好能在限定的内存大小下解决这个问题。

<think>好的,用户现在想了解如何开始学习编程技能,特别是学习路径和建议。根据用户之前的问题,他们一开始是想学人工智能,现在转向更基础的编程技能。我需要结合之前的回答和用户提供的三个引用来构建回应。 首先,用户提到的三个引用分别涉及编程语言选择、Python学习路线和前端开发建议。虽然用户现在的问题是编程入门,但需要整合这些信息。引用[1]提到选择编程语言的重要性,引用[2]详细说明Python的学习路径,引用[3]讲前端开发的技能。但用户的问题更广泛,是编程入门,而不仅仅是Python或前端。所以需要综合这些内容,但又不局限于特定领域。 用户之前对人工智能的学习路径已经涉及数学、Python和机器学习,现在转向编程基础,可能需要从更通用的角度出发。比如,先确定目标,选择语言,再系统学习,最后实践项目。同时,用户可能对如何选择语言有疑问,引用[1]和[2]都提到了Python,这可能是一个推荐点,尤其是因为Python在AI中的重要性,但也要提到其他语言如JavaScript(引用[3]中的前端)或Java等,根据用户的目标调整。 接下来,需要结构清晰的分步骤指南。可能分为:明确目标、选择语言、学习路径、实践项目、持续提升。每个部分参考之前的引用和用户历史回答。例如,在明确目标部分,引用[1]中的职业发展考虑;选择语言部分,引用Python的优势(引用[2])和前端需要的JavaScript(引用[3]);学习路径中结合基础语法、算法等;实践项目参考之前AI指南中的项目驱动学习;持续提升包括参与社区、开源项目等。 同时,用户可能遇到常见问题,比如数学基础差如何学编程,或者如何保持动力,这些需要包含在注意事项里。另外,要确保回答中的LaTeX格式正确,例如代码块用反引号,数学公式用$...$,如变量名$variable$,以及独立公式用$$...$$,比如算法复杂度示例。 最后,生成相关问题需要考虑用户可能的后续疑问,比如语言选择、资源推荐、项目实践等,结合之前的引用和用户的学习路径。需要确保问题覆盖用户可能的下一个步骤,帮助他们深入探索。</think>### 编程技能入门指南(综合学习路径与避坑建议) #### 一、明确学习目标与语言选择 根据职业规划和技术方向选择**第一门编程语言**(参考引用[1][^1]): | 目标方向 | 推荐语言 | 特点与场景 | |----------------|-------------|------------------------------| | 人工智能/数据分析 | Python | 语法简洁,生态完善(引用[2][^2]) | | 前端开发 | JavaScript | 浏览器直接运行(引用[3][^3]) | | 系统开发/高性能计算 | C++/Java | 执行效率高,企业级应用 | | 全栈开发 | JavaScript+Python | 覆盖前后端 | **新手推荐Python**: ```python # 示例:Python基础语法实践 print("Hello World") # 输出基础文本 sum = lambda a,b: a + b # lambda函数定义 print(sum(3,5)) # 输出8 ``` #### 二、系统学习路径(分阶段递进) 1. **基础语法阶段**(2-4周) - 掌握变量、条件语句、循环结构 - 例如:理解`for i in range(5):`的迭代逻辑 - 学习函数定义与模块化编程 - 函数复杂度示例:$$T(n)=O(n^2)$$ - 推荐工具:VS Code + Jupyter Notebook 2. **核心编程能力**(3-6个月) - **数据结构**:链表、栈、队列的实现(手写代码) - **算法基础**:排序(快速排序)、搜索(二分法) ```python # 二分查找示例 def binary_search(arr, target): left, right = 0, len(arr)-1 while left <= right: mid = (left + right) // 2 if arr[mid] == target: return mid elif arr[mid] < target: left = mid + 1 else: right = mid - 1 return -1 ``` - **版本控制**:Git基础操作(clone/commit/push) 3. **项目实战阶段**(持续实践) | 项目类型 | 技术栈 | 学习目标 | |---------------|---------------------|-----------------------| | 待办事项应用 | Python + SQLite | 掌握CRUD操作 | | 个人博客 | HTML/CSS/JS(引用[3][^3]) | 前端三件套实践 | | 数据可视化 | Python + Matplotlib | 数据分析基础 | #### 三、高效学习技巧与资源 1. **刻意练习法** - 每日代码量:建议≥50行(重点质量而非数量) - 代码重构:优化已实现功能的可读性 2. **优质学习资源** - **免费课程**:Codecademy交互式教程(JavaScript/Python) - **经典书籍**:《Python编程:从入门到实践》(引用[2][^2]) - **实战平台**:LeetCode(算法)、FreeCodeCamp(全栈) #### 四、避坑指南(常见误区) 1. **盲目追求新技术**:先深入掌握一门语言的核心生态 2. **跳过理论基础**:数据结构与算法是代码效率的基石 3. **缺乏文档习惯**:用Markdown记录学习笔记与代码注释 --相关问题--: 1. 零基础如何快速掌握Python基础语法? 2. 编程学习需要重点培养哪些思维能力? 3. 如何通过开源项目提升编程实战能力? [^1]: 编程语言选择与学习计划建议 [^2]: Python系统学习路线与目标规划 [^3]: 前端开发核心技能树与工具链
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值