二分查找中的死循环

二分算法是我们经常会用到的一个算法。它是分治法的一个应用。不过,虽然他写起来貌似很简单,但是却很容易写错。下面我们讨论一下二分的死循环问题。(这里讨论的是整数的二分问题,浮点数的二分不容易死循环)

1.查找的元素确定,值唯一或者不存在

这种情况等下,我们的流程分为三个分支:(相等、小于、大于)。这类不容易死循环,代码如下:

if ( data[mid] == key )
    return mid;
if ( data[mid] > key )
    r = mid-1;
else l = mid+1;
2.被查元素不确定,值可能有多个,找到第一个或者最后一个

这是最容易出现死循环的情况,也是本文讨论的核心。这种情况下,流程分成两个分支,我们分两种情况讨论:

a.取第一个小于key的元素:

if ( data[mid] >= key )
    r = mid-1;
else l = mid;

我们看式子 mid = (l+r)/2

如果(l+r)为奇数,则

mid = (l+r)/2 = (l+r-1)/2 导出 2*mid = (l+r-1)/2*2 = l+r-1

这时,若 mid = l 则“else l = mid;”这句代码就会就会进入死循环。

所以这时使用 mid = (l+r+1)/2 代替 mid = (l+r+1)/2 就不会死循环了。

如果(l+r+1)为偶数,则

mid = (l+r+1)/2 = (l+r)/2 导出 2*mid = (l+r)/2*2 = l+r 不会出现问题。

(这时使用 mid = (l+r)/2 也不会死循环)

综上,这种情况下使用 mid = (l+r+1)/2 就不会死循环了,不过这不是通用式子,看b情况。

int bs( int l, int r, int key )
{
	while ( l < r ) {
		int mid = (l+r+1)/2;
		if ( data[mid] >= key )
			r = mid-1;
		else l = mid;
	}
        return l;
}

b.取第一个大于key的元素:

if ( data[mid] <= key )
    l = mid+1;
else r = mid;

我们看式子 mid = (l+r+1)/2

如果(l+r+1)为奇数,则

mid = (l+r+1)/2 = 导出 2*mid = (l+r+1)/2*2 = l+r+1

这时,若 mid = r 则“else r = mid;”这句代码就会就会进入死循环。

所以这时要使用 mid = (l+r)/2 代替 mid = (l+r+1)/2 才不会死循环了。

如果(l+r)为偶数,则

mid = (l+r)/2 导出 2*mid = (l+r)/2*2 = l+r不会出现问题。

(这时使用 mid = (l+r+1)/2 也不会死循环)

综上,这种情况下使用 mid = (l+r)/2 就不会死循环了。

int bs( int l, int r, int key )
{
	while ( l < r ) {
		int mid = (l+r)/2;
		if ( data[mid] <= key )
			l = mid+1;
		else r = mid;
	}
        return r;
}

c.综合a、b得到结论取中值的计算方式与判断条件有关,下面加入一个小优化。

3.一步小优化,防止溢出

这里使用 mid = l+(r-l)/2 代替 mid = (l+r)/2 以及 mid = l+(r-l+1)/2
代替 mid = (l+r+1)/2。这样可以防止l+r和l+r+1溢出。下面证明两者的等价性。

a.l+r为奇数,则r-l为奇数,r-l+1为偶数

mid = l+(r-l+1)/2 = l*2/2 + (r-l+1)/2 = (l+r+1)/2

mid = l+(r-l)/2 = l*2/2 + (r-l-1)/2 = (r+l-1)/2 = (r+l)/2

b.l+r为偶数,则r-l为偶数,r-l+1为奇数

mid = l+(r-l+1)/2 = l*2/2 + (r-l)/2 =(l+r)/2 = (l+r+1)/2

mid = l+(r-l)/2 = l*2/2 + (r-l)/2 = (l+r)/2

c.综上所述上述替代成立。

时间: 2024-10-25 13:28:52

二分查找中的死循环的相关文章

关于二分查找中的一些问题

相信对于学习编程每一个同学来说,肯定都知道二分查找算法,并且写过相应的测试代码,那么我们先来说一下二分查找的的优缺点吧. 这大家都很清楚,优点呢就是二分查找是折半查找,每次查找都可以排除一半的数据,这应用在一个大数据量的查找中,效率是非常高的.当然了,缺点也很明显,就是二分查找的前提是,查找的数据一定是经过排序的,无论是升序还是降序,这会打乱原先的数据的位置.所以在实际情况中,还请大家仔细考虑,是否需要进行二分查找. 话不多说,我们直接上代码. public class Main{ public

HDOJ2141(map在二分查找中的应用)

#include<iostream> #include<cstdio> #include<map> #include<algorithm> using namespace std ; #define M 500 + 10 int a[M] ; int b[M] ; int c[M] ; int d[M] ; int l ; int n ; int m ; map <int , bool > Map ; void Marge() { for(int

二分查找 : 那个隐藏了 10 年的 Java Bug

一个偶然的机会,我想起以前还在谷歌上班的时候,有时候大家会在饭桌上讨论最新想出来的一些面试题.在众多有趣又有难度的题目中,有一道老题却是大家都纷纷选择避开的,那就是去实现二分查找. 因为它很好写,却很难写对.可以想象问了这道题后,在5分钟之内面试的同学会相当自信的将那一小段代码交给我们,剩下的就是考验面试官能否在更短的时间内看出这段代码的bug了. 二分查找是什么呢,这个不只程序员,其他很多非技术人员也会.比如我想一个1到100以内的数,你来猜,我告诉你每次猜的是大了还是小了,你会先猜50,然后

二分查找的改进--差值查找

差值查找 在二分查找中,我们每次比较都可以排除一半的数据量,这个已经是很高效了.如果利用关键字本身的信息,每次排除的数据量充分依赖于关键字的大小,则查找会更高效,这就是差值查找的思想. 下面通过示例代码,比较二分查找和差值查找的不同,在不同中领略差值查找的改良之处. #include <stdio.h> #include <stdlib.h> int InterSearch(int *array, int n, int key) { if (array && n &

检索算法——二分查找

如果要查找的数据是有序的, 二分查找算法比顺序查找算法更高效. function insertionSort(arr) { var temp, inner; for ( var outer = 1; outer < arr.length; ++outer) { temp = arr[outer]; //选中一个值作为临时值,使得其前面的数依次与它进行比较 inner = outer; while (inner > 0 && (arr[inner - 1] >= temp)

二分查找算法(递归,循环)

二分查找算法是在有序数组中用到的较为频繁的一种算法,在未接触二分查找算法时,最通用的一种做法是,对数组进行遍历,跟每个元素进行比较,其时间为O(n).但二分查找算法则更优,因为其查找时间为O(lgn),譬如数组{1, 2, 3, 4, 5, 6, 7, 8, 9},查找元素6,用二分查找的算法执行的话,其顺序为:    1.第一步查找中间元素,即5,由于5<6,则6必然在5之后的数组元素中,那么就在{6, 7, 8, 9}中查找,    2.寻找{6, 7, 8, 9}的中位数,为7,7>6,

详解二分查找算法

我周围的人几乎都认为二分查找很简单,但事实真的如此吗?二分查找真的很简单吗?并不简单.看看 Knuth 大佬(发明 KMP 算法的那位)怎么说的: Although the basic idea of binary search is comparatively straightforward, the details can be surprisingly tricky... 这句话可以这样理解:思路很简单,细节是魔鬼. 本文就来探究几个最常用的二分查找场景:寻找一个数.寻找左侧边界.寻找右侧

(二分查找思想)从有序递增旋转数组45679123 中找到数字6的位置

#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std; /** * 从有序递增旋转数组45679123 中找到数字6的位置 * 数组递增 但有旋转 * 二分查找思想 * 时间复杂度小于O(N) * {7,8,9,10,1,2,3,4,5,6} *************/ int find_revolve_array(const int arr[], int len, int value) { if

STL中的二分查找———lower_bound,upper_bound,binary_search

关于STL中的排序和检索,排序一般用sort函数即可,今天来整理一下检索中常用的函数——lower_bound , upper_bound 和 binary_search . STL中关于二分查找的函数有三个lower_bound .upper_bound .binary_search .这三个函数都运用于有序区间(当然这也是运用二分查找的前提). Tips:1.在检索前,应该用sort函数对数组进行从小到大排序.     2.使用以上函数时必须包含头文件:#include < algorith