二分查找
一开始写的是一个查到中间的那个值
查找中间的那个值
思路
一开始就是直接在循环里做判断,如果找到target
就break
这样只能找到target
中间的那个位置,不是第一次出现的位置,需要注意的点就是因为循环里是先执行的L = M + 1;
和R = M - 1;
,所以如果target
出现在了第一个位置或者最后一个位置,就会L=R
(),然后循环条件就要变成L<=R
,
代码
public static void main(String[] args) {
int A[] = { 0, 1, 1, 1, 1, 3 };
int len = A.length;
int x = 1;
int L = 0, R = len - 1, M = 0;
// 第一次循环判断条件用的是L!=R,然后答案在最边上的时候就会出错
while (L <= R) {
// 这里用(R-L)/2 + L,可以防止溢出(如果刚好是int类型的最大值就GG了)
M = L + (R - L) / 2;
if (A[M] == x)
break;
else if (A[M] > x) // x的位置是[L,M-1]
R = M - 1;
else // x的位置是[M+1,R]
L = M + 1;
}
if (A[M] == x)
System.out.println(M); // 3
else
System.out.println(-1);
}
第一次出现的位置
思路
第一次写:
整体思路其实跟第一次写的还是差不多,就是要注意这里不需要中途找到后break
,然后找到后也要继续执行(也就是代码里的else
的部分),当相等时执行R = M
,就是说‘’至少已经找到了一个,但是左边可能还有‘’,但是为了防止左边其实没有了,就要把M这个地方也带上
过了两天写:
因为加了后面的判断,所以输出的就是第一次出现的位置,找不到就返回-1,但是如果不加判断,最后L
的位置就是数组中第一个出现的大于等于target的值的位置,同理下面那个最后一次出现的位置就是从右往左数第一个出现的小于等于target的位置,当然都是排好序的数组
M的取整方式
如果要找从左往右第一个大于等于target的位置,那就要考虑到有可能出现重复元素,或者是没有出现这个值;如果有重复元素,那就是A[M]==target
的时候,需要R = M(为什么呢?因为现在已经找到一个target了,但是它左边可能还有,也可能没有,就需要把这个位置保留下来,既要保留左边的又要保留这个位置,就需要R = M
);如果用R = M
,就要考虑M是向下还是向上取整,如果M向上取整就可能出现M = R
,就会陷入死循环(例子:L = 1,R = 2,M = 2,然后又用了R = M,相当于没有更新R),这个时候就需要向下取整;这里的M取整方式在归并排序的也会遇到相同的问题。
如果没有target值,那么就会出现最后的位置的值是满足A[M] >= target;就是从左往右数第一个大于等于target的值
后面的找到从右往左数第一个小于等于target的位置M是向上取整,道理也是类似的,这里就不说了
代码
public static void main(String[] args) {
int A[] = { 0, 1, 1, 1, 3 };
int len = A.length;
int x = 2;
int L = 0, R = len - 1, M;
// 最后的结束条件就是L==R,意味着答案区间里只有一个值了
while (L < R) {
M = L + (R - L) / 2;
if (A[M] < x)// 说明答案在[M+1,R]之间(闭区间)
L = M + 1;
else // 说明答案是在[L,M]之间(闭区间),
// 因为有可能是x==A[M],这个时候如果用R = M-1就会漏掉这个
R = M;
}
if (A[L] == x)
System.out.println(L);// 1
else
System.out.println(-1);
}
最后一次出现的位置
思路
如果要找最后一次出现的位置,就是说如果找到了,我们还要往右推看看还有没有,上面的就是往左推R = M
,这里就需要有L = M
,当找到相等时,也就是下面的else
部分
但是这里也有一个要注意的地方,如果target
的位置是最后一个有可能陷入死循环(学二分查找没有死循环的人生是不完美的,哈哈哈),举个例子,如果target
的位置是R = 5
,最后的M = 4
,就会一直执行L = (5+4)/2 = 4
,一直上不去,就是因为M
取整是向下取整的,所以要让M
向上取整M = L+(R-L+1)/2
,上面找第一个出现的位置是需要向下取整的,因为系统就是向下取整的,所以没有专门做这个事
代码
public static void main(String[] args) {
int a[] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 3 };
int len = a.length;
int x = 2;
int L = 0, R = len - 1, M;
// 最后的结束条件就是L==R
while (L < R) {
//这里M必须要向上取整,如果不向上取整可能会死循环
M = L + (R - L + 1) / 2;
if (a[M] > x)// 说明答案在[L,M-1]之间(闭区间)
R = M - 1;
else // 说明答案是在[M,R]之间(闭区间),
// 因为有可能是x==a[M],这个时候如果用L = M+1就会漏掉这个
L = M;
}
if (a[L] == x)
System.out.println(L);
else
System.out.println(-1);
}
总结
之前简单的看过二分查找,今天多看了一些,所以有一点输出,然后就是二分查找有好多中写法,我上面写的很像就是为了我自己好记,然后那个M的取整方式就是
- 如果代码中是用的L = M,把L不断往右push,那么M向上取整
(M = L + (R - L + 1)/2))
; - 如果代码中是用的R = M,把R不断往左push,那么M向下取整
(M = L + (R - L)/2)
下面的是模板
while(L<R){
M = L + ( R - L ) / 2 // M = L + (R - L + 1) / 2;
if(/*答案在[M+1,R]之间*/)
L = M + 1;
else//答案在[L,M]之间
R = M;
}