我总是遇到最艰难的时刻,而且我还没有看到对于那些被认为非常普遍和高度使用的东西的明确解释。
我们已经知道标准二进制搜索。给定起始下限和上限,找到中间点(低+高)/ 2,然后将其与数组进行比较,然后相应地重新设置边界等。
然而,要调整搜索以查找(按升序排列的列表)需要的差异是什么:
似乎每个案例都需要对算法进行非常小的调整,但我永远无法使它们正常工作。我尝试改变不等式,返回条件,改变边界的更新方式,但似乎没有一致。
处理这四种情况的最终方法是什么?
答案 0 :(得分:1)
二进制搜索(至少我实现它的方式)依赖于一个简单的属性 - 谓词对于区间的一端是成立的,而对于另一端则不成立。我总是认为我的间隔在一端关闭而在另一端打开。我们来看看这段代码:
int beg = 0; // pred(beg) should hold true
int end = n;// length of an array or a value that is guranteed to be out of the interval that we are interested in
while (end - beg > 1) {
int mid = (end + beg) / 2;
if (pred(a[mid])) {
beg = mid;
} else {
end = mid;
}
}
// answer is at a[beg]
这适用于您定义的任何比较。只需将pred
替换为<=target
或>=target
或<target
或>target
。
循环退出后,a[beg]
将是给定不等式所持有的最后一个元素。
因此,我们假设(如评论中所建议的)我们希望找到a[i] <= target
的最大数字。然后,如果我们使用谓词a[i] <= target
,代码将如下所示:
int beg = 0; // pred(beg) should hold true
int end = n;// length of an array or a value that is guranteed to be out of the interval that we are interested in
while (end - beg > 1) {
int mid = (end + beg) / 2;
if (a[mid] <= target) {
beg = mid;
} else {
end = mid;
}
}
循环退出后,您要搜索的索引将为beg
。
另外,根据比较,您可能必须从数组的右端开始。例如。如果您要搜索最大值&gt; = target,您将执行以下某种操作:
beg = -1;
end = n - 1;
while (end - beg > 1) {
int mid = (end + beg) / 2;
if (a[mid] >= target) {
end = mid;
} else {
beg = mid;
}
}
您要搜索的值将使用索引end
。请注意,在这种情况下,我会考虑间隔(beg, end]
,因此我稍微修改了起始间隔。
答案 1 :(得分:0)
基本二进制搜索是搜索与目标键相等的位置/值。虽然它可以扩展到 找到满足某些条件的最小位置/值 ,或者 找到满足某些条件的最大位置/值 强>
假设数组是升序,如果找不到满意的位置/值,则返回-1。
代码示例:
// find the minimal position which satisfy some condition
private static int getMinPosition(int[] arr, int target) {
int l = 0, r = arr.length - 1;
int ans = -1;
while(l <= r) {
int m = (l + r) >> 1;
// feel free to replace the condition
// here it means find the minimal position that the element not smaller than target
if(arr[m] >= target) {
ans = m;
r = m - 1;
} else {
l = m + 1;
}
}
return ans;
}
// find the maximal position which satisfy some condition
private static int getMaxPosition(int[] arr, int target) {
int l = 0, r = arr.length - 1;
int ans = -1;
while(l <= r) {
int m = (l + r) >> 1;
// feel free to replace the condition
// here it means find the maximal position that the element less than target
if(arr[m] < target) {
ans = m;
l = m + 1;
} else {
r = m - 1;
}
}
return ans;
}
int[] a = {3, 5, 5, 7, 10, 15};
System.out.println(BinarySearchTool.getMinPosition(a, 5));
System.out.println(BinarySearchTool.getMinPosition(a, 6));
System.out.println(BinarySearchTool.getMaxPosition(a, 8));
答案 2 :(得分:0)
您需要的是二进制搜索,可让您在最后一步参与此过程。典型的二进制搜索将收到(array, element)
并生成一个值(通常是索引或未找到)。但是如果你有一个修改过的二进制文件接受在搜索结束时调用的函数,你可以覆盖所有情况。
例如,在Javascript中可以轻松测试,以下二进制搜索
function binarySearch(array, el, fn) {
function aux(left, right) {
if (left > right) {
return fn(array, null, left, right);
}
var middle = Math.floor((left + right) / 2);
var value = array[middle];
if (value > el) {
return aux(left, middle - 1);
} if (value < el) {
return aux(middle + 1, right);
} else {
return fn(array, middle, left, right);
}
}
return aux(0, array.length - 1);
}
允许您使用特定的返回功能覆盖每个案例。
function(a, m) { return m; }
function(a, m, l, r) { return m != null ? a[m] : r + 1 >= a.length ? null : a[r + 1]; }
function(a, m, l, r) { return (m || r) + 1 >= a.length ? null : a[(m || r) + 1]; }
function(a, m, l, r) { return m != null ? a[m] : l - 1 > 0 ? a[l - 1] : null; }
function(a, m, l, r) { return (m || l) - 1 < 0 ? null : a[(m || l) - 1]; }
答案 3 :(得分:0)
我遇到了完全相同的问题,直到我发现循环不变式和谓词是解决所有二进制问题的最好,最一致的方法。
第1点:想到谓语
通常,对于所有这4种情况(以及对相等性的常规二进制搜索),请将它们想象为谓词。因此,这意味着某些值满足谓词,而有些则失败。因此,以这个目标为5的数组为例:
[1、2、3、4、6、7、8]。找到大于5的第一个数字基本上等同于找到此数组中的第一个数字:[0,0,0,0,1,1,1]。
第2点:包括搜索边界
我喜欢两端都有包容性。但是我可以看到有些人喜欢开始包容和结束包容(在len而不是len -1上)。我喜欢将所有元素都包含在数组中,因此在引用a [mid]时,我不认为这是否会使我超出范围。所以我的偏好是:包容!!!
第3点:循环条件<=
因此,我们什至要在while循环中处理大小为1的子数组,而while循环结束时不应有未处理的元素。我真的很喜欢这种逻辑。它总是坚如磐石。最初,所有元素都未经检查,基本上是未知的。表示不检查[st = 0,到end = len-1]范围内的所有内容。然后,当while循环结束时,未检查的元素的范围应为大小为0的数组!
第4点:不变式
由于我们定义了start = 0,end = len-1,因此不变式将如下所示:
开始时剩下的任何东西都小于目标。
任何终止权均大于或等于目标。
第5点:答案
一旦循环结束,基本上基于循环不变性,开始左侧的任何内容都会更小。因此,这意味着开始是大于或等于目标的第一个元素。
同样,末端右边的任何值都大于或等于目标。因此,这意味着答案也等于end +1。
代码:
public int find(int a[], int target){
int start = 0;
int end = a.length - 1;
while (start <= end){
int mid = (start + end) / 2; // or for no overflow start + (end - start) / 2
if (a[mid] < target)
start = mid + 1;
else // a[mid] >= target
end = mid - 1;
}
return start; // or end + 1;
}
变化:
<< / strong>
等效于找到第一个0。因此基本上只返回更改。
return end; // or return start - 1;
>
将if条件更改为<=,否则将为>。没有其他变化。
<=
与>,return end; // or return start - 1;
因此,对于该模型,通常对于所有5个变体(<=,<,>,> =,常规二进制搜索),仅条件是if更改中的条件和return语句。当您考虑不变量(第4点)和答案(第5点)时,找出这些小的变化非常容易。
希望对任何阅读此书的人都可以澄清。如果有什么不清楚的感觉像魔术,请ping me进行解释。了解了这种方法之后,用于二进制搜索的所有内容都应该像白天一样清晰!
要点:尝试包括开始但不包括结尾也是一个很好的做法。因此该数组最初为[0,len)。如果您可以编写不变式,while循环的新条件,答案以及清晰的代码,则意味着您已了解了这一概念。