Tout d'abord, un tableau bitonique pour cette question est défini de la manière suivante: pour certains index, K
dans un tableau de longueur N
où 0 < K < N - 1
et 0 à K est une séquence d'entiers croissant de manière monotone, et K à N - 1 est une séquence décroissante de façon monotone entiers.
Exemple: [1, 3, 4, 6, 9, 14, 11, 7, 2, -4, -9]
. Il augmente monotonement de 1 à 14, puis diminue de 14 à -9.
Le précurseur de cette question est de le résoudre en 3log(n)
, ce qui est beaucoup plus facile. Une recherche binaire modifiée pour trouver l'index du max, puis deux recherches binaires pour 0 à K et K + 1 à N - 1 respectivement.
Je suppose que la solution dans 2log(n)
nécessite que vous résolviez le problème sans trouver l’indice du maximum. J'ai envisagé de faire en sorte que les recherches binaires se chevauchent, mais au-delà, je ne sais pas comment aller de l'avant.
Les algorithmes présentés dans d'autres réponses ( this et this ) sont malheureusement incorrects, ils ne sont pas O(logN)!
La formule récursive f(L) = f(L/2) + log (L/2) + c ne conduit pas à f(L) = O(log(N)) mais conduit à f(L) = O ((log (N)) ^ 2) !
En effet, supposons que k = log (L), puis log (2 ^ (k-1)) + log (2 ^ (k-2)) + ... + log (2 ^ 1) = log (2) * ( k-1 + k-2 + ... + 1) = O (k ^ 2). Par conséquent, log (L/2) + log (L/4) + ... + log (2) = O ((log (L) ^ 2)).
La bonne façon de résoudre le problème dans le temps ~ 2log (N) est de procéder comme suit (en supposant que le tableau soit d’abord en ordre croissant, puis en ordre décroissant):
Dans le dernier cas, il peut être surprenant de faire une recherche binaire sur un sous-tableau qui peut être bitonique, mais cela fonctionne car nous savons que les éléments qui ne sont pas dans le bon ordre sont tous plus grands que la valeur souhaitée. Par exemple, une recherche binaire ascendante de la valeur 5 dans le tableau [2, 4, 5, 6, 9, 8, 7] fonctionnera, car 7 et 8 sont plus grands que la valeur souhaitée 5.
Voici une implémentation pleinement fonctionnelle (en C++) de la recherche bitonique dans le temps ~ 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;
}
L'algorithme fonctionne de manière récursive en combinant des recherches bitoniques et binaires:
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)
La formule récursive pour le temps est donc f(l) = f(l/2) + log(l/2) + c
, où log(l/2)
provient de la recherche binaire et c
correspond au coût des comparaisons effectuées dans le corps de la fonction.
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;
}
où DownSearch est
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;
}
et BinarySearch est
/// <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;
}
La recherche du changement de signe parmi les différences de premier ordre, par recherche dichotomique standard, prendra 2Lg(n)
accès au tableau.
Vous pouvez faire un peu mieux en utilisant la stratégie de recherche pour le maximum d’une fonction unimodale appelée recherche de Fibonacci. Après n étapes comportant chacune une seule recherche, vous réduisez la taille de l'intervalle d'un facteur Fn
, correspondant à environ Log n/Log φ ~ 1.44Lg(n)
accès pour rechercher le maximum.
Ce gain marginal est un peu plus logique lorsque les accès aux tableaux sont des évaluations de fonctions coûteuses.
Les réponses fournies ont une complexité temporelle de (N/2) * logN. Parce que le pire des cas peut inclure trop de sous-recherches inutiles. Une modification consiste à comparer la valeur cible avec les éléments gauche et droit de la sous-série avant la recherche. Si la valeur cible n'est pas comprise entre deux extrémités de la série monotone ou inférieure aux deux extrémités de la série bitonique, la recherche suivante est redondante. Cette modification conduit à une complexité de 2lgN.
Il y a 5 cas principaux selon l'endroit où se trouve l'élément max du tableau et si l'élément du milieu est supérieur à la valeur souhaitée
Calculez l'élément central. Comparez la valeur souhaitée de l'élément central, si elle correspond aux fins de la recherche. Sinon, passez à l'étape suivante.
Comparez l'élément du milieu avec les voisins pour voir si l'élément max est à gauche ou à droite. Si les deux voisins sont inférieurs à l'élément du milieu, l'élément n'est pas présent dans le tableau, d'où sa sortie.
Si l'élément central est inférieur à la valeur souhaitée et que l'élément max est à droite, effectuez une recherche bitonique dans la sous-matrice de droite.
Si l'élément du milieu est inférieur à la valeur souhaitée et que l'élément max est à gauche, effectuez une recherche bitonique dans la sous-matrice de gauche.
Si l'élément central est supérieur à la valeur souhaitée et que l'élément maximal est à gauche, effectuez une recherche binaire décroissante dans la sous-matrice de droite.
Si l'élément du milieu est supérieur à la valeur souhaitée et que l'élément max est à droite, effectuez une recherche binaire ascendante dans la sous-matrice de gauche
Dans le pire des cas, nous effectuerons deux comparaisons chaque fois que le tableau est divisé en deux, la complexité sera donc de 2 * logN