У меня есть отсортированный список, который повернут и хотел бы сделать двоичный поиск в том списке для нахождения минимального элемента.
Позволяет предполагают, что первоначальный список {1,2,3,4,5,6,7,8}, повернутый список может быть похожим {5,6,7,8,1,2,3,4}
Нормальный двоичный поиск не работает в этом случае. Любая идея, как сделать это.
- Редактирование
У меня есть друг друг условие. Что, если список не отсортирован??
Все, что вам нужно, - это небольшая модификация алгоритма двоичного поиска; вот решение в полностью работоспособной Java (см. ответ Серга для реализации Delphi и ответ tkr для визуального объяснения алгоритма).
import java.util.*;
public class BinarySearch {
static int findMinimum(Integer[] arr) {
int low = 0;
int high = arr.length - 1;
while (arr[low] > arr[high]) {
int mid = (low + high) >>> 1;
if (arr[mid] > arr[high]) {
low = mid + 1;
} else {
high = mid;
}
}
return low;
}
public static void main(String[] args) {
Integer[] arr = { 1, 2, 3, 4, 5, 6, 7 };
// must be in sorted order, allowing rotation, and contain no duplicates
for (int i = 0; i < arr.length; i++) {
System.out.print(Arrays.toString(arr));
int minIndex = findMinimum(arr);
System.out.println(" Min is " + arr[minIndex] + " at " + minIndex);
Collections.rotate(Arrays.asList(arr), 1);
}
}
}
Это печатает:
[1, 2, 3, 4, 5, 6, 7] Min is 1 at 0
[7, 1, 2, 3, 4, 5, 6] Min is 1 at 1
[6, 7, 1, 2, 3, 4, 5] Min is 1 at 2
[5, 6, 7, 1, 2, 3, 4] Min is 1 at 3
[4, 5, 6, 7, 1, 2, 3] Min is 1 at 4
[3, 4, 5, 6, 7, 1, 2] Min is 1 at 5
[2, 3, 4, 5, 6, 7, 1] Min is 1 at 6
Integer []
вместо int []
>>> 1
вместо / 2
Обратите внимание, что дублирование делает невозможным для этого в O (log N)
.Рассмотрим следующий битовый массив, состоящий из множества 1
и одного 0
:
(sorted)
01111111111111111111111111111111111111111111111111111111111111111
^
(rotated)
11111111111111111111111111111111111111111111101111111111111111111
^
(rotated)
11111111111111101111111111111111111111111111111111111111111111111
^
Этот массив можно повернуть N
способами и найти 0
в O (log N)
невозможно, поскольку нет способа определить, находится ли он слева или справа от «середины».
У меня одно другое состояние. Что делать, если список не отсортирован ??
Тогда, если вы не хотите сначала отсортировать его и продолжить оттуда, вам придется выполнить линейный поиск, чтобы найти минимум.
Вот изображение, иллюстрирующее предлагаемые алгоритмы:
Версия Delphi - третья улучшенная (благодаря коду polygenelubricants - еще одно сравнение удалено) вариант:
type
TIntegerArray = array of Integer;
function MinSearch(A: TIntegerArray): Integer;
var
I, L, H: Integer;
begin
L:= Low(A); // = 0
H:= High(A); // = Length(A) - 1
while A[L] > A[H] do begin
I:= (L + H) div 2; // or (L + H) shr 1 to optimize
Assert(I < H);
if (A[I] > A[H])
then L:= I + 1
else H:= I;
end;
Result:= A[L];
end;
Что-то вроде этого может сработать (не проверено):
//assumes the list is a std::vector<int> myList
int FindMinFromRotated(std::vector<int>::iterator begin, std::vector<int>::iterator end) {
if (begin == end)
throw std::invalid_argument("Iterator range is singular!");
if (std::distance(begin, end) == 1) //What's the min of one element?
return *begin;
if (*begin < *end) //List is sorted if this is true.
return *begin;
std::vector<int>::iterator middle(begin);
std::advance(middle, std::distance(begin, end)/2);
if (*middle < *begin) //If this is true, than the middle element selected is past the rotation point
return FindMinFromRotated(begin, middle)
else if (*middle > *begin) //If this is true, the the middle element selected is in front of the rotation point.
return FindMinFromRotated(middle, end)
else //Looks like we found what we need :)
return *begin;
}
Я хотел бы выполнить бинарный поиск в этом списке, чтобы найти минимальный элемент.
Троичный поиск будет работать для такого случая: когда функция имеет ровно один локальный минимум.
http://en.wikipedia.org/wiki/Ternary_search
edit При повторном прочтении я, вероятно, неправильно понял вопрос: функция не соответствует требованиям троичного поиска :/ Но разве двоичный поиск не работает? Предположим, исходный порядок был возрастающим.
if (f(left) < f(middle))
// which means, 'left' and 'middle' are on the same segment (before or after point X we search)
// and also 'left' is before X by definition
// so, X must be to the right from 'middle'
left = middle
else
right = middle
Просто выполните метод деления пополам в списке - list [end]
в диапазоне [1, end). Метод деления пополам ищет нули в функции путем поиска изменения знака и работает в O (log n).
Например,
{5,6,7,8,1,2,3,4} -> {1,2,3,4, -3, -2, -1,0}
Затем используйте (дискретизированный) метод деления пополам в этом списке {1,2,3,4, -3, -2, -1}. Он найдет нулевое пересечение между 4 и -3, что соответствует вашей точке вращения.
Выберите некоторую подпоследовательность [i, j]
из списка [первый, последний)
. Либо [i, j]
не содержит разрыва, и в этом случае * i <= * j
, либо содержит, и в этом случае остальные элементы (j, last) U [first, i)
, правильно отсортированы, и в этом случае * j <= * i
.
Рекурсивно разделите подозрительный диапазон пополам, пока не разделите его до одного элемента. Принимает O (log N) сравнений.