Поиск элемента в круговом отсортированном массиве - PullRequest
27 голосов
/ 14 мая 2010

Мы хотим найти данный элемент в круговом отсортированном массиве по сложности, не превышающей O(log n).
Пример: поиск 13 в {5,9,13,1,3}.

Моя идея состояла в том, чтобы преобразовать круговой массив в обычный отсортированный массив, а затем выполнить бинарный поиск по результирующему массиву, но моя проблема заключалась в том, что алгоритм, который я нашел, был глуп, что в худшем случае он принимает O(n):

for(i = 1; i < a.length; i++){
    if (a[i] < a[i-1]){
        minIndex = i; break;
    }
}

тогда соответствующий индекс i-го элемента будет определяться из следующего соотношения:

(i + minInex - 1) % a.length

ясно, что мой алгоритм преобразования (из кругового в обычный) может занять O (n), поэтому нам нужен лучший.

Согласно идее ire_and_curses, вот решение на Java:

public int circularArraySearch(int[] a, int low, int high, int x){
    //instead of using the division op. (which surprisingly fails on big numbers)
    //we will use the unsigned right shift to get the average
    int mid = (low + high) >>> 1;
    if(a[mid] == x){
        return mid;
    }
    //a variable to indicate which half is sorted
    //1 for left, 2 for right
    int sortedHalf = 0;
    if(a[low] <= a[mid]){
        //the left half is sorted
        sortedHalf = 1;
        if(x <= a[mid] && x >= a[low]){
            //the element is in this half
            return binarySearch(a, low, mid, x);
        }
    }
    if(a[mid] <= a[high]){
        //the right half is sorted
        sortedHalf = 2;
        if(x >= a[mid] && x<= a[high] ){
            return binarySearch(a, mid, high, x);
        }
    }
    // repeat the process on the unsorted half
    if(sortedHalf == 1){
        //left is sorted, repeat the process on the right one
        return circularArraySearch(a, mid, high, x);
    }else{
        //right is sorted, repeat the process on the left
        return circularArraySearch(a, low, mid, x);
    }
}

Надеюсь, это сработает.

Ответы [ 16 ]

50 голосов
/ 14 мая 2010

Вы можете сделать это, воспользовавшись тем, что массив отсортирован, за исключением особого случая значения pivot и одного из его соседей.

  • Найти среднее значение массива a.
  • Если a[0] < a[mid], то все значения в первая половина массива отсортирован.
  • Если a[mid] < a[last], то все значения во второй половине массив отсортирован.
  • взять отсортированный наполовину, и проверьте, стоит ли ваша ценность лежит внутри него (сравните с максимальный idx в этой половине).
  • Если так, просто бинарный Ищите эту половину.
  • Если это не так, это должно быть в несортированной половине. принимать эту половину и повторить этот процесс, определяя, какая половина этой половины сортируется и т. д.
9 голосов
/ 14 мая 2010

Не очень элегантно, но, с моей стороны, просто используйте бинарный поиск, чтобы найти точку вращения повернутого массива, а затем снова выполните бинарный поиск, компенсируя смещение оси. Глупо выполнять два полных поиска, но он удовлетворяет условию, поскольку O (log n) + O (log n) == O (log n). Держи это просто и глупо (тм)!

7 голосов
/ 19 февраля 2012

Это пример, который работает в Java. Поскольку это отсортированный массив, вы воспользуетесь этим и запустите бинарный поиск, однако его необходимо немного изменить, чтобы он соответствовал позиции стержня.

Метод выглядит так:

private static int circularBinSearch ( int key, int low, int high )
{
    if (low > high)
    {
        return -1; // not found
    }

    int mid = (low + high) / 2;
    steps++;

    if (A[mid] == key)
    {
        return mid;
    }
    else if (key < A[mid])
    {
        return ((A[low] <= A[mid]) && (A[low] > key)) ?
               circularBinSearch(key, mid + 1, high) :
               circularBinSearch(key, low, mid - 1);
    }
    else // key > A[mid]
    {
        return ((A[mid] <= A[high]) && (key > A[high])) ?
               circularBinSearch(key, low, mid - 1) :
               circularBinSearch(key, mid + 1, high);
    }
}

Теперь, чтобы облегчить любые заботы, вот небольшой класс, который проверяет алгоритм:

public class CircularSortedArray
{
    public static final int[] A = {23, 27, 29, 31, 37, 43, 49, 56, 64, 78, 
                                   91, 99, 1, 4, 11, 14, 15, 17, 19};
    static int steps;

    // ---- Private methods ------------------------------------------

    private static int circularBinSearch ( int key, int low, int high )
    {
        ... copy from above ...
    }

    private static void find ( int key )
    {
        steps = 0;
        int index = circularBinSearch(key, 0, A.length-1);
        System.out.printf("key %4d found at index %2d in %d steps\n",
                          key, index, steps);
    }

    // ---- Static main -----------------------------------------------

    public static void main ( String[] args )
    {
        System.out.println("A = " + Arrays.toString(A));
        find(44);   // should not be found
        find(230);
        find(-123);

        for (int key: A)  // should be found at pos 0..18
        {
            find(key);
        }
    }
}

Это даст вам вывод:

A = [23, 27, 29, 31, 37, 43, 49, 56, 64, 78, 91, 99, 1, 4, 11, 14, 15, 17, 19]
key   44 found at index -1 in 4 steps
key  230 found at index -1 in 4 steps
key -123 found at index -1 in 5 steps
key   23 found at index  0 in 4 steps
key   27 found at index  1 in 3 steps
key   29 found at index  2 in 4 steps
key   31 found at index  3 in 5 steps
key   37 found at index  4 in 2 steps
key   43 found at index  5 in 4 steps
key   49 found at index  6 in 3 steps
key   56 found at index  7 in 4 steps
key   64 found at index  8 in 5 steps
key   78 found at index  9 in 1 steps
key   91 found at index 10 in 4 steps
key   99 found at index 11 in 3 steps
key    1 found at index 12 in 4 steps
key    4 found at index 13 in 5 steps
key   11 found at index 14 in 2 steps
key   14 found at index 15 in 4 steps
key   15 found at index 16 in 3 steps
key   17 found at index 17 in 4 steps
key   19 found at index 18 in 5 steps
5 голосов
/ 14 мая 2010

У вас есть три значения, l, m, h для значений по нижнему, среднему и верхнему показателям вашего поиска. Если бы вы думали, что вы бы продолжили искать каждую возможность:

// normal binary search
l < t < m    - search(t,l,m)
m < t < h    - search(t,m,h)

// search over a boundary
l > m, t < m - search(t,l,m)
l > m, t > l - search(t,l,m)
m > h, t > m - search(t,m,h)  
m > h, t < h - search(t,m,h)  

Это вопрос рассмотрения того, где может быть целевое значение, и поиска этой половины пространства. Не более половины пространства будет иметь обтекание, и легко определить, находится ли целевое значение в этой половине или в другой.

Это своего рода мета-вопрос - вы думаете о бинарном поиске в терминах того, как он часто представляется - поиск значения между двумя точками или, в более общем смысле, как повторное деление абстрактного пространства поиска.

0 голосов
/ 31 декабря 2016

Хотя одобренный ответ является оптимальным, мы можем также использовать аналогичный и более чистый алгоритм.

  1. запустить бинарный поиск, чтобы найти элемент поворота (где вращается массив). O(logn)
  2. левая половина сводки будет отсортирована в порядке убывания, запустите здесь двоичный поиск назад для ключа. O(logn)
  3. Правая половина сводки будет отсортирована в порядке возрастания, запустите двоичный поиск вперед в этой половине ключа. O(logn)
  4. вернуть найденный ключевой индекс из шагов 2 и 3.

Общая сложность времени: O(logn)

Мысли приветствуются.

0 голосов
/ 17 января 2016

Простой метод в Ruby

def CircularArraySearch(a, x)
  low = 0
  high = (a.size) -1

  while low <= high
    mid = (low+high)/2
    if a[mid] == x
      return mid
    end
    if a[mid] <= a[high]
      if (x > a[mid]) && (x <= a[high])
        low = mid + 1
      elsif high = mid -1
      end
    else
      if (a[low] <= x) && (x < a[mid])
        high = mid -1
      else
        low = mid +1
      end
    end
  end
  return -1
end

a = [12, 14, 18, 2, 3, 6, 8, 9]
x = gets.to_i
p CircularArraySearch(a, x)
0 голосов
/ 06 января 2016

Простой бинарный поиск с небольшими изменениями.

Индекс вращающегося массива = (i + pivot)% размер

pivot - это индекс i + 1, где a [i]> a [i + 1].

#include <stdio.h>
#define size 5
#define k 3
#define value 13
int binary_search(int l,int h,int arr[]){

int mid=(l+h)/2;

if(arr[(mid+k)%size]==value)
    return (mid+k)%size;

if(arr[(mid+k)%size]<value)
    binary_search(mid+1,h,arr);
else
    binary_search(l,mid,arr);
}

int main() {
    int arr[]={5,9,13,1,3};
    printf("found at: %d\n", binary_search(0,4,arr));
    return 0;
}
0 голосов
/ 05 ноября 2015

Вот решение в javascript. Протестировал его с несколькими различными массивами, и, кажется, работает. В основном он использует тот же метод, который описан в ire_and_curses:

function search(array, query, left, right) {
  if (left > right) {
    return -1;
  }

  var midpoint = Math.floor((left + right) / 2);
  var val = array[midpoint];
  if(val == query) {
    return midpoint;
  }

  // Look in left half if it is sorted and value is in that 
  // range, or if right side is sorted and it isn't in that range.
  if((array[left] < array[midpoint] && query >= array[left] && query <= array[midpoint])
    || (array[midpoint] < array[right] 
        && !(query >= array[midpoint] && query <= array[right]))) {
    return search(array, query, left, midpoint - 1);
  } else {
    return search(array, query, midpoint + 1, right);
  }
}
0 голосов
/ 19 апреля 2015

Ниже приведена реализация на C с использованием бинарного поиска.

int rotated_sorted_array_search(int arr[], int low, int high, int target)
{
    while(low<=high)
    {
        int mid = (low+high)/2;

        if(target == arr[mid])
            return mid;

        if(arr[low] <= arr[mid])
        {
            if(arr[low]<=target && target < arr[mid])
            {
                high = mid-1;
            }
            else
                low = mid+1;
        }
        else
        {
            if(arr[mid]< target && target <=arr[high])
            {
                low = mid+1;
            }
            else
                high = mid-1;
        }
    }
    return -1;
}
0 голосов
/ 08 ноября 2014

Я думаю, вы можете найти смещение, используя этот код:

public static int findOffset(int [] arr){
        return findOffset(arr,0,arr.length-1);
    }
private static int findOffset(int[] arr, int start, int end) {

    if(arr[start]<arr[end]){
        return -1;
    }
    if(end-start==1){
        return end;
    }
    int mid = start + ((end-start)/2);
    if(arr[mid]<arr[start]){
        return findOffset(arr,start,mid);
    }else return findOffset(arr,mid,end);
}
...