Учитывая битномический массив и элемент x в массиве, найдите индекс x в 2log (n) времени
Сначала битонный массив для этого вопроса определяется как один такой, что для некоторого индекса K
в массиве длины N
, где 0 < K < N - 1
и от 0 до K - монотонно возрастающая последовательность целых чисел, а K - N - 1 - монотонно убывающая последовательность целых чисел.
Пример: [1, 3, 4, 6, 9, 14, 11, 7, 2, -4, -9]
. Он монотонно увеличивается от 1 до 14, затем уменьшается с 14 до -9.
Предшественником этого вопроса является его решение в 3log(n)
, что намного проще. Один измененный двоичный поиск, чтобы найти индекс max, затем два бинарных поиска для 0 до K и от K + 1 до N - 1 соответственно.
Я предполагаю, что решение в 2log(n)
требует решения проблемы без нахождения индекса max. Я думал о перекрытии бинарных поисков, но помимо этого, я не уверен, как двигаться вперед.
Ответы
Ответ 1
Алгоритмы, представленные в других ответах (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) должен выполняться следующим образом (предполагая, что массив сначала в порядке возрастания, а затем в порядке убывания):
- Возьмите середину массива
- Сравните средний элемент с одним из его соседей, чтобы увидеть, находится ли макс справа или слева.
- Сравните средний элемент с желаемым значением
- Если средний элемент меньше желаемого значения, а max - с левой стороны, то выполните битномический поиск в левом подмассиве (мы уверены, что это значение не находится в правом подмассиве)
- Если средний элемент меньше желаемого значения, а max - с правой стороны, то выполните битонический поиск в правом подмассиве
- Если средний элемент больше желаемого значения, то выполните нисходящий двоичный поиск в правом подмассиве и восходящем двоичном поиске в левом подмассиве.
В последнем случае, возможно, было бы удивительно выполнить двоичный поиск в подмассиве, который может быть битным, но он действительно работает, потому что мы знаем, что элементы, которые находятся не в хорошем порядке, больше, чем требуемое значение. Например, выполнение возрастающего двоичного поиска значения 5 в массиве [2, 4, 5, 6, 9, 8, 7] будет работать, потому что 7 и 8 больше желаемого значения 5.
Ниже приведена полностью работающая реализация (на С++) битонического поиска во времени ~ 2logN:
#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;
}
Ответ 2
Алгоритм работает рекурсивно, комбинируя битонные и бинарные поисковые запросы:
def bitonic_search (array, value, lo = 0, hi = array.length - 1)
if array[lo] == value then return lo
if array[hi] == value then return hi
mid = (hi + lo) / 2
if array[mid] == value then return mid
if (mid > 0 & array[mid-1] < array[mid])
| (mid < array.length-1 & array[mid+1] > array[mid]) then
# max is to the right of mid
bin = binary_search(array, value, low, mid-1)
if bin != -1 then return bin
return bitonic_search(array, value, mid+1, hi)
else # max is to the left of mid
bin = binary_search(array, value, mid+1, hi)
if bin != -1 then return bin
return bitonic_search(array, value, lo, mid-1)
Таким образом, рекурсивная формула для времени f(l) = f(l/2) + log(l/2) + c
, где log(l/2)
происходит из двоичного поиска, а c
- это стоимость сравнений, выполненных в теле функции.
Ответ 3
public int FindLogarithmicGood(int value)
{
int lo = 0;
int hi = _bitonic.Length - 1;
int mid;
while (hi - lo > 1)
{
mid = lo + ((hi - lo) / 2);
if (value < _bitonic[mid])
{
return DownSearch(lo, hi - lo + 1, mid, value);
}
else
{
if (_bitonic[mid] < _bitonic[mid + 1])
lo = mid;
else
hi = mid;
}
}
return _bitonic[hi] == value
? hi
: _bitonic[lo] == value
? lo
: -1;
}
где DownSearch -
public int DownSearch(int index, int count, int mid, int value)
{
int result = BinarySearch(index, mid - index, value);
if (result < 0)
result = BinarySearch(mid, index + count - mid, value, false);
return result;
}
и BinarySearch
/// <summary>
/// Exactly log(n) on average and worst cases.
/// Note: System.Array.BinarySerch uses 2*log(n) in the worst case.
/// </summary>
/// <returns>array index</returns>
public int BinarySearch(int index, int count, int value, bool asc = true)
{
if (index < 0 || count < 0)
throw new ArgumentOutOfRangeException();
if (_bitonic.Length < index + count)
throw new ArgumentException();
if (count == 0)
return -1;
// "lo minus one" trick
int lo = index - 1;
int hi = index + count - 1;
int mid;
while (hi - lo > 1)
{
mid = lo + ((hi - lo) / 2);
if ((asc && _bitonic[mid] < value) || (!asc && _bitonic[mid] > value))
lo = mid;
else
hi = mid;
}
return _bitonic[hi] == value ? hi : -1;
}
github
Ответ 4
Ответы предоставленные имеют временную сложность (N/2) * logN. Потому что худший случай может включать слишком много подпросмотров, которые не нужны. Модификация заключается в сравнении целевого значения с левым и правым элементом подсерии перед поиском. Если целевое значение не находится между двумя концами монотонной серии или меньше обоих концов битной серии, последующий поиск является избыточным. Эта модификация приводит к сложности 2lgN.
Ответ 5
Существует 5 основных случаев, в зависимости от того, где находится максимальный элемент массива, и является ли средний элемент большим, чем желаемое значение
Вычислить средний элемент.
Сравните желаемое значение среднего элемента, если оно совпадает с поиском. В противном случае переходите к следующему шагу.
-
Сравните средний элемент с соседями, чтобы увидеть, находится ли элемент max слева или справа. Если оба из соседей меньше среднего элемента, тогда элемент отсутствует в массиве, следовательно, выйдите. (Массив, упомянутый в вопросе, сначала столкнется с этим случаем, так как 14, максимальный элемент находится в середине)
-
Если средний элемент меньше желаемого значения, а элемент max находится справа, выполните битномический поиск в правом подмассиве
-
Если средний элемент меньше желаемого значения, а элемент max находится слева, выполните битномический поиск в левом подмассиве
-
Если средний элемент больше желаемого значения, а элемент max находится слева, выполните нисходящий двоичный поиск в правом подмассиве
-
Если средний элемент больше желаемого значения, а элемент max находится справа, выполните восходящий двоичный поиск в левом подмассиве
В худшем случае мы будем делать два сравнения каждый раз, когда массив делится пополам, поэтому сложность будет 2 * logN
Ответ 6
Поиск изменения знака среди различий первого порядка, стандартным дихотомическим поиском, будет принимать обращения к массиву 2Lg(n)
.
Вы можете сделать немного лучше, используя стратегию поиска для максимальной унимодальной функции, известной как поиск Фибоначчи. После n шагов, каждый из которых включает один поиск, вы уменьшаете размер интервала на коэффициент Fn
, соответствующий примерно Log n/Log φ ~ 1.44Lg(n)
, для доступа к максимальному значению.
Этот предельный выигрыш дает немного больше смысла, когда обращения к массиву являются более дорогостоящими оценками funciton.
Ответ 7
Для двоичного разбиения есть три случая:
- Максимальный элемент находится справа, затем двоичный поиск влево и прав поиска в биттонах.
- Максимальный элемент слева, затем правый двоичный поиск и поиск по битонику слева.
- max элемент находится в точке разделения точно, затем двоичный как слева, так и справа.
Предупреждение: двоичный поиск, используемый слева и справа, отличается тем, что увеличивается/уменьшается порядок.
public static int bitonicSearch(int[] a, int lo, int hi, int key) {
int mid = (lo + hi) / 2;
int now = a[mid];
if (now == key)
return mid;
// deal with edge cases
int left = (mid == 0)? a[mid] : a[mid - 1];
int right = (mid == a.length-1)? a[mid] : a[mid + 1];
int leftResult, rightResult;
if (left < now && now < right) { // max item is at right
leftResult = binarySearchIncreasing(a, lo, mid - 1, key);
if (leftResult != -1)
return leftResult;
return bitonicSearch(a, mid + 1, hi, key);
}
else if (left > now && now > right) { // max item is at left
rightResult = binarySearchDecreasing(a, mid + 1, hi, key);
if (rightResult != -1)
return rightResult;
return bitonicSearch(a, lo, mid - 1, key);
}
else { // max item stands at the split point exactly
leftResult = binarySearchIncreasing(a, lo, mid - 1, key);
if (leftResult != -1)
return leftResult;
return binarySearchDecreasing(a, mid + 1, hi, key);
}
}