二分查找
学习目的:通读算法入门书籍《算法图解》,学习二分查找,了解大O表示法,分析算法运行时间。
自己也从过去经历的项目中实践过Java的脚步开发和编程,实际没有用到过算法。还是认为学习算法是提高水平的必经之路,而且面试过程中也会用到。
最近继续刷牛客网的基础语法编程,觉得算法是编程的灵魂,能成功完成一些简单算法的编程题并运行成功,非常有成就感。逐渐对算法有了一些兴趣,希望自己能一直保持下去,学习的更加系统和深入。周末趁着有时间,在网上看别人学习感悟和学习路径。从github上找了一些算法书籍,从入门的《算法图解》、到《数据结构与算法分析:Java 语言描述》,等等还有很多相关书籍资料。贪多嚼不烂,先从这2本书开始,慢慢学习。
二分查找算法思想
最简单的理解,就像《算法图解》作者的举例,就是我们小时候玩的猜数字游戏,从1~100中抽一个数字,让另外的小朋友去猜,看谁最快猜到正确数字。
有序的序列,每次都是以序列的中间位置的数来与待查找的关键字进行比较,每次缩小一半的查找范围,直到匹配成功。
一个情景:将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
大 O 表示法
大O表示法是一种特殊的表示法,指出了算法的速度有多快。
算法的运行时间以不同的速度增加
有鉴于此,仅知道算法需要多长时间才能运行完毕还不够,还需知道运行时间如何随列表增长而增加。这正是大O表示法的用武之地。
大O表示法指出了算法有多快。例如,假设列表包含n个元素。简
单查找需要检查每个元素,因此需要执行n次操作。使用大O表示法,
这个运行时间为O(n)。单位秒呢?没有——大O表示法指的并非以秒为单位的速度。大O表示法
让你能够比较操作数,它指出了算法运行时间的增速。
一些常见的大 O 运行时间
下面按从快到慢的顺序列出了你经常会遇到的5种大O运行时间。
O(log n),也叫对数时间,这样的算法包括二分查找。
O(n),也叫线性时间,这样的算法包括简单查找。
O(n * log n),这样的算法包括第4章将介绍的快速排序——一种速度较快的排序算法。
O(n2),这样的算法包括第2章将介绍的选择排序——一种速度较慢的排序算法。
O(n!),这样的算法包括接下来将介绍的旅行商问题的解决方案——一种非常慢的算法。
算法的速度指的并非时间,而是操作数的增速。
谈论算法的速度时,我们说的是随着输入的增加,其运行时间将以什么样的速度增加。
算法的运行时间用大O表示法表示。
O(log n)比O(n)快,当需要搜索的元素越多时,前者比后者快得越多。
二分查找优缺点
优点:
- 时间复杂度低: 二分查找的时间复杂度是 O(log n),相比线性搜索 O(n),在大型有序数据集上具有更高的效率。
- 简单清晰: 算法逻辑相对简单,易于理解和实现。
- 节省空间: 二分查找通常只需要很少的额外空间,主要用于存储左右边界、中间索引等变量。
缺点:
- 仅适用于有序数据: 二分查找要求数据集是有序的,如果数据集无序,需要先进行排序,这可能增加额外的时间复杂度。
- 不适用于链表: 二分查找需要随机访问,对于链表这样的数据结构,二分查找的效率较低,因为它无法直接跳到中间元素。
- 只能查找单一元素: 二分查找适用于查找单一元素的情况,如果需要查找多个匹配的元素,可能需要其他算法。
- 不适用于动态数据集: 如果数据集经常发生变化,频繁的插入和删除可能会导致数组重新排序,使得二分查找的优势减弱。
总体来说,二分查找是一种非常有用的算法,尤其适用于有序数据集的查找操作。然而,使用前需要考虑数据集的特点和操作需求,以确保选择最适合的算法。
线性时间:
回到前面的二分查找。使用它可节省多少时间呢?简单查找逐个地检查数
字,如果列表包含100个数字,最多需要猜100次。如果列表包含40亿个数字,最
多需要猜40亿次。换言之,最多需要猜测的次数与列表长度相同,这被称为线性
时间(linear time)。
以下是二分查找的详细步骤:
- 初始化左右边界:将初始查找范围设置为整个数组,即
left = 0
,right = array.length - 1
。 - 计算中间元素的索引:
mid = (left + right) / 2
。 - 检查中间元素是否是目标元素:
- 如果
array[mid] == target
,则找到目标元素,返回mid
。 - 如果
array[mid] < target
,说明目标元素在右半部分,更新left = mid + 1
。 - 如果
array[mid] > target
,说明目标元素在左半部分,更新right = mid - 1
。
- 重复步骤2和步骤3,直到找到目标元素或左边界大于右边界
实例:
描述
请实现无重复数字的升序数组的二分查找
给定一个 元素升序的、无重复数字的整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标(下标从 0 开始),否则返回 -1
数据范围:0≤𝑙𝑒𝑛(𝑛𝑢𝑚𝑠)≤2×1050≤len(nums)≤2×105 , 数组中任意值满足 ∣𝑣𝑎𝑙∣≤109∣val∣≤109
进阶:时间复杂度 𝑂(log𝑛)O(logn) ,空间复杂度 𝑂(1)O(1)
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param nums int整型一维数组 * @param target int整型 * @return int整型 */ public int search (int[] nums, int target) { // write code here int left = 0; int right = nums.length - 1; int mid = 0; while(left<=right){ mid=left+( right-left) / 2; if (target==nums[mid]) { return mid; } else if (target <nums[mid]) { right = mid - 1; } else { left = mid + 1; } } return -1; } }