首次出现在二分查找中

时间:2011-07-13 08:47:47

标签: java algorithm search binary binary-search

我正在修补一些代码,我意识到我从来不知道的事情。正常的二进制搜索将在数据集中返回多次出现的密钥的随机索引。如何修改以下代码以返回第一次出现?这是人们做的事吗?

//ripped from the JDK
public static int binarySearchValue(InvertedContainer.InvertedIndex[] a, long key) {
    return bSearchVal(a, 0, a.length, key);
}

private static int bSearchVal(InvertedContainer.InvertedIndex[] a, int fromIndex,
                                 int toIndex, long key) {
    int low = fromIndex;
    int high = toIndex - 1;

    while (low <= high) {
        int mid = (low + high) >>> 1;
        long midVal = a[mid].val;

        if (midVal < key)
            low = mid + 1;
        else if (midVal > key)
            high = mid - 1;
        else
            return mid; // key found
    }
    return (low); // key not found. return insertion point
}

15 个答案:

答案 0 :(得分:46)

Jon Skeets的补充帖子:

潜力更快的实现实际上并不难实现,只添加了2行代码,以下是我的工作方式:

    if (midVal < key)
        low = mid + 1;
    else if (midVal > key)
        high = mid - 1;
    else if (low != mid) //Equal but range is not fully scanned
        high = mid; //Set upper bound to current number and rescan
    else //Equal and full range is scanned
        return mid;

答案 1 :(得分:10)

找到 匹配值后,您基本上需要向上走集合,直到找到匹配的条目。

你可以潜在地通过获取一个直接低于你所寻找的密钥的索引来加快它的速度,然后在两者之间做一个二进制斩 - 但我可能会去更简单的版本,除非你有相当多的相同条目,否则很可能“足够有效”。

答案 2 :(得分:3)

您可以通过更清晰的匹配定义来调整现有的搜索算法。您可以看出序列1,3, 5 ,5,5,9中突出显示的5是第一个因为它之前的数字(3)小于5.所以如果中间落在如果[mid-1]小于key,则数组元素等于你只将它视为匹配的键,其他相等的数组元素被视为大于元素。现在你的算法变成了(在包括Jon Skeet建议返回插入点的负数之后):

public static int binarySearch(int[] a, int key) {
    int low=0,high=a.length-1;
    while (low<=high) {
        int mid=(low+high) >>> 1;
        int midVal=a[mid];
        if (midVal < key) 
            low=mid+1;
        else if (mid>0 && a[mid-1]>=key) //we already know midval>=key here
            high=mid-1;
        else if (midVal==key) //found the 1st key 
             return mid;
        else
            return ~mid;      //found insertion point
    }
    return ~(a.length);       //insertion point after everything
}

它使用了更多的比较,但在我的基准测试中比Stev314的版本更快,可能是因为缓存效果。

答案 3 :(得分:2)

您可以实现“下限”算法而不是二进制搜索。例如,使用该算法。在C++/STL中,它的成绩单很简单。下界的算法复杂度也是O(log n)作为二分搜索。这比首先使用二进制搜索更好,而不是线性搜索第一个匹配元素 - 这将具有最坏情况行为O(n)。

答案 4 :(得分:2)

如果您的数据是完整的,那么这个黑客可以提供帮助。 它使用float数组来存储值。

float array[];    //contains all integral values
int searchValue;

int firstIndex = -(binarySearch(array, (float)searchValue - 0.5F) + 1);

它的作用基本上是在搜索值和它之前的整数之间找到值的插入索引。由于所有值都是整数,因此它会找到第一次出现的搜索值。

这也是 log(n)时间。

示例:

import java.util.Arrays;

public class BinarySearch {
    // considering array elements are integers
    float ar[] = new float[] { 1, 2, 3, 3, 4, 4, 5, 9, 9, 12, 12 };

    public void returnFirstOccurrence(int key) {
        int firstIndex = -(Arrays.binarySearch(ar, key - 0.5F) + 1);
        if (ar[firstIndex] != key)
            System.out.println("Key doesn't exist");
        else
            System.out.println("First index of key is " + firstIndex);
    }

    public static void main(String Args[]) throws Exception {
        new BinarySearch().returnFirstOccurrence(9);
    }

}

输出:7

p.s:我在几个编码竞赛中使用了这个技巧,每次都很好用。

答案 5 :(得分:1)

以下算法二进制搜索第一项,其键大于或等于您的搜索键...

while (upperbound > lowerbound)
{
  testpos = lowerbound + ((upperbound-lowerbound) / 2);

  if (item[testpos] >= goal)
  {
    //  new best-so-far
    upperbound = testpos;
  }
  else
  {
    lowerbound = testpos + 1;
  }
}

这不是为Java编写的,我不太清楚,所以可能需要进行一些小调整。请注意,边界是半开放的(下限是包含的,上限是独占的),这对正确性很重要。

这可以适用于其他类似的搜索 - 例如找到最后一个键&lt; =搜索值。

这稍微修改了我之前的问答here

答案 6 :(得分:1)

这是解决方案,我找到了使用二进制搜索获得在排序数组中多次出现的键的较低索引。

int lowerBound(int[] array,int fromIndex, int toIndex, int key)
{
    int low = fromIndex-1, high = toIndex;
    while (low+1 != high)
    {
        int mid = (low+high)>>>1;
        if (array[mid]< key) low=mid;
        else high=mid;
    }
    int p = high;
    if ( p >= toIndex || array[p] != key )
        p=-1;//no key found
    return p;
}

我们必须使用二进制搜索在此代码中稍微更改以使用上限,因此这是代码的工作副本。

 int upperBound(int[] array,int fromIndex, int toIndex, int key)
{
    int low = fromIndex-1, high = toIndex;
    while (low+1 != high)
    {
        int mid = (low+high)>>>1;
        if (array[mid]> key) high=mid;
        else low=mid;
    }
    int p = low;
    if ( p >= toIndex || array[p] != key )
        p=-1;//no key found
    return p;
}

答案 7 :(得分:1)

在此thread中,您可以找到二进制搜索的完整示例(递归版本),以及其他两个版本(基于原始版本),这些示例可让您获取给定键的第一个索引和最后一个索引。

为方便起见,我添加了相关的Junit测试。

答案 8 :(得分:0)

这是scala中解决方案的一种变体。使用模式匹配和递归而不是while循环来获得第一次出现。

def binarySearch(arr:Array[Int],key:Int):Int = {
     def binSearchHelper(lo:Int,hi:Int,mid:Int):Int = {
        if(lo > hi) -1 else {
            if(arr(mid) == key) mid else if(arr(mid) > key){
                binSearchHelper(lo,mid-1,lo + (((mid-1) - lo)/2))
            }else{
                binSearchHelper(mid+1,hi,(mid+1) + ((hi - (mid+1))/2))
            }
        }
     }
    binSearchHelper(0,arr.size-1,(arr.size-1)/2)
}

def findFirstOccurrence(arr:Array[Int],key:Int):Int = {
    val startIdx = binarySearch(arr,key)
    startIdx match {
        case 0 => 0
        case -1 => -1
        case _ if startIdx > 0 => {
            if(arr(startIdx - 1) < key) startIdx else {
                    findFirstOccurrence(arr.slice(0,startIdx),key)
            }
        }
    }
}

答案 9 :(得分:0)

这应该可以解决问题

private static int bSearchVal(InvertedContainer.InvertedIndex[] a, int fromIndex,
                             int toIndex, long key) {
int low = fromIndex;
int high = toIndex - 1;
int result = low;
while (low <= high) {
    int mid = (low + high) >>> 1;
    long midVal = a[mid].val;

    if (midVal < key)
        low = mid + 1;
    else if (midVal > key)
        high = mid - 1;
    else
    {
        result = mid;
        high = mid -1; 
    }
}
return result; 

}

答案 10 :(得分:0)

对于元素的最后一次出现:

static int elementExists(int input[], int element){
    int lo=0;
    int high = input.length-1;
    while(lo<high){
        int mid = (lo + high )/2;
        if(element >input[mid] ){
            lo = mid+1;
        }
        else if(element < input[mid]){
            high= mid-1;
        }
        else if (high != input.length-1) //Change for the Occurrence check
            lo = mid;
        else {
            return mid;
        }
    }
    return -1;
}

第一次出现:

else if (lo != mid){
        high = mid;
}

答案 11 :(得分:0)

一种方法是在整个二进制搜索中保持不变量。在您的特定情况下,不变量将是:

  • array[low] < key
  • key <= array[high]

然后,您可以使用二进制搜索最小化低和高之间的差距。当low + 1 == high时,high就是答案。 C ++中的示例代码:

// check invariant on initial values.
if (array[low] >= key) return low;
if (array[high] < key) return high+1;
// low + 1 < high ensures high is at least low + 2, thus
// mid will always be different from low or high. It will
// stop when low + 1 == high.
while (low + 1 < high) {
  int mid = low + (high - low) / 2;
  if (array[mid] < key) {
    low = mid;   // invariant: array[low] < key
  } else {
    high = mid;  // invariant: array[high] >= key
  }
}
return high;

此示例代码与您的示例代码之间的主要区别是将lowhigh更新为mid而不是mid+1mid-1,因为我们已经检查过array[mid]的值,我们可以保证在更新边界时仍然保持不变量。在开始搜索之前,您需要检查初始值的不变量。

答案 12 :(得分:0)

我认为一种更简单的方法是将最新的mid索引存储在结果变量中xs[mid] == key,然后继续运行二进制搜索。

这是快速代码:

func first<T: Comparable>(xs: [T], key: T) -> Int {
    var lo = xs.startIndex
    var hi = xs.endIndex - 1
    var res = -1
    while lo <= hi {
        let mid = lo + (hi - lo) >> 1
        if xs[mid] == key { hi = mid - 1; res = mid }
        else if xs[mid] < key { lo = mid + 1}
        else if xs[mid] > key { hi = mid - 1 }
    }

    return res
}

此外,如果您要查找密钥的最后一个索引,则需要进行非常小的更改(只需一行)。

func last<T: Comparable>(xs: [T], key: T) -> Int {
    var lo = xs.startIndex
    var hi = xs.endIndex - 1
    var res = -1
    while lo <= hi {
        let mid = lo + (hi - lo) >> 1
        if xs[mid] == key { lo = mid + 1;  res = mid }
        else if xs[mid] < key { lo = mid + 1}
        else if xs[mid] > key { hi = mid - 1 }
    }

    return res
}

答案 13 :(得分:0)

尝试此javascript递归解决方案。从某种意义上说,它是O(log(N))

function solve(A, e) {
  function solve (A, start, end, e, bestUntilNow) {
    if (start > end) {
      if (A[start] === e)
        return start
      return bestUntilNow
    } else {
      const mid = start + Math.floor((end - start) / 2)
      if (A[mid] === e) {
        return solve(A, start, mid - 1, e, mid)
      } else if (e < A[mid]) {
        return solve(A, start, mid - 1, e, bestUntilNow)
      } else {
        return solve(A, mid + 1, end, e, bestUntilNow)
      }
    }
  }
  return solve(A, 0, A.length, e, -1)
}

答案 14 :(得分:0)

给出一个排序数组,其中数组中可能包含重复的元素,例如[1,1,2,2,2,2,3,3],以下具有时间复杂度O(logn)的代码将查找元素首次出现和最后出现的索引在给定的数组中。此方法基于递归JS Binary Search实现,通过与最初匹配元素之后的立即较低或较高索引/元素进行比较(如下文也建议)。

// Find the first occurence of the value using binary search
function binarySearchFirstOccurence(arr, left, right, value) {
  let middle = Math.floor((left+right)/2);
  if (left > right) {
    return -1;
  } else if (arr[middle] === value && (arr[middle-1] < value || middle === 0)) {
    return middle;
  } else if (arr[middle] < value) {
    return binarySearchFirstOccurence(arr, middle + 1, right, value);
  } else {
    // Going lower
    return binarySearchFirstOccurence(arr, left, middle - 1, value);
  }
}

// Find the last occurence of the value using binary search
function binarySearchLastOccurence(arr, left, right, value) {
  let middle = Math.floor((left+right)/2);
  if (left > right) {
    return -1;
  } else if (arr[middle] === value && (arr[middle+1] > value || middle === arr.length - 1)) {
    return middle;
  } else if (arr[middle] > value) {
    return binarySearchLastOccurence(arr, left, middle - 1, value);
  } else {
    // Going higher
    return binarySearchLastOccurence(arr, middle + 1, right, value);
  }
}

function sortedFrequency(arr, value) {
  let left = 0;
  let right = arr.length -1;
  let first = binarySearchFirstOccurence(arr, left, right, value);
  let last = binarySearchLastOccurence(arr, left, right, value);
  if (first === -1 && last === -1) {
    return -1;
  } else if (first === -1 && last !== -1) {
    return 1;
  } else {
    return last-first+1;
  }
}

let arr = [1,1,2,2,2,2,3];
console.log(sortedFrequency(arr, 3));

最佳 乔治