二分查找

阅读数:61 评论数:0

跳转到新版页面

分类

算法/数据结构

正文

用伪代码来表示, 二分查找算法大致是这个样子的:

left = 0, right = n -1
while (left <= right)
    mid = (left + right) / 2
    case
        x[mid] < t:    left = mid + 1;
        x[mid] = t:    p = mid; break;
        x[mid] > t:    right = mid -1;

return -1;

二分查找算法的边界,一般来说分两种情况,一种是左闭右开区间,类似于[left, right),一种是左闭右闭区间,类似于[left, right].需要注意的是, 循环体外的初始化条件,与循环体内的迭代步骤, 都必须遵守一致的区间规则,也就是说,如果循环体初始化时,是以左闭右开区间为边界的,那么循环体内部的迭代也应该如此.如果两者不一致,会造成程序的错误.比如下面就是错误的二分查找算法:

int search_bad(int array[], int n, int v)
{
    int left, right, middle;

    left = 0, right = n;

    while (left < right)
    {
        middle = (left + right) / 2;
        if (array[middle] > v)
        {
            right = middle - 1;
        }
        else if (array[middle] < v)
        {
            left = middle + 1;
        }
        else
        {
            return middle;
        }
    }

    return -1;
}

这个算法的错误在于, 在循环初始化的时候,初始化right=n,也就是采用的是左闭右开区间,而当满足array[middle] > v的条件是, v如果存在的话应该在[left, middle)区间中,但是这里却把right赋值为middle - 1了,这样,如果恰巧middle-1就是查找的元素,那么就会找不到这个元素.(下面这两个算法是正确的

// 左闭右闭
int search2(int array[], int n, int v)
{
    int left, right, middle;

    left = 0, right = n - 1;

    while (left <= right)
    {
        middle = (left + right) / 2;
        if (array[middle] > v)
        {
            right = middle - 1;
        }
        else if (array[middle] < v)
        {
            left = middle + 1;
        }
        else
        {
            return middle;
        }
    }

    return -1;
}

// 左闭右开
int search3(int array[], int n, int v)
{
    int left, right, middle;

    left = 0, right = n;

    while (left < right)
    {
        middle = (left + right) / 2;

        if (array[middle] > v)
        {
            right = middle;
        }
        else if (array[middle] < v)
        {
            left = middle + 1;
        }
        else
        {
            return middle;
        }
    }

    return -1;
}

 

假如,left与right之和超过了所在类型的表示范围的话,那么middle就不会得到正确的值.
所以,更稳妥的做法应该是这样的:

middle = left + (right - left) / 2;

更完善的算法
前面我们说了,给出的第一个算法是一个"正确"的程序, 但是还有一些小的问题.
首先, 如果序列中有多个相同的元素时,查找的时候不见得每次都会返回第一个元素的位置, 比如考虑一种极端情况:序列中都只有一个相同的元素,那么去查找这个元素时,显然返回的是中间元素的位置.
其次, 前面给出的算法中,每次循环体中都有三次情况,两次比较,有没有办法减少比较的数量进一步的优化程序?
<<编程珠玑>>中给出了解决这两个问题的算法,结合前面提到溢出问题我对middle的计算也做了修改:

int search4(int array[], int n, int v)
{
    int left, right, middle;

    left = -1, right = n;

    while (left + 1 != right)//这个循环维持的条件是left<right && array[left]<v<=array[right],所以到最后的时候,
    {//如果可以找到目标,则只剩下两个数,并且满足 array[left]<v<=array[right],是要查找的数是right
        middle = left + (right - left) / 2;

        if (array[middle] < v)//必须保证array[left]<v<=array[right],所以left = middle;
        {//如果left =middle+1,则有可能出现 array[left]<=v的情况
            left = middle;
        }
        else
        {
            right = middle;
        }
    }

    if (right >= n || array[right] != v)
    {
        right = -1;
    }

    return right;
}



相关推荐

一、介绍 1、二分图 其最大的特点在于,可以将图里的顶点分为两个集合,且集合内的点没有直接关联。 2、增广路径 增广路径是指,由一个未匹配的顶点开始,经过若干个匹配顶点,最后到达对面集合的一个未匹配

0、这里假设有人要从A点去往B点。 1、找到与起始点相邻的所有可到达方块,<span style="col

MD5,即“Message-Digest Algorithm 5”(信息-摘要算法),从名字来看就知道它是从MD3、MD4发展而来的一种加密算法,其主要通过采集文件的信息摘要,从此进行计算并加密。 M

概念 Base54算法使用了单表置换算法的思想,Base64使用一个由64个字符组成的映射表。 实现原理 1、将给定的字符串转换成对应的字符编码

deflate是同时使用lz77算法与huffman编码的一个无损压缩算法。人们普遍认为deflate不受任何专利所制约,deflate压缩与解压缩的源代码可以在开源的zlib库中找到。

一、密码学的常见概念 1、代替密码 代替密码是指先建立一个替换表,加密时通过查表,将明文的每个字母依次替换为对应的字符,生成密文。 按替换表的个数又细分为: (1)单表代替密码 即一个明文字符对应一个

零-概述 ECB模式和CBC模式是最常用的两种模式。 1、需要块对齐的模式:ECB、CBC、PCBC 2、需要初始向量的模式:除了ECB模式外,都需要初始向量。 一、ECB,电子密码本 Electro

一、概述 1、图的表示法 图G = (V, E)的表示有邻接表和邻接矩阵两种形式。通常,对于| E |远小于| V | ^ 2的稀疏图,用邻接表比较合适。若图稠密或很小,或必须很快判别给定的两个顶点是

单模式VS多模式 (1)单模式 单模就是给你一个单词,然后给你一个字符串,问你这个单词是否在这个字符串中出现过(匹配) (2)多模式 给你

字符串的匹配包括蛮力匹配算法,KMP算法,BM算法,Horspool算法,Sunday算法,fastsearch算法,KR算法等等。<