我有一个很大的排序数组,我想快速找到最正确最小元素的索引。 O(logn)时间
例如
INDEX: 0 1 2 3 4 5 6 7 8 9 10 11 12
VALUE: 5 5 5 5 5 5 5 5 6 8 9 9 10 ......... N
The answer is 7
我的观点是二元搜索,但我有疑问
答案 0 :(得分:3)
由于输入已排序,二进制搜索将在 O(log n)中找到解决方案。
对于认为自己 O(n)的人来说,因为他们认为必须进行线性搜索才能找到边界,所以你不会考虑它。< / p>
要查找边界,在找到目标值的索引时,不要停止二进制搜索。您继续执行二进制搜索,直到查看相邻索引。
为了说明(我将编码作为练习留给感兴趣的各方):
INDEX: 0 1 2 3 4 5 6 7 8 9 10 11 12
VALUE: 5 5 5 5 5 5 5 5 6 8 9 9 10
在索引0处找到最小值,因此5
。现在搜索5
直到找到边界,即直到找到左边有值且右边有更高值的相邻索引:
INDEX: 0 1 2 3 4 5 6 7 8 9 10 11 12
VALUE: 5 5 5 5 5 5 5 5 6 8 9 9 10
|-----------|--------------| Found 5, so search right
|-----|--------| Found 8, so search left
|-|---| Found 5, so search right
|-|-| Found 6, so search left
|-| Adjacent, so search complete
在索引7和8之间找到边界,因此最小值的最后一个索引是: 7
这可以推广到查找任何目标数的第一个/最后一个索引,或查找小于目标的最后一个索引,或者第一个索引的数字大于目标,无论目标数是否实际存在于输入中。
<强>更新强>
因为我喜欢NavigableSet
和NavigableMap
的关系搜索操作,所以我认为实现Arrays.binarySearch(int[] a, int key)
的{{1}}等效方法可能很有趣,{{1} } {,ceiling()
和floor()
,以及higher()
的{{1}}和lower()
变体。
为了只实现主二进制搜索逻辑一次,我决定使用函数接口/ lambda表达式来处理比较逻辑。克隆代码或使用first()
可能会表现得更好,但是,我只是 有趣 。
因此,以下是last()
的6种二元搜索方法,以及主搜索逻辑的get()
方法:
boolean
由于有些人似乎很难接受 O(log n)的复杂性,我在主搜索逻辑中添加了统计信息。
这是测试代码,对9个值(n = 9)进行36次测试。对于统计数据,不计算空输入的搜索。
int[]
输出,搜索值5
private
可以看出,执行的平均搜索/**
* Returns the least index in this array with value strictly equal to the given key,
* or {@code -1} if there is no such key.
*/
public static int binaryFirst(int[] a, int key) {
int idx = binaryCeiling(a, key);
return (idx < 0 || a[idx] != key ? -1 : idx);
}
/**
* Returns the greatest index in this array with value strictly equal to the given key,
* or {@code -1} if there is no such key.
*/
public static int binaryLast(int[] a, int key) {
int idx = binaryFloor(a, key);
return (idx < 0 || a[idx] != key ? -1 : idx);
}
/**
* Returns the greatest index in this array with value strictly less than the given key,
* or {@code -1} if there is no such key.
*/
public static int binaryLower(int[] a, int key) {
return binarySearch(a, x -> Integer.compare(x, key) < 0);
}
/**
* Returns the greatest index in this array with value less than or equal to the given key,
* or {@code -1} if there is no such key.
*/
public static int binaryFloor(int[] a, int key) {
return binarySearch(a, x -> Integer.compare(x, key) <= 0);
}
/**
* Returns the least index in this array with value greater than or equal to the given key,
* or {@code -1} if there is no such key.
*/
public static int binaryCeiling(int[] a, int key) {
int idx = binaryLower(a, key) + 1;
return (idx == a.length ? -1 : idx);
}
/**
* Returns the least index in this array with value strictly greater than the given key,
* or {@code -1} if there is no such key.
*/
public static int binaryHigher(int[] a, int key) {
int idx = binaryFloor(a, key) + 1;
return (idx == a.length ? -1 : idx);
}
private static int minComp = Integer.MAX_VALUE; // For stats
private static int maxComp = Integer.MIN_VALUE; // For stats
private static int countComp = 0; // For stats
private static int countSearch = 0; // For stats
private static int binarySearch(int[] a, IntPredicate searchRight) {
if (a.length == 0)
return -1;
int comp = 0; // For stats
int first = 0, last = a.length - 1;
while (first + 1 < last) {
int mid = (first + last) / 2;
comp++; // For stats
if (searchRight.test(a[mid]))
first = mid;
else
last = mid;
}
int result;
if (first == last || first == 0) {
comp++; // For stats
result = (searchRight.test(a[first]) ? first : first - 1);
} else if (last == a.length - 1) {
comp++; // For stats
result = (searchRight.test(a[last]) ? last : last - 1);
} else {
result = first;
}
minComp = Math.min(minComp, comp); // For stats
maxComp = Math.max(maxComp, comp); // For stats
countComp += comp; // For stats
countSearch++; // For stats
return result;
}
比较(public static void main(String[] args) {
System.out.println(" = = < <= >= >");
System.out.println("First Last Lower Floor Ceiling Higher Input");
test();
test(1,1,1,1,1,9,9,9,9,9);
test(1,1,1,5,5,5,9,9,9,9);
test(1,1,1,1,1,1,1,1,1,1);
test(5,5,5,5,5,5,5,5,5,5);
test(9,9,9,9,9,9,9,9,9,9);
test(0,1,2,3,4,5,6,7,8,9);
System.out.printf("%nStats: min=%d, max=%d, avg=%s%n",
minComp, maxComp, countComp / (double) countSearch);
}
private static void test(int... a) {
System.out.printf("%3d%7d%7d%7d%7d%7d %s%n",
binaryFirst(a, 5), binaryLast(a, 5), binaryLower(a, 5),
binaryFloor(a, 5), binaryCeiling(a, 5), binaryHigher(a, 5),
Arrays.toString(a));
}
),最差情况为 = = < <= >= >
First Last Lower Floor Ceiling Higher Input
-1 -1 -1 -1 -1 -1 []
-1 -1 4 4 5 5 [1, 1, 1, 1, 1, 9, 9, 9, 9, 9]
3 5 2 5 3 6 [1, 1, 1, 5, 5, 5, 9, 9, 9, 9]
-1 -1 9 9 -1 -1 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
0 9 -1 9 0 -1 [5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
-1 -1 -1 -1 0 0 [9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
5 5 4 5 5 6 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Stats: min=3, max=5, avg=3.75
比较以执行一次搜索。
我还做了4个10000个值的不同数组,共24次搜索:
3.75
同样,log2(9) = 3.169925
的平均值与搜索10000个值(5
)进行比较。
我认为这充分证明了我对搜索的 O(log n)复杂性的断言。
要完全解决问题:
Stats: min=14, max=15, avg=14.375
输出
14.375