我在排序数组中有多次出现的键,并且我想对它们执行二进制搜索,正常的二进制搜索会为多次出现的键返回一些随机索引,其中我想要该键最后一次出现的索引.
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 实现会找到第一次出现的键。有一条关于如何更改它以找到最后一次出现的评论,但建议会导致无限循环。不过,这个想法似乎很合理。
编辑:经过一番研究,我在 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)
复杂性。不过,实际性能取决于应用程序。当数组的长度远大于所寻找的键的重复数量时,对最后一次出现的线性搜索可能会更快。但是,当有很多重复项时,这种修改后的二进制搜索可能更可取。
大概你想要一个O(log N)解决方案?(否则你可以做一个线性搜索。)
在 C++ 中,一种可能性(在几种可能性中)是使用std::upper_bound。这将为您提供一个大于您要求的第一个元素的迭代器,因此您需要检查前一个元素。这确实是O(log N)。
我不知道 Java 是否提供了这个标准库方法。但是,上面的链接中给出了伪代码upper_bound
,并且应该很容易重新实现。
好吧,感谢所有人,尤其是@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;
}
当你找到钥匙时。而不是返回它对数组进行顺序搜索以获取最后一个。这将是 O(N) 解决方案。
这是二进制搜索的递归版本。稍微调整一下这个版本会给你最后一个索引或第一个索引,零努力和相同的复杂度 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);
}
在二进制搜索中,您将您的键与数组 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;
}