算法概述

二分查找(英语:binary search),也叫折半查找(英语:half-interval search),是一种在有序数组中查找特定元素的搜索算法。所以,二分查找的前提是数组必须是有序的。

时间复杂度、空间复杂度请参照下图(图片来自wikipedia):

适用情况

二分查找只适用顺序存储结构。为保持表的有序性,在顺序结构里插入和删除都必须移动大量的结点。因此,二分查找特别适用于那种一经建立就很少改动、而又经常需要查找的线性表。

对那些查找少而又经常需要改动的线性表,可采用链表作存储结构,进行顺序查找。链表上无法实现二分查找(更准确的说链表上使用二分查找得不偿失)。

算法原理

二分查找的基本思想是:

  • 设R[low…..high]是当前的查找区间。
  • 首先确定该区间的中点位置:mid = low + ((high - low) >> 1)。
  • 然后将待查的target值与ary[mid]比较:若相等,则查找成功并返回此位置,否则须确定新的查找区间,继续二分查找。
  • 若ary[mid]>target,则由表的有序性可知ary[mid….high]均大于K,因此若表中存在关键字等于target的结点,则该结点必定是在位置mid左边的子表R[low…mid-1]中,故新的查找区间是左子表ary[low…...mid-1]。
  • 若ary[mid]<target,则要查找的target必在mid的右子表ary[mid+1……high]中,即新的查找区间是右子表ary[mid+1……high]。
  • 下一次查找是针对新的查找区间进行的。

因此,从初始的查找区间R[0..n-1]开始,每经过一次与当前查找区间的中点位置上的结点关键字的比较,就可确定查找是否成功,不成功则当前的查找区间就缩小一半。这一过程重复直至找到关键字为target的结点,或者直至当前的查找区间为空(high<low,即查找失败)时为止。

算法实现(C#)

算法基于C#编写,有简单和泛型两种实现,每种实现又分递归版本、While循环版本。实际运用时,推荐使用While循环版本的二分查找。

算法代码如下:

    //此算法假定数组已排序;如果不是这样,则结果将不正确。
class BinarySearch
{
//不要使用mid = (high + low) / 2,可能会导致运算溢出
#region 简单
// 递归版本
public static int Recursive(int[] ary, int target)
{
return Recursive(ary, 0, ary.Length-1, target);
}
static int Recursive(int[] ary, int low, int high, int target)
{
if (high < low) return -1;
int mid = low + ((high - low) >> 1);
if (ary[mid] == target) return mid;
if (ary[mid] > target)
{
return Recursive(ary, low, mid-1, target);
}
else
{
return Recursive(ary, mid + 1, high, target);
}
}
//While循环版本
public static int WhileLoop(int[] ary, int target)
{
int low = 0;
int high = ary.Length - 1;
while (low <= high)
{
int mid = low + ((high - low) >> 1);
if (ary[mid] == target) return mid;
if (ary[mid] > target)
{
high = mid - 1;
}
else
{
low = mid + 1;
}
}
return -1;
}
#endregion #region 泛型
// 递归版本
public static int RecursiveT<T>(T[] ary, T target) where T : IComparable
{
return RecursiveT(ary, 0, ary.Length - 1, target);
}
static int RecursiveT<T>(T[] ary, int low, int high, T target) where T : IComparable
{
if (high < low) return -1;
int mid = low + ((high - low) >> 1);
int cr = Comparer.Default.Compare(ary[mid], target);
if(cr==0)return mid;
if (cr > 0)
{
return RecursiveT(ary, low, mid - 1, target);
}
else
{
return RecursiveT(ary, mid + 1, high, target);
}
}
//While循环版本
public static int WhileLoopT<T>(T[] ary, T target) where T : IComparable
{
int low = 0;
int high = ary.Length - 1;
while (low <= high)
{
int mid = low + ((high - low) >> 1);
int cr = Comparer.Default.Compare(ary[mid], target);
if (cr == 0) return mid;
if (cr>0)
{
high = mid - 1;
}
else
{
low = mid + 1;
}
}
return -1;
}
//默认情况下推荐使用While循环版本
public static int DefaultT<T>(T[] ary, T target) where T : IComparable
{
return WhileLoopT(ary, target);
}
#endregion
}

测试代码如下:

//数组必须有序
//此处用升序递增的整数数组是为了便于检查结果
int[] ary = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
long[] aryT = new long[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
int target = 8;
int r = BinarySearch.Recursive(ary, target);
int w = BinarySearch.WhileLoop(ary, target);
int rT = BinarySearch.RecursiveT(ary, target);
int wT = BinarySearch.WhileLoopT(ary, target);
Console.WriteLine("r={0} w={1} rT={2} wT={3}", r, w, rT, wT);

实际应用:用二分查找法找寻边界值

在集合中找到一个大于(小于)目标数t的数x,使得集合中的任意数要么大于(小于)等于x,要么小于(大于)等于t。

举例来说:给予数组和目标数

int array = {2, 3, 5, 7, 11, 13, 17};
int target = 7;

那么上界值应该是11,因为它“刚刚好”大于7;下界值则是5,因为它“刚刚好”小于7。

该问题不能直接使用二分查找的实现代码解决,需要对代码做一些修改,但解题思路还是二分查找。

实现代码如下:

//用二分查找法找寻上界
static int BSearchUpperBound(int[] ary, int target)
{
int low = 0;
int high = ary.Length - 1;
while (low <= high)
{
int mid = low + ((high - low) >> 1);
if (high == low)
{
if (ary[mid] > target) return mid;
else return -1;
}
if (ary[mid] > target)
{
//当前找到的数大于目标数时,它可能就是我们要找的数,所以需要保留这个索引
high = mid ;
}
else
{
//当前找到的数小于等于目标数时继续向上取区间
low = mid + 1;
}
}
return -1;
}
//用二分查找法找寻下界
static int BSearchLowerBound(int[] ary, int target)
{
int low = 0;
int high = ary.Length - 1;
while (low <= high)
{
//取中间索引时使用向上取整,否则low无法往上爬到下界值
int mid = low + ((high - low + 1) >> 1);
if (high == low)
{
if (ary[mid] < target) return mid;
else return -1;
}
if (ary[mid] >= target)
{
//当前找到的数大于等于目标数时继续向下取区间
high = mid-1;
}
else
{
//当前找到的数小于目标数时,它可能就是我们要找的数,所以需要保留这个索引
low = mid;
}
}
return -1;
}

测试代码如下:

//寻找边界值
int[] array =new int[]{ 2, 3, 5, 7, 11, 13, 17 };
int target =6;
//用二分查找法找寻上届
int up = BSearchUpperBound(array, target);
int lo=BSearchLowerBound(array, target);

参考文章

二分搜索(Binary_Search)——简书

binary search——百度百科

BinarySearch——.NET源码

二分查找BinarySearch原理分析、判定树、及其变种——CSDN

二分查找法的实现和应用汇总——CSDN

简单实用算法——二分查找法(BinarySearch)的更多相关文章

  1. Java-数据结构与算法-二分查找法

    1.二分查找法思路:不断缩小范围,直到low <= high 2.代码: package Test; import java.util.Arrays; public class BinarySe ...

  2. python算法&二分查找法

    import random def random_list(n): result = [] ids = list(range(1001,1001+n)) a1 = ["赵",&qu ...

  3. js 二分查找法之每日一更

    <!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content ...

  4. javascript数据结构与算法---检索算法(二分查找法、计算重复次数)

    javascript数据结构与算法---检索算法(二分查找法.计算重复次数) /*只需要查找元素是否存在数组,可以先将数组排序,再使用二分查找法*/ function qSort(arr){ if ( ...

  5. 【C/C++学院】0723-32位与64位/调戏窗体程序/数据分离算法/内存检索/二分查找法/myVC

    [送给在路上的程序猿] 对于一个开发人员而言,能够胜任系统中随意一个模块的开发是其核心价值的体现. 对于一个架构师而言,掌握各种语言的优势并能够运用到系统中,由此简化系统的开发,是其架构生涯的第一步. ...

  6. Java常用排序算法+程序员必须掌握的8大排序算法+二分法查找法

    Java 常用排序算法/程序员必须掌握的 8大排序算法 本文由网络资料整理转载而来,如有问题,欢迎指正! 分类: 1)插入排序(直接插入排序.希尔排序) 2)交换排序(冒泡排序.快速排序) 3)选择排 ...

  7. 二分查找法&大O表示法

    二分查找法的输入是一个有序的元素列表,如果要查找的元素包含在列表中,二分查找返回其位置,否则返回null Python代码(来源于<算法图解>一书): def binary_search( ...

  8. python 全栈开发,Day15(递归函数,二分查找法)

    一.递归函数 江湖上流传这这样一句话叫做:人理解循环,神理解递归.所以你可别小看了递归函数,很多人被拦在大神的门槛外这么多年,就是因为没能领悟递归的真谛. 递归函数:在一个函数里执行再调用这个函数本身 ...

  9. C# 快速排序--二分查找法--拉格朗日插值法

    1.快速排序  参考自: https://www.cnblogs.com/yundan/p/4022056.html namespace 快速排序算法 { class Program { static ...

  10. 二分查找法(binary search)

    二分查找法:一种在有序列表中查找某个值的算法,它每次都将待查找的空间分为两半,在其中一般继续查找. 使用二分查找的前提是:已经排序好的列表.否则,sum对其查找的结果不做保证. 代码实现: // 使用 ...

随机推荐

  1. iOS安装包瘦身总结

    前段时间APP要做资源压缩,需要把项目中使用的所有图片资源进行压缩,以减小APP安装包体积.想着既然压缩APP资源是为了缩小APP体积,那么来一遍APP整体瘦身流程并做一下总结吧. 整个过程分三步: ...

  2. KMP 学习笔记

    前言-- \(char\) 与 \(string\) 有的时候 \(char\) 数组确实比 \(string\) 好用,且字符串长度很大时 \(string\) 会被卡掉,所以不要犯懒,老实用 \( ...

  3. C语言程序设计之字符串处理

    C语言程序设计-字符串处理 第一题:回文数判断 问题描述] 回文是正读和倒读都一样的句子.读入一个最大长度不超过50个字符的句子,判断其是否是回文. [输入形式] 输入一个最大长度不超过50个字符的句 ...

  4. IDEA 2024.1:Spring支持增强、GitHub Action支持增强、更新HTTP Client等

    有段时间没有更新IDEA了,早上看到 IntelliJ IDEA 2024.1 EAP 5发布的邮件提示,瞄了一眼,发现真的是越来越强了,其中不少功能对我来说还是非常有用的.也许这些能力对关注DD的小 ...

  5. B3610 [图论与代数结构 801] 无向图的块 题解

    题目传送门 前言 本题解内容均摘自我的 Tarjan 学习笔记 . 解法 Tarjan 与无向图 无向图与割点(割顶) 在一个无向图中,不存在横叉边(因为边是双向的). 一个无向图中,可能不止存在一个 ...

  6. JS Leetcode 26. 删除有序数组中的重复项 题解分析,字典与快慢双指针

    壹 ❀ 引 本题来自LeetCode26. 删除有序数组中的重复项,是一道简单题,题目描述如下: 给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组 ...

  7. NC24858 [USACO 2009 Nov S]Job Hunt

    题目链接 题目 题目描述 Bessie is running out of money and is searching for jobs. Farmer John knows this and wa ...

  8. Buffer Queue原理

    BufferQueue详解 原理一.BufferQueue 简介在工作中,我们经常会和Surface,ImageReader,ImageWriter BufferQueue打交道,它们之间是什么关系呢 ...

  9. Python异步编程原理篇之IO多路复用模块selector

    selector 简介 selector 是一个实现了IO复用模型的python包,实现了IO多路复用模型的 select.poll 和 epoll 等函数. 它允许程序同时监听多个文件描述符(例如套 ...

  10. docker基于commit方式为镜像添加SSH服务

    下载启动镜像 docker pull ubuntu: 18.04 docker run -it ubuntu:18.04 bash 配置软件源 备份/etc/apt/sources.list#备份cp ...