二分查找细则讨论

二分查找细则讨论

         二分查找有两种实现方式:非递归和递归。我们首先给出非递归的实现,然后对其中的细则进行讨论。之后,我们再讨论递归实现的细则。

一、非递归实现

         这里我们假设待查找序列是有序且互异的。

#include <iostream>
#include <vector>
using namespace std;

void nonrec_binary(const vector<int>& arr, int n, int& pos)
{
    pos = -1;
    assert(arr.size() > 0);
    int left = 0, right = arr.size() - 1, middle = 0;
    while (left <= right) // 细则1
    {
        middle = (left + right) / 2; // 细则2
        if (n == arr[middle])
        {
            pos = middle;
            break;
        }
        else if (n > arr[middle])
        {
            left = middle + 1; // 细则3
        }
        else
        {
            right = middle - 1; // 细则4
        }
    }
    return;
}

int main()
{
    int a[] = {2, 4, 5, 8, 9, 10, 13, 16, 18, 19};
    vector<int> arr(a, a+sizeof (a) / sizeof (*a));
    for (vector<int>::size_type i = 0; i != arr.size(); ++i)
    {
        cout << arr[i] << ' ';
    }
    cout << endl;
    int n = 9, pos = -1;
    nonrec_binary(arr, n, pos);
    cout << n << ':' << pos << endl;

    n = 12, pos = -1;
    nonrec_binary(arr, n, pos);
    cout << n << ':' << pos << endl;
    
    system("PAUSE");
    return 0;
}

         细则讨论:

1) 细则1

while (left <= right)

         这里是 left <= right,而不是 left < right。举个例子,如果序列中只有一个元素,则left=0,right=0,如果n=arr[0],二分查找的结果pos应为0,但是如果是while(left < right)则无法进入循环,程序运行结果pos为-1,导致bug。

2) 细则2

middle = (left + right) / 2;

         left、right、middle均为int型,所以除法/操作得到的数只是取整,如果left=0,right=9,则middle为4,而非4.5。

3) 细则3

left = middle + 1;

         if (n > arr[middle])则left = middle + 1; 而不是 left = middle。因为,这是根据细则2讨论的整形除法,如果arr为{3, 5},待查找元素为5,正常来说结果pos应为为1。但是如果left=middle;,在每一次循环中,left一直为0,middle一直为0,所以导致了死循环,最终程序异常终止。

4) 细则4

right = middle – 1

if (n < arr[middle])则right = middle – 1, 而不是 right = middle。细则4与细则3并不一样,如果写成right=middle并不程序的运行效果,因为整数整除对细则4的影响并不像细则3那么严重。但是由于 n < arr[middle] 了,虽然 right = middle; 可以照常工作,arr[middle] 已可以不在考虑范围内,所以最好写为 right = middle – 1,这样既可以提高效率,也可以与细则3表现一致。

5) 细则5

程序开头的部分并没有检测待查找元素n与arr[0]、arr[arr.size()-1]的大小关系,如果进行检查,如果n < arr[0] || n > arr[arr.size()-1] 则返回 pos = -1,如果 n == arr[0],则 pos = 0,如果 n == arr[arr.size() - 1],则 pos = arr.size() – 1。如果都不符合上面的条件,则进入 while 循环。

其实没必要进行前面的检查,因为不管前面检查与否,while 循环都会考虑到前面的所有因素,细则1和 if (n == arr[middle]) break; 会涵盖n < arr[0] || n > arr[arr.size()-1]的情况,细则2会涵盖n == arr[arr.size() - 1],则 pos = arr.size() – 1的情况。

在有先验知识的情况下,可以决定是否要加上前面的判断情况:如果进行大量查找的情况下,且n不经常出现于序列中或n主要为最大或最小元素时,则进行前面的判断可以提高一定的效率,否则没有必要加上这些判断条件。

二、递归实现

#include <iostream>
#include <vector>
using namespace std;

void rec_binary(const vector<int>& arr, int left, int right, int middle, int n, int& pos) // 细则1
{
    if (left > right) // 细则2
    {
        pos = -1;
        return;
    }
    else if (n == arr[middle])
    {
        pos = middle;
        return;
    }
    else if (n > arr[middle])
    {
        rec_binary(arr, middle+1, right, (middle+1+right)/2, n, pos); // 细则3
    }
    else
    {
        rec_binary(arr, left, middle-1, (left+middle-1)/2, n, pos); // 细则4
    }
}

int main()
{
    int a[] = {2, 4, 5, 8, 9, 10, 13, 16, 18, 19};
    vector<int> arr(a, a+sizeof (a) / sizeof (*a));
    for (vector<int>::size_type i = 0; i != arr.size(); ++i)
    {
        cout << arr[i] << ' ';
    }
    cout << endl;
    int n = 9, pos = -1;
    rec_binary(arr, 0, arr.size()-1, (0+arr.size()-1)/2, n, pos); // 细则5
    cout << n << ':' << pos << endl;

    n = 12, pos = -1;
    rec_binary(arr, 0, arr.size()-1, (0+arr.size()-1)/2, n, pos);
    cout << n << ':' << pos << endl;
    
    system("PAUSE");
    return 0;
}

         递归的实现,关键在于递归结构的定义,以及终止条件以及递归公式。

1)  细则1

void rec_binary(const vector<int>& arr, int left, int right, int middle, int n, int& pos)

         这里需要有 left 和 right 作为查找序列的边界,递归的意义也就在于对 left 和 right 上下边界的操作。

         middle 参数可有可无,这里在函数定义的时候作为了参数,也可以在函数内部有 left 和 right 得到。

2)  细则2

细则2和下面的 n == arr[middle] 作为递归函数的终止条件。

3)  细则3、4

递归函数的内部调用——递归公式。

4)  细则5

递归函数的外部调用。

rec_binary(arr, 0, arr.size()-1, (0+arr.size()-1)/2, n, pos);

         附:递归的另一个实现

void rec_binary_2(const vector<int>& arr, int left, int right, int n, int& pos)

#include <iostream>
#include <vector>
using namespace std;

void rec_binary_2(const vector<int>& arr, int left, int right, int n, int& pos)
{
    int middle = (left + right) / 2;
    if (left > right)
    {
        pos = -1;
        return;
    }
    else if (n == arr[middle])
    {
        pos = middle;
        return;
    }
    else if (n > arr[middle])
    {
        rec_binary_2(arr, middle+1, right, n, pos);
    }
    else
    {
        rec_binary_2(arr, left, middle-1, n, pos);
    }
}

int main()
{
    int a[] = {2, 4, 5, 8, 9, 10, 13, 16, 18, 19};
    vector<int> arr(a, a+sizeof (a) / sizeof (*a));
    for (vector<int>::size_type i = 0; i != arr.size(); ++i)
    {
        cout << arr[i] << ' ';
    }
    cout << endl;
    int n = 9, pos = -1;
    rec_binary_2(arr, 0, arr.size()-1, n, pos);
    cout << n << ':' << pos << endl;

    n = 12, pos = -1;
    rec_binary_2(arr, 0, arr.size()-1, n, pos);
    cout << n << ':' << pos << endl;
    
    system("PAUSE");
    return 0;
}

 

原文地址:https://www.cnblogs.com/unixfy/p/3151446.html