Given a bitonic array and element x in the array, find the index of x in 2log(n) time

后端 未结 7 1064
我寻月下人不归
我寻月下人不归 2021-01-29 22:14

First, a bitonic array for this question is defined as one such that for some index K in an array of length N where 0 < K < N - 1 an

相关标签:
7条回答
  • 2021-01-29 22:30

    For a binary split, there are three cases:

    1. max item is at right, then binary search left, and bitoinc search right.
    2. max item is at left, then binary search right, and bitoinc search left.
    3. max item is at the split point exactly, then binary both left and right.

    caution: the binary search used in left and right are different because of increasing/decreasing order.

    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);
        }
    }
    
    0 讨论(0)
  • 2021-01-29 22:33

    The algorithm works recursively by combining bitonic and binary searches:

    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)        
    

    So the recursive formula for the time is f(l) = f(l/2) + log(l/2) + c where log(l/2) comes from the binary search and c is the cost of the comparisons done in the function body.

    0 讨论(0)
  • 2021-01-29 22:38

    Answers those provided have time complexity of (N/2)*logN. Because the worst case may include too many sub-searches which are unnecessary. A modification is to compare the target value with the left and right element of sub series before searching. If target value is not between two ends of the monotonic series or less than both ends of the bitonic series, subsequent search is redundant. This modification leads to 2lgN complexity.

    0 讨论(0)
  • 2021-01-29 22:39
        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;
        }
    

    where DownSearch is

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

    and BinarySearch is

        /// <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

    0 讨论(0)
  • 2021-01-29 22:42

    The algorithms presented in other answers (this and this) are unfortunately incorrect, they are not O(logN) !

    The recursive formula f(L) = f(L/2) + log(L/2) + c doesn't lead to f(L) = O(log(N)) but leads to f(L) = O((log(N))^2) !

    Indeed, assume k = log(L), then log(2^(k-1)) + log(2^(k-2)) + ... + log(2^1) = log(2)*(k-1 + k-2 + ... + 1) = O(k^2). Hence, log(L/2) + log(L/4) + ... + log(2) = O((log(L)^2)).

    The right way to solve the problem in time ~ 2log(N) is to proceed as follows (assuming the array is first in ascending order and then in descending order):

    1. Take the middle of the array
    2. Compare the middle element with one of its neighbor to see if the max is on the right or on the left
    3. Compare the middle element with the desired value
    4. If the middle element is smaller than the desired value AND the max is on the left side, then do bitonic search on the left subarray (we are sure that the value is not in the right subarray)
    5. If the middle element is smaller than the desired value AND the max is on the right side, then do bitonic search on the right subarray
    6. If the middle element is bigger than the desired value, then do descending binary search on the right subarray and ascending binary search on the left subarray.

    In the last case, it might be surprising to do a binary search on a subarray that may be bitonic but it actually works because we know that the elements that are not in the good order are all bigger than the desired value. For instance, doing an ascending binary search for the value 5 in the array [2, 4, 5, 6, 9, 8, 7] will work because 7 and 8 are bigger than the desired value 5.

    Here is a fully working implementation (in C++) of the bitonic search in time ~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;
    }
    
    0 讨论(0)
  • 2021-01-29 22:50

    Finding the change of sign among the first order differences, by standard dichotomic search, will take 2Lg(n) array accesses.

    You can do slightly better by using the search strategy for the maximum of a unimodal function known as Fibonacci search. After n steps each involving a single lookup, you reduce the interval size by a factor Fn, corresponding to about Log n/Log φ ~ 1.44Lg(n) accesses to find the maximum.

    This marginal gain makes a little more sense when array accesses are instead costly funciton evaluations.

    0 讨论(0)
提交回复
热议问题