不幸的是,其他答案(this和this)中提出的算法不正确,它们不是O(logN)!
递归公式 f(L) = f(L/2) + log(L/2) + c 不会导致 f(L) = O(log(N)) 但会导致 f(L) = O( (log(N))^2)!
事实上,假设 k = log(L),那么 log(2^(k-1)) + log(2^(k-2)) + ... + log(2^1) = log(2)*( k-1 + k-2 + ... + 1) = O(k^2)。因此,log(L/2) + log(L/4) + ... + log(2) = O((log(L)^2))。
及时解决问题的正确方法~2log(N)是这样进行的(假设数组是先升序再降序):
- 取数组的中间
- 将中间元素与其相邻元素之一进行比较,以查看最大值是在右侧还是左侧
- 将中间元素与所需值进行比较
- 如果中间元素小于期望值并且最大值在左侧,则对左侧子数组进行双调搜索(我们确定该值不在右侧子数组中)
- 如果中间元素小于期望值并且最大值在右侧,则在右侧子数组上进行双调搜索
- 如果中间元素大于期望值,则对右子数组进行降序二分查找,对左子数组执行升序二分查找。
在最后一种情况下,对可能是双调的子数组进行二分搜索可能会令人惊讶,但它确实有效,因为我们知道顺序不正确的元素都大于期望值。例如,对数组 [2, 4, 5, 6, 9, 8, 7] 中的值 5 进行升序二进制搜索将起作用,因为 7 和 8 大于所需的值 5。
这是时间~2logN的双音搜索的完整工作实现(在 C++ 中) :
#include <iostream>
using namespace std;
const int N = 10;
void descending_binary_search(int (&array) [N], int left, int right, int value)
{
// cout << "descending_binary_search: " << left << " " << right << endl;
// empty interval
if (left == right) {
return;
}
// look at the middle of the interval
int mid = (right+left)/2;
if (array[mid] == value) {
cout << "value found" << endl;
return;
}
// interval is not splittable
if (left+1 == right) {
return;
}
if (value < array[mid]) {
descending_binary_search(array, mid+1, right, value);
}
else {
descending_binary_search(array, left, mid, value);
}
}
void ascending_binary_search(int (&array) [N], int left, int right, int value)
{
// cout << "ascending_binary_search: " << left << " " << right << endl;
// empty interval
if (left == right) {
return;
}
// look at the middle of the interval
int mid = (right+left)/2;
if (array[mid] == value) {
cout << "value found" << endl;
return;
}
// interval is not splittable
if (left+1 == right) {
return;
}
if (value > array[mid]) {
ascending_binary_search(array, mid+1, right, value);
}
else {
ascending_binary_search(array, left, mid, value);
}
}
void bitonic_search(int (&array) [N], int left, int right, int value)
{
// cout << "bitonic_search: " << left << " " << right << endl;
// empty interval
if (left == right) {
return;
}
int mid = (right+left)/2;
if (array[mid] == value) {
cout << "value found" << endl;
return;
}
// not splittable interval
if (left+1 == right) {
return;
}
if(array[mid] > array[mid-1]) {
if (value > array[mid]) {
return bitonic_search(array, mid+1, right, value);
}
else {
ascending_binary_search(array, left, mid, value);
descending_binary_search(array, mid+1, right, value);
}
}
else {
if (value > array[mid]) {
bitonic_search(array, left, mid, value);
}
else {
ascending_binary_search(array, left, mid, value);
descending_binary_search(array, mid+1, right, value);
}
}
}
int main()
{
int array[N] = {2, 3, 5, 7, 9, 11, 13, 4, 1, 0};
int value = 4;
int left = 0;
int right = N;
// print "value found" is the desired value is in the bitonic array
bitonic_search(array, left, right, value);
return 0;
}