使用二进制搜索的多个键的最后索引?
Posted
技术标签:
【中文标题】使用二进制搜索的多个键的最后索引?【英文标题】:Last index of multiple keys using binary-search? 【发布时间】:2013-01-03 03:16:29 【问题描述】:我在排序数组中有多次出现的键,我想对它们执行二进制搜索,正常的二进制搜索会为多次出现的键返回一些随机索引,而我想要最后一次出现的索引那个键。
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
【问题讨论】:
Java和C++是不同的语言,你对哪一种感兴趣?int data[] = [1,2,3,4,4,4,4,5,5,6,6];
在 Java 中是否正确?我不这么认为。
对于C++,有很多standard algorithms你可以试试。
一般同样的问题:first-occurrence-in-a-binary-search
是的,它的java语法,最好是java
【参考方案1】:
this answer 中的 Java 实现找到键的第一个项。有一个 comment 关于如何更改它以找到 last 的出现,但该建议会导致无限循环。不过,这个想法似乎很合理。
编辑:经过一番研究,我找到了neat solution on 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)
的复杂性。不过,实际性能取决于应用程序。当数组的长度远大于所寻找键的重复数量时,对最后一次出现的线性搜索可能会更快。但是当有很多重复时,这种修改后的二分搜索可能更可取。
【讨论】:
+1 的想法,对最后一行代码的轻微修改可以返回所谓的插入点:return (found != - 1) ?找到:-(low + 1);【参考方案2】:大概你想要一个 O(log N) 解决方案? (否则你可以做一个线性搜索。)
在 C++ 中,一种可能性(在几种可能性中)是使用std::upper_bound。这将为您提供一个大于您要求的第一个元素的迭代器,因此您需要检查前一个元素。这确实是 O(log N)。
我不知道 Java 是否提供了这个标准库方法。但是,upper_bound
的伪代码在上面的链接中给出,应该很容易重新实现。
【讨论】:
是的,我尝试在 java 中转换该上界,但它总是给我上界索引+1,并且我的转换有一些问题,无论如何我在数组中有 10K 条目,我想要具有多次出现的相同键的上下索引,具有比线性更好的解决方案,二进制对我来说可以正常工作,因为它为我提供了至少 O(log2N) 解决方案【参考方案3】:好吧,特别感谢@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;
这是第一次出现,我也更新了另一个类似的帖子First occurrence in a binary search
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;
【讨论】:
所以基本上,你拿了Bentley's method from that article 并稍微重写它以找到最后一次出现而不是第一次出现?干得好,虽然我必须同意那篇文章的作者:我还发现该算法更棘手,更难理解。例如,我不知道何时以及如何最终检查“未找到密钥”。 是的,我从宾利找到了较低索引的算法,我只是稍微修改它以适应我的需要。是的,它有点棘手,在降低索引的情况下,它将最后一个匹配的值存储在循环中的高变量中,但最后它确保最后一个存储值实际上是匹配的,因为它在两种情况下都存储 >= @MattiasBuelens 需要进行最终检查。想想数组为空的情况。 当 x = [2, 3] 时,似乎 upperBound(x, 0, 1, 3) 失败了,不是吗?见:ideone.com/qoYsfB @DominicFarolino 实际上这里使用的变量名称令人困惑,它实际上不是这里数组的真正索引,而是这些是从 1 开始的下限和等于元素数量的上限在数组中。请参阅ideone.com/Y9zoxd 无论如何我在这里更新名称以避免误解。谢谢。【参考方案4】:当你找到钥匙时。而不是返回它对数组进行顺序搜索以获取最后一个。这将是 O(N) 解决方案。
【讨论】:
您想要的复杂度是多少?你可以在里面玩。让我知道你的限制 @RizwanYasin 这是一个不错的解决方案。复杂度不是 O(n),而是 O(log n + k)【参考方案5】:这是一个递归版本的二分搜索。 稍微调整一下这个版本将为您提供最后一个索引或第一个索引,而零工作量和相同的复杂度 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);
【讨论】:
【参考方案6】:在二分搜索中,您将键与数组 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;
【讨论】:
这是行不通的,甚至在大多数搜索框架中都不可能。以上是关于使用二进制搜索的多个键的最后索引?的主要内容,如果未能解决你的问题,请参考以下文章