O(log n)算法在排序数组中找到最佳插入位置

时间:2014-03-02 03:32:36

标签: arrays algorithm sorting linked-list

我正在尝试制作一个算法,找到将目标插入已排序数组的最佳位置。

目标是返回列表中存在的项目位置,否则返回它将保持列表排序的位置。

所以说我有一个清单:

   0   1   2   3   4    5    6
 ---------------------------------
 | 1 | 2 | 4 | 9 | 10 | 39 | 100 |
 ---------------------------------

我的目标项目为14 它应返回5

的索引位置

我目前有伪代码:

array = generateSomeArrayOfOrderedNumbers()

number findBestIndex(target, start, end)
    mid = abs(end - start) / 2

    if (mid < 2) 
        // Not really sure what to put here
        return start + 1 // ??

    if (target < array[mid])
        // The target belongs on the left side of our list //
        return findBestIndex(target, start, mid - 1)
    else
        // The target belongs on the right side of our list //
        return findBestIndex(target, mid + 1, end)

我不确定此时要放什么。我尝试采用二分搜索方法来解决这个问题,但这是我在5次重写之后能想到的最好的方法。

7 个答案:

答案 0 :(得分:8)

您的代码存在一些问题:

mid = abs(end - start) / 2

startend之间的中间位置,它们之间的距离是一半(向下舍入到整数)。后来你使用它就像它确实是一个有效的索引:

findBestIndex(target, start, mid - 1)

它不是。你可能想在这里使用mid = (start + end) // 2或其他东西。 你也错过了几个指数,因为你跳过了中期:

return findBestIndex(target, start, mid - 1)
 ...
return findBestIndex(target, mid + 1, end)

现在,您的基本情况也必须表达不同。一个好的候选人是条件

if start == end

因为现在你肯定知道你已经完成了搜索。请注意,您还应该考虑所有数组元素都小于target的情况,因此您需要在最后插入它。

我不经常搜索二进制文件,但如果我这样做,这就是

如果你以前从未做过二进制搜索,那么很难做到这一点。如果我进行二分搜索,我通常会使用以下模式:

lo, hi = 0, n // [lo, hi] is the search range, but hi will never be inspected.
while lo < hi:
    mid = (lo + hi) // 2
    if check(mid): hi = mid
    else:          lo = mid + 1

check是单调二元谓词的情况下(从某点开始,false始终为true,从该点开始为lo == hi),在此循环之后{{1} }将是[0..n] check(lo) == true范围内的第一个数字。隐含地认为check(n)是真的(这是这种方法的魔力之一)。

那么所有指数的true单调谓词是什么,包括和在我们的目标位置之后,以及false之前的所有位置?

如果我们考虑一下,我们想要找到数组中第一个大于目标的数字,所以我们只需插入它就可以了:我们很高兴:

lo, hi = 0, n
while lo < hi:
    mid = (lo + hi) // 2
    if (a[mid] > target): hi = mid
    else:                 lo = mid + 1
return lo;

答案 1 :(得分:1)

你走在正确的轨道上。

首先,mid = abs(end + start) / 2

中不需要abs

假设abs在这里表示绝对值,因为end应该始终不小于start,除非你的代码中有一些错误。所以这里绝对没有帮助,但可能隐藏你的问题使其难以调试。

你也不需要if (mid < 2)部分,没有什么特别关于mid小于2。

array = generateSomeArrayOfOrderedNumbers()

int start = 0;
int end = array.size(); 

int findBestIndex(target, start, end){

if (start == end){   //you already searched entire array, return the position to insert
  if (stat == 0) return 0; // if it's  the beginning of the array just return 0.
  if(array[start] > target) return start -1; //if last searched index is bigger than target return the position before it.
else return start;
}
mid = (end - start) / 2

// find correct position 
if(target == array[mid]) return mid;

if (target < array[mid])
{
 // The target belongs on the left side of our list //
return findBestIndex(target, start, mid - 1)
}
else
{
 // The target belongs on the right side of our list //
 return findBestIndex(target, mid + 1, end)
}
}

答案 2 :(得分:1)

我通过计算严格小于(&lt;)的元素数量来解决这个问题。检索到的计数是插入位置。这是Java中随时可用的实现:

int binarySearchCount(int array[], int left, int right, int key) {
    if(left > right) {
        return -1; // or throw exception
    }
    int mid = -1;   //init with arbitrary value 

    while (left <= right) {
        // Middle element
        mid = (left + right) / 2;

        // If the search key on the left half
        if (key < array[mid]) {
            right = mid - 1;
        }
        // If the search key on the right half
        else if (key > array[mid]) {
            left = mid + 1;
        }
        // We found the key
        else {
            // handle duplicates
            while(mid > 0 && array[mid-1] == array[mid]) {
                --mid;
            }
            break;
        }
    }

    // return the number of elements that are strictly smaller (<) than the key
    return key <= array[mid] ? mid : mid + 1;
}

答案 3 :(得分:1)

这是我用过的代码:

int binarySearch( float arr[] , float x , int low , int high )
{
    int mid;
    while( low < high ) {
        mid = ( high + low ) / 2;
        if( arr[mid]== x ) {
            break;
        }
        else if( arr[mid] > x ) {
            high=mid-1;
        }
        else {
            low= mid+1;
        }
    }
    mid = ( high + low ) / 2;
    if (x<=arr[mid])
        return mid;
    else 
        return mid+1;
}

重点是,即使低位等于高,你也必须检查。

例如,请参阅此示例: 0.5-&GT; 0.75 而你正在寻找0.7或1的真实位置。

在两种情况下,当走出while循环时:low = high = 1 但其中一个应放在位置1,另一个放在位置2。

答案 4 :(得分:1)

下面是用于从排序后的数组(包含重复值)中搜索目标值(它是数组的列表)的代码。

它返回可以插入目标值的位置数组。

希望此代码能以任何方式为您提供帮助。

欢迎提出任何建议。

componentWillUnmount

答案 5 :(得分:0)

这是通过使用 python 调整二进制搜索的解决方案。

def func(x, y):
    start = 0
    end = len(x)
    while start <= end:
        mid = (start + end)//2
        print(start, end, mid)
        if mid + 1 >= len(x):
            return mid + 1
        if x[mid] < y and x[mid + 1] > y:
            return mid + 1
        elif x[mid] > y:
            end = mid - 1
        else:
            start = mid + 1
    return 0

func([1,2,4,5], 3)

答案 6 :(得分:-2)

该算法对数组进行二进制搜索,其复杂度为O(log n)。
startend是数组第一个和最后一个元素的索引。 midstartend之间的索引。
第一个if语句检查我们尝试插入的元素是否恰好位于midmid之后的元素之间。 如果mid之后的元素不在数组的末尾,则在这种情况下,我们选择数组的最后一个元素进行比较。此检查很有用,因为在某些情况下,我们可能会在所有log(N)次迭代之前找到目标位置。
如果目标位置不是恰好在mid + 1上,则如果新元素的值大于mid,则在array[mid]的右侧继续搜索,否则在左侧。
startend相遇时,表示找到了解决方案。

以下是C#中的实现:

private static int FindInsertionIndex(int[] array, int newElement)
{
    int start = 0,
        end = array.Length,
        mid = (start + end) / 2;
    while (start != end)
    {
        if (newElement >= array[mid] && newElement <= array[Math.Min(mid + 1, array.Length - 1)])
        {
            start = mid + 1;
            end = start;
        }
        else if (newElement > array[mid])
        {
            start = mid + 1;
            mid += (end - mid) / 2;
        }
        else
        {
            end = mid;
            mid = start + (end - start) / 2;
        }
    }

    return start;
}