3

问题是扩展二分搜索算法,以最有效的方式在排序数组中查找目标值的所有出现。具体来说,该算法的输入是(1)整数的排序数组,其中一些数字可能出现多次,以及(2)要搜索的目标整数。算法的输出应该是一对索引值,指示数组中整数的第一次和最后一次出现(如果确实出现的话)。源代码可以是 c#、c、c++。

此外,我们可能需要查找索引的最大和最小比较次数是多少?

4

7 回答 7

6

对于 C++,您可以查找std::equal_range()它的复杂性要求。只要您对基本算法感兴趣,就应该应用相同的通用规则,而不管实现时使用何种语言。

于 2010-02-08T04:33:53.043 回答
2

如果你有点聪明,你可以定义两个不同的二分搜索函数。一个将返回搜索值的第一次出现的索引,另一个将返回搜索值的最后一次出现。根据您对二分搜索的了解,您应该能够确定最大和最小比较次数。

在我看来,平均而言,使用两次二进制搜索应该是最快的方法。例如,如果您只使用一个二分搜索来查找第一项并随后进行线性搜索,那么最坏的情况是整个函数的值相同。对于长度为 10000 的数组,这将在最坏的情况下进行 10013 次比较,而使用两次二进制搜索在最坏的情况下对同一数组进行 28 次比较。当然,使用相同大小的数组,二分/线性搜索方法的最佳情况是 14 次比较,而两次二分搜索方法的最佳情况是 26 次比较。

** 更新

好的,这是一个二分搜索,用于查找数组中元素的第一次出现。我会给你一个递归函数(你当然可以让它迭代并以其他方式优化它)。这将在 int 数组 a 中搜索 int val。另外,我没有仔细寻找中点(如果数组真的很大,可能会出现问题)。

int bs1(int a[], int val, int left, int right)
{
    if(right == left) return left;
    int mid = (right+left)/2;

    if(val > a[mid]) return bs1(a, val, mid+1, right);
    else return bs1(a, val, left, mid);
}

但是,您应该在返回一个索引后检查它实际上引用了正确的值,因为如果 val 不在数组中,则返回的索引将对应于下一个大于 val 的元素。

对此进行一些小的更改将创建一个查找最后一个元素的函数。这样做的关键是正确使用比较器并记住整数除法总是截断。

于 2010-02-09T06:19:48.540 回答
1

这很容易做到,无需编写自己的二进制搜索算法,只需重复调用标准算法即可。

// some curly-bracket language:

// int BinarySearch(sortedList, searchIndex, searchLength, valueToFind)
// returns the zero-based index of the item in the list, or a negative value
// if the item is not found

int inner = BinarySearch(list, 0, listSize, value);
if(inner < 0){
    // handle case where value is not found in list
}

int bottom = inner, top = inner;
while(true){
    int i = BinarySearch(list, 0, bottom, value);
    if(i < 0)
        break;
    bottom = i;
}
while(true){
    int i = BinarySearch(list, top + 1, listSize - top - 1, value);
    if(i < 0)
        break;
    top = i;
}

// bottom and top now hold the bounds of all instances of value in list

这与使用自定义算法获得的效率非常接近,只是您有更多的函数调用开销。

至于比较的数量,我得想一想才能确定,但​​我认为它只是 2*log 2 N,其中 N 是列表中的项目数。


编辑

呸! 它不是 2*log 2 N,因为与您可以使用自定义算法执行的操作不同,它不会逐步排除列表的某些部分。似乎1最大比较计数为 (log 2 N - 0.5) * log 2 N。对于具有 2 30 个元素的列表,这仍然只有 885 次比较(2 20 N 为 390 次比较,2 10 N为 95 次),但我们可以做得更好。

// int Compare(a, b)
// returns 0 if a and b are equal,
//         a negative value if a < b, or
//         a positive value if a > b

int start = 0, end = listSize, inner;

while(true){
    if(end == start){
        // handle case where value is not found in list
    }
    inner = (start + end) / 2;
    int cmp = Compare(list[inner], value);
    if(cmp == 0)
        break;
    if(cmp < 0)
        start = inner + 1;
    else end = inner;
}

int top = inner, bottom = inner;

while(true){
    if(start >= bottom)
        break;
    inner = (start + bottom) / 2;
    int cmp = Compare(list[inner], value);
    if(cmp == 0)
        bottom = inner;
    else start = inner + 1;
}

while(true){
    if(end - 1 <= top)
        break;
    inner = (top + 1 + end) / 2;
    int cmp = Compare(list[inner], value);
    if(cmp == 0)
        top = inner;
    else end = inner;
}

这将最多进行 2*log 2 N 次比较。2 30项最多需要 60 次比较,2 20项最多需要 40 次比较,以此类推。


1我通过实验确定了这一点。我不够聪明,无法从数学上算出来。

于 2010-02-11T02:56:02.090 回答
1

您可以在 Bentley Programming Pearls和 Knuth 的第 3 卷:排序和搜索中找到对此的讨论。

这是 C++ 中的一个实现:http: //the-algo-blog.blogspot.com/2011/06/binary-search-to-find-last-and-first.html

于 2011-06-26T12:45:21.887 回答
0

我想正常的算法会有这样的东西:

if(value == test) return;
if(value < test) min = i;
if(value > test) max = i;

一旦您使用它来查找其中一个值,请使用您当前必须找到提示的最小值和最大值执行两个稍微模式化的二进制搜索。

要找到最上面的内容,请将上面的内容替换为:

if(value <= test) min = i;
if(value > test) max = i;

对于最底部的替换为:

if(value >= test) max = i;
if(value < test) min = i;

请注意,使用此方法不会提前返回,您只需继续直到 min 和 max 像一个或某个东西一样,我想您可以添加一个和另一个检查

if(value == test and arr[i-1] != test) return;

等等

于 2010-02-08T07:28:04.923 回答
0

对于问题中最有效的部分,没有明确的答案。这将取决于预期有多少具有相同值的条目。如果它是一些在找到一个元素后在数组的两个方向上的线性搜索将是你最快的选择,但如果你期望有很多具有相同值的条目,你可以做一种二进制搜索来找到开始结束索引。

免责声明:未经测试;它的目的是展示这个想法,而不是直接用作生产代码

int org = binarySearch(array,value) //do the binary search and find on element
int min = org-delta; //delta is some constant based on how many elemts are to be expected
int max = org;
min = min < 0 ? 0 : min;
int search= min;
bool latestWasHit = false;
while(search > 0)
{
  if(search+1 == max)
     return max;
  if(array[search] != value)
  {
     min = search;
     search = search + (max-search)/2
  }
  else
  {
     max = search;
     search = (search-min)/2;
  } 
}

然后相反的上限。然而,它需要相当多的元素才能比简单的线性搜索更快。

于 2010-02-11T17:14:01.707 回答
0

我创建了两种二进制搜索方法,分别返回第一次和最后一次出现。

public static void main(String[] args) {
    int a[] ={1,2,2,2,2,2,5,5,6,8,9,10};

    System.out.println(5+" first = "+first(a, 5, 0, a.length-1));
    System.out.println(5+" last = "+right(a, 5, 0, a.length-1));

    System.out.println(1+" first = "+first(a, 1, 0, a.length-1));
    System.out.println(1+" last = "+right(a, 1, 0, a.length-1));

    System.out.println(2+" first = "+first(a, 2, 0, a.length-1));
    System.out.println(2+" last = "+right(a, 2, 0, a.length-1));

    System.out.println(10+" first = "+first(a, 10, 0, a.length-1));
    System.out.println(10+" last = "+right(a, 10, 0, a.length-1));

    System.out.println(8+" first = "+first(a, 8, 0, a.length-1));
    System.out.println(8+" last = "+right(a, 8, 0, a.length-1));

    System.out.println(11+" first = "+first(a, 11, 0, a.length-1));
    System.out.println(11+" last = "+right(a, 11, 0, a.length-1));


}

private static int first(int [] a, int x, int l, int h){
    if(l>h){
        return -1;
    }
    int mid = (h-l)/2+l;
    if(a[mid] == x && (mid==0 || a[mid-1] != x) ){
        return mid;
    }else if(a[mid] == x){
        return first(a, x, l, mid-1);
    }else if(a[mid]>x){
        return first(a, x, l, mid-1);
    }else{
        return first(a, x, mid+1, h);
    }
}


private static int right(int [] a, int x, int l, int h){
    if(l>h){
        return -1;
    }
    int mid = (h-l)/2+l;
    if(a[mid] == x && (mid==a.length-1 || a[mid+1] != x) ){
        return mid;
    }else if(a[mid] == x){
        return right(a, x, mid+1, h);
    }else if(a[mid]>x){
        return right(a, x, l, mid-1);
    }else{
        return right(a, x, mid+1, h);
    }
}

Output:
    1 first = 0
    1 last = 0
    2 first = 1
    2 last = 5
    10 first = 11
    10 last = 11
    8 first = 9
    8 last = 9
    11 first = -1
    11 last = -1
于 2017-12-07T06:37:48.893 回答