IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 二分查找及其变种实现详解 -> 正文阅读

[数据结构与算法]二分查找及其变种实现详解

? ? ? ? 二分查找(Binary Search)又叫折半查找,对于已排序的数组,是一种非常高效的排序算法,时间复杂度为O(logn)。二分查找很简单也很高效,但要写好用好二分查找却不容易,多数程序员都不能完整地实现一个无bug的二分查找。

1.最基本的二分查找

? ? ? ? 我们先来看一个最基本的二分查找,在一组无重复的数组中查找指定的数并返回数组索引。

int binarySearch(int* a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid = low + (high - low)/2;
    if (a[mid] == value) {
      return mid;
    } else if (a[mid] < value) {
      low = mid + 1;
    } else {
      high = mid - 1;
    }
  }

  return -1;
}

?容易错的地方:

  • 循环结束条件

? ? ? ? 循环结束条件为 low <= high;

? ? ? ? 错误写法:low < high,low < high可能会导致死循环,边界判断很重要;

  • 中间位置的计算

? ? ? ? 中间位置的计算可以写作 mid = (low+high)/2 或者 mid = low+(high-low)/2。但前者的问题是low和high比较大时low+high可能会溢出,超出int表达的最大范围。如果有对性能的极致要求,还可以把除2改为位运算,写作mid=low+((high-low)>>1),位运算的性能要比除法好很多。

? ? ? ? 错误写法:面试中看到很多人写作 mid = (high-low)/2,这种写法肯定是错误的,只要low不是0,就会出错。

  • 边界更新

? ? ? ? 中间值小于目标值时,low=mid+1;中间值大于目标值时,high=mid-1;

? ? ? ? 错误写法:low=mid; high=mid; 不但有重复计算,而且会导致死循环(例 low==high时)

注:此处的二分查找也可以通过递归实现,递归实现代码更加简洁,不过递归算法递归过深会有堆栈溢出的风险,面试中面试官也更愿意看到非递归的实现。

2. 二分查找的缺点

? ? ? ? 看过了最基本的二分查找实现之后,我们可以发现二分查找有以下局限性:

  • 依赖数组实现,需要使用数组连续存储数据
  • 针对的是有序数据,必须是有序的数才能使用
  • 数据量太大太小都不适合,数据太大内存没有足够连续内存,数据太小体现不出性能优势

在实际应用中,很难保证数据不会有重复,而且我们可能需要查找满足条件的第一个或最后一个元素,下面看看二分查找的一些变体:

3. 二分查找的下界 查找第一个等于目标值的元素

int binarySearch(int* a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + (high - low)/2;
    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;
}

????????这个有序数组中有重复的元素,需要查找出第一个等于目标值的元素。相对第一个算法仅修改了算法退出条件(11-14行),当中间元素值等于目标数时:

  • 如果为数组第一个元素,或者mid左侧的数不为目标数时则返回mid
  • 否则 high=mid-1, 继续向左侧查找

4. 二分查找的上界 查找最后一个等于目标值的元素

int binarySearch(int* a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + (high - low)/2;
    if (a[mid] > value) {
      high = mid - 1;
    } else if (a[mid] < value) {
      low = mid + 1;
    } else {
      if ((mid == n-1) || (a[mid + 1] != value)) 
        return mid;
      else 
        low = mid + 1;
    }
  }
  return -1;
}

这个有序数组中有重复的元素,需要查找出最后一个等于目标值的元素。还是只用修改算法退出条件(11-14行),当中间元素值等于目标数时:

  • 如果为数组最后一个元素,或者mid右侧的数不为目标数时则返回mid
  • 否则 low=mid+1, 继续向右侧查找

5. 查找第一个大于等于目标值的元素

int binarySearch(int* a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid = low + (high - low)/2;
    if (a[mid] >= value) {
      if ((mid == 0) || (a[mid - 1] < value)) 
        return mid;
      else 
        high = mid -1;
    } else{
      low = mid + 1;
    }
  }
  return -1;
}

这个有序数组中有重复的元素,需要查找出第一个等于目标值的元素。相对于查找第一个等于元素的算法,修改算法结束条件,当中间元素值大于等于目标数时:

  • 如果为数组第一个元素,或者mid左侧的数小于目标数时则返回mid
  • 否则 high=mid-1, 继续向左侧查找

6. 查找最后一个小于等于目标值的元素

int binarySearch(int* a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid = low + (high - low)/2;
    if (a[mid] <= value) {
      if ((mid == n-1) || (a[mid + 1] > value)) 
        return mid;
      else 
        low = mid + 1;
    } else{
      high = mid - 1;
    }
  }
  return -1;
}

????????这个有序数组中有重复的元素,需要查找出第一个等于目标值的元素。相对于查找最后一个等于元素的算法,修改算法结束条件,当中间元素值小于等于目标数时:

  • 如果为数组最后一个元素,或者mid右侧的数大于目标数时则返回mid
  • 否则 low=mid+1, 继续向右侧查找

7. 二分查找的优势

? ? ? ?通过学习以上二分查找的变体,我们发现二分查找很适合区间查找,而且时间复杂度都为O(logn)。实际上在工程上一般的查找场景都是使用二叉搜索树(例如红黑树)或者哈希表来实现的,二分查找的优势在于对有序数组的区间查找,二叉搜索树和哈希表区间查找不方便。

? ? ? ?此外我们知道二分查找依赖于数组实现,链表要实现二分查找,可以使用跳跃表(Skip List)来实现,redis中正是使用跳表实现了键值的高效区间查找。

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-08-20 15:22:16  更:2021-08-20 15:24:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 22:50:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码