当数组的一部分被反转时的二进制搜索

时间:2017-06-20 11:45:05

标签: arrays algorithm search binary-search

给定一个数组(大小为N)(已排序但其中某些部分被反转),以及一系列元素(Q),我们必须根据元素是否存在于数组中而输出是/否。

我提出的解决方案如下:

  1. 执行线性遍历并找出数组反转的索引。
  2. 对于每个元素查询,在3个子数组(第一个子数组,反向子数组和剩余子数组)上调用二进制搜索
  3. 时间复杂度为O(N + Q* log N).我想知道我们是否可以在O(Q * Log N)避免第一步这样做?

3 个答案:

答案 0 :(得分:0)

可以在 log(n)

中完成

可能主要有3种情况:反向子阵列共享两半(1 5 4 3 2 6 7)或完全位于上半部分(1 3 2 4 5 6 7)或下半年(1 2 3 4 6 5 7)。

1)最初,只需进行正常的二分查找并继续搜索,直到

(i)您找到了所需的元素或
   (ii)遇到反转子数组(即当索引(l + h)/ 2处的元素小于prev_element且大于next_element时)。
如果遇到反向子阵列,则继续 2)。

2)当反向子阵列共享两半时,即通过中间索引切换,则这是枢轴阵列方法的情况。对两个数组的前半部分和后半部分应用下面提到的数据透视数组方法,看看是否找到了元素。
 2 * log(n)~log(n)。
确保也处理角落情况。

总体时间复杂度:log(n)

透视阵列方法:

请记住,仍然会对数组的一半进行排序。 因此,您仍然可以通过稍微修改二进制搜索来找到log(n)中的元素。

Input arr[] = {3, 4, 5, 1, 2}
Element to Search = 1
 1) Find middle point mid = (l + h)/2
 2) If key is present at middle point, return mid.
 3) Else If arr[l..mid] is sorted
    a) If key to be searched lies in range from arr[l]
       to arr[mid], recur for arr[l..mid].
    b) Else recur for arr[mid+1..r]
 4) Else (arr[mid+1..r] must be sorted)
    a) If key to be searched lies in range from arr[mid+1]
       to arr[r], recur for arr[mid+1..r].
    b) Else recur for arr[l..mid] 

关于www.geeksforgeeks.org/search-an-element-in-a-sorted-and-pivoted-array/

的非常好的文章

答案 1 :(得分:0)

你知道这个数组包括三个部分,升序,降序,升序。所以你可以通过抽样找到两个关键点。这有点棘手,一旦你在反转部分找到一个点就足够了(只需要两次二分搜索)。找到反转部分更难。如果我们采样和数组[i + 1]< array [i],我们找到了它。但如果我们错过它,我们不知道我们是太高还是太低,所以我们需要一个有间隔的队列来测试。我们从一个间隔开始,然后对中间进行采样。这给了我们两个区间,所以我们对它们的中间进行采样,它给出了四个区间,依此类推,直到我们找到了反转区间的索引。

答案 2 :(得分:0)

我想我找到了解决方案。首先我们需要的是找到一个突破点的某种方式,我的意思是:让我们说我们有一个数组{1,2,3,4,9,8,7,6}注意索引4处的数字9它是一个断点,因为排序方向已经改变,现在重要的部分是在log(n)中找到它。为此,我创建了下面的类。为了这个类工作,他需要获取以下类型的数组{sorted,reverseSorted}

public class BreakPointSearcher {
private int array[];
private BiFunction<Integer, Integer, Boolean> compare;
private Map<Boolean, Direction> directions;

public BreakPointSearcher(int[] array, BiFunction<Integer, Integer, Boolean> compare) {
    this.array = array;
    this.compare = compare;
}

public int findTheBreackingPoint(int start, int end) {
    if (start >= end || end >= array.length)
        return -1;

    loadDirectionMap(start, end);
    int mid = (start + end) / 2;
    if (isBreackingPoint(mid))
        return mid;

    Direction direction = getDirection(mid);
    //Using recursive call by that making the algorithm run in O(log(n))
    if (GoRight == direction) {
        return findTheBreackingPoint(mid + 1, end);
    } else if (GoLeft == direction) {
        return findTheBreackingPoint(start, mid - 1);
    }

    return -1;
}

/**
 * I assume that two sides of the array have opposite sorting
 * that is if at the start we have ascending at the end we have descending
 */
private void loadDirectionMap(int start, int end) {
    directions = new HashMap<>();
    directions.put(compare.apply(array[start], array[start + 1]), GoRight);
    directions.put(compare.apply(array[end - 1], array[end]), GoLeft);
}

private Direction getDirection(int mid) {
    return directions.get(compare.apply(array[mid], array[mid + 1]));
}

private boolean isBreackingPoint(int mid) {
    boolean inBetween = compare.apply(array[mid - 1], array[mid]) && compare.apply(array[mid + 1], array[mid]); // a > b < c
    boolean inBetweenTypeTwo = compare.apply(array[mid], array[mid - 1]) && compare.apply(array[mid], array[mid + 1]); // a < b > c
    return inBetween || inBetweenTypeTwo;
}

enum Direction {
    GoLeft,
    GoRight
}

public static void main(String... args) {
    int[] arrayNums = {1, 2, 3, 4, 5, 6, 7, 20, 9, 8};
    BiFunction<Integer, Integer, Boolean> compareAsc = (a, b) -> a > b;
    BreakPointSearcher sbs = new BreakPointSearcher(arrayNums, compareAsc);
    System.out.println(sbs.findTheBreackingPoint(0, arrayNums.length - 1)); //output is 7

    arrayNums = new int[]{252, 48, 22, 10, 12, 13, 16};
    sbs = new BreakPointSearcher(arrayNums, compareAsc);
    System.out.println(sbs.findTheBreackingPoint(0, arrayNums.length - 1)); //output 3

    arrayNums = new int[]{22, 56, 13};
    sbs = new BreakPointSearcher(arrayNums, compareAsc);
    System.out.println(sbs.findTheBreackingPoint(0, arrayNums.length - 1)); //output 1

    arrayNums = new int[]{22, 56, 78, 33};
    sbs = new BreakPointSearcher(arrayNums, compareAsc);
    System.out.println(sbs.findTheBreackingPoint(0, arrayNums.length - 1)); //output 2

    arrayNums = new int[]{1, 2, 3, 4};
    sbs = new BreakPointSearcher(arrayNums, compareAsc);
    System.out.println(sbs.findTheBreackingPoint(0, arrayNums.length - 1)); //output -1

    arrayNums = new int[]{4, 3, 2, 1};
    sbs = new BreakPointSearcher(arrayNums, compareAsc);
    System.out.println(sbs.findTheBreackingPoint(0, arrayNums.length - 1)); //output -1

}

}

现在我们有了这样的类,我们可以开始二进制搜索,我要做的是搜索密钥,如果发现函数已经完成,如果在某些时候算法注意到排序被反转它会去在该点的左侧和右侧,将搜索反转数组的边界,之后我们在每个中执行3个数组,我们执行二进制搜索(其中一个是反向的)并且它就是它。它是不是一个完美的解决方案,可能在某些特殊情况下,但作为一般idia我认为是正确的

public class SpecialBinarySearch {

private static BiFunction<Integer, Integer, Boolean> ascComp = (a, b) -> a > b;
private static BiFunction<Integer, Integer, Boolean> dscComp = (a, b) -> a < b;

public static int search(int[] array, int key, int start, int end, BiFunction<Integer, Integer, Boolean> compare) {
    int mid = (start + end) / 2;
    if (array[mid] == key)
        return mid;
    if (start == end)
        return -1;

    boolean opositeSorted = compare.apply(array[mid], array[mid + 1]);
    if (opositeSorted) {
        BreakPointSearcher breakPointSearcher = new BreakPointSearcher(array, compare);
        int leftBound = breakPointSearcher.findTheBreackingPoint(0, mid);
        int rightBound = breakPointSearcher.findTheBreackingPoint(mid, array.length - 1);

        leftBound = leftBound == -1 ? 0 : leftBound; //
        rightBound = rightBound == -1 ? array.length - 1 : rightBound;

        int opt1 = search(array, key, leftBound, rightBound, getOpositeCompare(compare));
        int opt2 = search(array, key, start, leftBound, compare);
        int opt3 = search(array, key, rightBound, end, compare);

        return Math.max(Math.max(opt1, opt2), opt3);
    }

    if (compare.apply(key, array[mid])) {
        return search(array, key, mid + 1, end, compare);
    } else {
        return search(array, key, start, mid - 1, compare);
    }
}

private static BiFunction<Integer, Integer, Boolean> getOpositeCompare(BiFunction<Integer, Integer, Boolean> compare) {
    return compare == ascComp ? dscComp : ascComp;
}

public static void main(String... args) {
    int[] array = {1, 2, 3, 20, 25};
    System.out.println(search(array, 20, 0, array.length - 1, ascComp)); //output 3

    array = new int[]{10, 9, 8, 7, 6, 3};
    System.out.println(search(array, 6, 0, array.length - 1, dscComp));//output 4

    array = new int[]{1, 2, 3, 4, 5, 20, 19, 18, 17, 16};
    System.out.println(search(array, 18, 0, array.length - 1, ascComp));//output 7

    array = new int[]{1, 2, 3, 4, 5, 20, 19, 18, 22, 25};
    System.out.println(search(array, 22, 0, array.length - 1, ascComp));//output 8

    array = new int[]{1, 2, 3, 4, 5, 20, 19, 18, 17, 16};
    System.out.println(search(array, 16, 0, array.length - 1, ascComp));//output 9

    array = new int[]{1, 2, 3, 4, 5, 20, 19, 18, 17, 16};
    System.out.println(search(array, 2, 0, array.length - 1, ascComp));//output 1

}

}