2

我在排序数组中有多次出现的键,并且我想对它们执行二进制搜索,正常的二进制搜索会为多次出现的键返回一些随机索引,其中我想要该键最后一次出现的索引.

int data[] = [1,2,3,4,4,4,4,5,5,6,6];
int key = 4;
int index = upperBoundBinarySearch(data, 0, data.length-1, key);

Index Returned = 6
4

6 回答 6

9

此答案中的 Java 实现会找到第一次出现的键。有一条关于如何更改它以找到最后一次出现的评论,但建议会导致无限循环。不过,这个想法似乎很合理。

编辑:经过一番研究,我在 The Algo Blog 上找到了一个简洁的解决方案。由于第一个找到的匹配不一定是需要的,因此您需要跟踪迄今为止的“最佳”匹配。当你得到一个匹配时,你存储它并继续在匹配的右边进行二分搜索(low = mid + 1)。

public static int binarySearch(int[] a, int key) {
    return binarySearch(a, 0, a.length, key);
}

private static int binarySearch(int[] a, int fromIndex, int toIndex,
        int key) {
    int low = fromIndex;
    int high = toIndex - 1;
    int found = -1;

    while (low <= high) {
        int mid = (low + high) >>> 1;
        int midVal = a[mid];

        if (midVal < key) {
            low = mid + 1;
        } else if (midVal > key) {
            high = mid - 1;
        } else {
            found = mid;
            // For last occurrence:
            low = mid + 1;
            // For first occurrence:
            // high = mid - 1;
        }
    }
    return found;
}

这种变化保持了O(log n)复杂性。不过,实际性能取决于应用程序。当数组的长度远大于所寻找的键的重复数量时,对最后一次出现的线性搜索可能会更快。但是,当有很多重复项时,这种修改后的二进制搜索可能更可取。

于 2013-01-19T14:58:44.707 回答
2

大概你想要一个O(log N)解决方案?(否则你可以做一个线性搜索。)

在 C++ 中,一种可能性(在几种可能性中)是使用std::upper_bound。这将为您提供一个大于您要求的第一个元素的迭代器,因此您需要检查前一个元素。这确实是O(log N)

我不知道 Java 是否提供了这个标准库方法。但是,上面的链接中给出了伪代码upper_bound,并且应该很容易重新实现。

于 2013-01-19T14:42:47.513 回答
1

好吧,感谢所有人,尤其是@Mattias,该算法听起来不错。无论如何,我已经完成了自己的工作,这似乎我给出了更好的结果,但是如果有人可以帮助我衡量我的算法和@Mattias 的复杂性,或者任何人有更好的解决方案,欢迎...... .无论如何,这是我为这个问题找到的解决方案,

int upperBound(int[] array,int lo, int hi, int key)
{
    int low = lo-1, high = hi;
    while (low+1 != high)
    {
        int mid = (low+high)>>>1;
        if (array[mid]> key) high=mid;
        else low=mid;
    }
    int p = low;
    if ( p >= hi || array[p] != key )
        p=-1;//no key found
    return p;
}

这是第一次出现,我也用另一个类似的帖子更新相同的二分搜索中的第一次出现

int lowerBound(int[] array,int lo, int hi, int key)
{
    int low = lo-1, high = hi;
    while (low+1 != high)
    {
        int mid = (low+high)>>>1;
        if (array[mid]< key) low=mid;
        else high=mid;
    }
    int p = high;
    if ( p >= hi || array[p] != key )
        p=-1;//no key found
    return p;
}
于 2013-01-19T20:04:26.713 回答
0

当你找到钥匙时。而不是返回它对数组进行顺序搜索以获取最后一个。这将是 O(N) 解决方案。

于 2013-01-19T15:02:14.260 回答
0

这是二进制搜索的递归版本。稍微调整一下这个版本会给你最后一个索引或第一个索引,零努力和相同的复杂度 O(log-n)。

原始的二分查找递归版本如下所示:

public static int binarySearch(List<Integer> a, int startIndex, int endIndex, int key) {
    int midIndex = (endIndex - startIndex)/2 + startIndex;
    if (a.get(midIndex) == key) // found!
        return midIndex;
    if (startIndex == endIndex || startIndex == endIndex - 1)
        return -1;
    else if (a.get(midIndex) > key) // Search in the left
        return binarySearch(a, 0, midIndex, key); 
    else if (a.get(midIndex) < key) // Search in the right
        return binarySearch(a, midIndex, endIndex, key);
    else
        return -1; // not found 
}

对第一个 if 语句稍作改动,就可以得到第一个索引:

public static int binarySearchLowIndex(List<Integer> a, int startIndex, int endIndex, int key) {
    int midIndex = (endIndex - startIndex)/2 + startIndex;
    if (a.get(midIndex) == key && a.get(midIndex - 1) != key) // found!
        return midIndex;
    if (startIndex == endIndex || startIndex == endIndex - 1)
        return -1;
    else if (a.get(midIndex) >= key) // Search in the left
        return binarySearchLowIndex(a, 0, midIndex, key); 
    else if (a.get(midIndex) < key) // Search in the right
        return binarySearchLowIndex(a, midIndex, endIndex, key);
    else
        return -1; // not found 
}

最后一个索引也是如此:

public static int binarySearchHighIndex(List<Integer> a, int startIndex, int endIndex, int key) {
    int midIndex = (endIndex - startIndex)/2 + startIndex;
    if (a.get(midIndex) == key **&& a.get(midIndex + 1) != key**) // found!
        return midIndex;
    if (startIndex == endIndex || startIndex == endIndex - 1)
        return -1;
    else if (a.get(midIndex) > key) // Search in the left
        return binarySearchHighIndex(a, 0, midIndex, key); 
    else if (a.get(midIndex) <= key) // Search in the right
        return binarySearchHighIndex(a, midIndex, endIndex, key);
    else
        return -1; // not found 
}

以下是一些测试示例(基于 Junit):

@Test
public void binarySearchTest() {
    assert(BinarySearch.binarySearch(Arrays.asList(5, 7, 7, 8, 8, 10), 0, 5, 5) == 0);
}

@Test
public void binarySearchLowIndexTest() {
    assert(BinarySearch.binarySearchLowIndex(Arrays.asList(5, 8, 8, 8, 8, 10), 0, 5, 8) == 1);
}

@Test
public void binarySearchHighIndexTest() {
    assert(BinarySearch.binarySearchHighIndex(Arrays.asList(5, 8, 8, 8, 8, 10), 0, 5, 8) == 4);
}
于 2019-05-15T09:46:47.263 回答
-2

在二进制搜索中,您将您的键与数组 data[i] 的元素进行比较。要获得最后一个匹配的索引,您应该更改比较函数,以便即使 key 等于 data[i] 也等于 data[i+1],它也会给出不等式。

int upperBoundBinarySearch(int data[],int start, int end, int key) {
  while(start < end) {
    int middle = start + (end-start)/2;
    if (data[middle] == key && (middle == end || data[middle+1] != key))
      return middle;
    if (data[middle] > key)
      end = middle;
    else {
      if (start == middle)
        return start;
      start = middle;
    }
  }
  return start;
}
于 2013-01-19T14:52:00.587 回答