算法概述

二分查找(英语: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. LyScript 通过PEB结构解析堆基址

    LyScript中默认并没有提供获取进程堆基址的函数,不过却提供了获取PEB/TEB的函数,以PEB获取为例,可以调用dbg.get_peb_address(local_pid)用户传入当前进程的PI ...

  2. WebAssembly入门笔记[4]:利用Global传递全局变量

    利用WebAssembly的导入导出功能可以灵活地实现宿主JavaScript程序与加载的单个wasm模块之间的交互,那么如何在宿主程序与多个wasm之间传递和共享数据呢?这就需要使用到Global这 ...

  3. 内存池是什么原理?|内存池简易模拟实现|为学习高并发内存池tcmalloc做准备

    前言 那么这里博主先安利一些干货满满的专栏了! 这两个都是博主在学习Linux操作系统过程中的记录,希望对大家的学习有帮助! 操作系统Operating Syshttps://blog.csdn.ne ...

  4. 解决Python报错SSLError,如果试了网上一大堆方法还不行,看看这个吧!!

    前言 这个问题困扰了群友一天,我怀着好奇心去试试看,不到5分钟给解决了哈哈. 报错代码 报错代码中对相关的host和url进行了替换,大家在网上发布内容也要注意隐私哈,多长个心眼子总没错. reque ...

  5. Windows 10 快捷键大全|日常办公效率加倍

    ## 复制.粘贴及其他常规     Ctrl + X 剪切选定项. Ctrl + C(或 Ctrl + Insert) 复制选定项. Ctrl + V(或 Shift + Insert) 粘贴选定项. ...

  6. 选课 洛谷P2014

    传送门 \(\Large \textbf{问题描述}\) 大学里实行学分.每门课程都有一定的学分,学生只要选修了这门课并考核通过就能获得相应的学分.学生最后的学分是他选修的各门课的学分的总和. 每个学 ...

  7. JS leetcode 反转字符串中的单词 III 题解分析

    壹 ❀ 引 又到了快乐的leetcode算法时间,今天的题目特别特别简单,来自leetcode557. 反转字符串中的单词 III,题目描述如下: 给定一个字符串,你需要反转字符串中每个单词的字符顺序 ...

  8. 使用TensorFlow实现MNIST数据集分类

    1 MNIST数据集 MNIST数据集由70000张28x28像素的黑白图片组成,每一张图片都写有0~9中的一个数字,每个像素点的灰度值在0 ~ 255(0是黑色,255是白色)之间. MINST数据 ...

  9. Laravel入坑指南(2)——路由、控制器

    接上一节,我们已经把Laravel有Hello World项目跑起来了. 现在各位小友最着急的,应该是想了解,我们怎么在"页面"echo一个自己的Hello World字符串. & ...

  10. Java设计模式-中介者模式Mediator

    介绍 中介者模式(Mediator Pattern),用一个中介对象来封装一系列的对象交互.中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互. 中介者模式属于行 ...