web-dev-qa-db-fra.com

Étant donné un tableau bitonique et un élément x dans le tableau, recherchez l'index de x dans 2log (n) time

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 N0 < 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. 

20
David

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):

  1. Prendre le milieu de la rangée
  2. Comparez l'élément du milieu avec l'un de ses voisins pour voir si le max est à droite ou à gauche
  3. Comparer l'élément du milieu avec la valeur souhaitée
  4. Si l'élément du milieu est inférieur à la valeur souhaitée ET que le max est du côté gauche, lancez une recherche bitonique dans le sous-tableau de gauche (nous sommes sûrs que la valeur ne se trouve pas dans le sous-tableau de droite).
  5. Si l'élément du milieu est inférieur à la valeur souhaitée ET que le max est du côté droit, effectuez une recherche bitonique dans le sous-tableau de droite.
  6. Si l'élément du milieu est supérieur à la valeur souhaitée, effectuez une recherche binaire décroissante dans le sous-tableau droit et une recherche binaire ascendante dans le sous-tableau gauche.

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;
}
38
user3017842

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.

2
sds
    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;
    }

github

1
Denis Larionov

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.

1
Yves Daoust

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.

0
Bravo

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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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

0
Manohar Bhat