算法 .二分法
二分法也就是折半查找,在 有序 的数列中查找指定的元素,设定最小索引(low)和最大索引(height-1)还有中间值mid((low+height-1)/2),这种查找,如果中间值比指定元素小让low=mid+1,如果中间值比指定元素大,让height=mid-1;
以上是大体思路,下面展示两个动图,帮助理解
第一个图表示了二分法的整体过程;
第二个图表示了原方法的整体过程;
观察可得二分法的优越性!
代码实现
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
public class Main2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int arr[] = { 2, 5, 6, 8, 9, 4, 7 };
Arrays.sort(arr);
int deix(索引) = getxiabiao(arr, 7);
}
public static int getxiabiao(int[] arr, int key) {
int heigh = arr.length-1;
int low = 0;
int mid = 0;
while (low <= heigh) {
mid = low + (heigh - low)/2;
if (arr[mid] == key) {
return mid;
} else if (arr[mid] < key) {
low = mid + 1;
} else if (arr[mid] > key) {
heigh = mid - 1;
}
}
return -1;
}
}
中间值的设定有两种方法;
算法一: mid = (low + high) / 2
算法二: mid = low + (high – low)/2
乍看起来,算法一简洁,算法二提取之后,跟算法一没有什么区别。但是实际上,区别是存在的。
算法一的做法,在极端情况下,(low + high)存在着溢出的风险,进而得到错误的mid结果,导致程序错误。
而算法二能够保证计算出来的mid,一定大于low,小于high,不存在溢出的问题。
例题:
给定三个整数数组
A = [A1, A2, … AN],
B = [B1, B2, … BN],
C = [C1, C2, … CN],
请你统计有多少个三元组(i, j, k) 满足:
1. 1 <= i, j, k <= N
2. Ai < Bj < Ck
输入
第一行包含一个整数N。 第二行包含N个整数A1, A2, ... AN。 第三行包含N个整数B1, B2, ... BN。 第四行包含N个整数C1, C2, ... CN。
输出
一个整数表示答案
样例输入
3
1 1 1
2 2 2
3 3 3
题目分析:
就是遍历A组找小于B【i】并且在C组找大于B【i】的个数,因为数据很大,所以直接暴力搜索肯定超时,所以需要二分法搜索比指定元素大的第一个元素下标
A组当中比如arr={1,2,3,4,5}中找比4小的第一个元素 返回结果是 2 然后2+1就是小于4的个数;
C组当中比如arr={1,2,3,4,5}中找比3大的第一个元素 返回下标是 3 然后用长度5减去3就是大于3的个数;
知识点一(在一以升序序列中找到第一个比指定元素大的元素位置):
代码实现:
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int []arr = {3,3,3};
System.out.println(er(arr,4));
}
public static Integer er(int[] C, int a) {
int start = 0;
int end = C.length - 1;
int sum = C.length; //不能是C.length-1,情况如 3 3 3 在这组数据脸面找比四大的如果是C.length-1答案是1.反之 0
while (start <= end) {
int mid = start + (end - start) / 2;
if (C[mid] < a) {
start = mid + 1;
} else if (C[mid] > a) {
sum = mid;
end = mid - 1;
}else if(C[mid]==a) {
sum=mid+1;
start = mid+1;
}
}
return sum;
}
}
代码原理:
一:
在C组中找比B【i】大的第一个元素:
情况一,指定元素在这组数列中.比如arr中找比5大的第一个元素的下标
arr={2,5,8,9,12,15,16,18,20}
1.当arr【mid】<5,start+=1;证明那个数在右面所以将左端点右移
2.当arr【mid】>5,end-=1;如果大于5,那么就有可能是第一个大于5的数,将这个下标暂时给sum,又因为找离指定元素最近的一个,所以要将右端点左移,找还有没有大于5的数;
3.当arr【mid】=5,那么那么5右面的一个数肯定是比5大的第一个元素,所以sum=mid+1;之前想不明白为什么mid+1肯定是比5大的第一个元素了,直接return mid+1就好了,为什么还有继续控制start=mid+1;因为如果arr={5,5,5},那么返回的下标直接就是1,然后长度(3)减去1就是2,证明大于5的个数是2,明显是错误的
所以!必须让start=mid+1;这个方法很巧妙,那么又有一个例子来了
arr={1,4,6,8,18,20} 找比6大的一个元素的下标;start = 0, end =5
第一步:mid=(s+e)/2=2;arr【2】=6 所以sum=mid+1=3;start=mid+1=3;end=5;
第二步:mid=(s+e)/2=4;arr【4】=18并且大于6,所以sum=4,这个时候我就在想这也不对啊,答案肯定是下标3啊,这咋4赋值给sum了啊,再看要找比6大的第一个数,那么在6右面的肯定都是比6大的,所以比6大的肯定都要走这一步啊end=mid-1;所以end从下标5,到4,到3,又因为只要比6大就有可能是比6大的第一个数,所以要将下标赋给sum,所以最后start=end
(start+end)/2=start, sum还是等于了3;
再看这个例子 arr={6,6,6} 找比6大的数
因为每一个数都等于6,所以一直循环sum=mid+1;start=mid+1;最后sum=3 长度-3=0 所以在这组数列中没有比6大的数
再有细节就是sum的初始化 arr.length ,比如数组arr={6,6,6} 找7大的第一个数,最后sum还是等于arr.length 答案 0
情况二,指定元素不在这组数列中.比如arr中找比5大的第一个元素的下标
arr={2,(5),8,9,12,15,16,18,20}
如果指定元素不在这组数列当中,那么start和end会无限趋近于5的两个端点2和8,最后8的下标赋给sum
二:
在A组中找比B【i】小的数:
代码实现;
import java.math.BigInteger;
import java.util.*;
public class Main2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int []arr = {1,3,4};
System.out.println(er(arr,2));
}
public static Integer er(int[] C, int a) {
int start = 0;
int end = C.length - 1;
int sum = -1;
while (start <= end) {
int mid = start + (end - start) / 2;
if (C[mid] < a) {
sum=mid;
start = mid + 1;
} else if (C[mid] > a) {
end = mid - 1;
}else if(C[mid]==a) {
sum=mid-1;
end = mid-1;
}
}
return sum;
}
}
与在C组中找比指定元素大的数原理上大同小异,
例如arr={1,4,6,8,18,20} 找比6小的第一个元素
异处:当arr【mid】=指定元素时、end = mid-1,sum=mid-1,因为在指定元素左面肯定都是比指定元素小的,start从 0 到 1 到 2;
返回sum的值等于2;最后sum+1等于arr中有多少个比指定元素小的数;
end = mid-1的作用就是当出现arr中的所有元素等于指定元素例如在arr={6,6,6,6}中找比6小的数;
end=mid-1之后 因为每一个数都等于指定元素,最后sum=-1;sum+1等于答案
细节sum的初始值-1,arr{6,6,6}中找比5小的数,sum返回-1,sum+1=0,所以无比5小的数
题目代码:
import java.math.BigInteger;
import java.util.*;
public class Main{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] A = new int[n];
int[] B = new int[n];
int[] C = new int[n];
for (int i = 0; i < n; i++) {
A[i] = sc.nextInt();
}
for (int i = 0; i < n; i++) {
B[i] = sc.nextInt();
}
for (int i = 0; i < n; i++) {
C[i] = sc.nextInt();
}
BigInteger jk = BigInteger.valueOf(0);
Arrays.sort(A);
Arrays.sort(B);
Arrays.sort(C);
for (int i = 0; i < B.length; i++) {
int x = fen(A, B[i]) + 1;
int y =er(C, B[i]);
jk =jk.add(BigInteger.valueOf(x).multiply(BigInteger.valueOf(y)));
}
System.out.println(jk);
}
public static Integer fen(int[] A, int a) {
int start = 0;
int end = A.length - 1;
int sum = -1;//注意sum的初始值
while (start <= end) {
int mid = start + (end - start) / 2;
if (A[mid] > a) {
end = mid - 1;
} else if (A[mid] < a) {
sum = mid;
start = mid + 1;
}else if(A[mid]==a) {
sum=mid-1;
end = mid-1;
}
}
return sum;
}
public static Integer er(int[] C, int a) {
int start = 0;
int end = C.length - 1;
int sum = C.length;
while (start <= end) {
int mid = start + (end - start) / 2;
if (C[mid] < a) {
start = mid + 1;
} else if (C[mid] > a) {
sum = mid;
end = mid - 1;
}else if(C[mid]==a) {
sum = mid+1;
start = mid+1;
}
}
return C.length-sum;
}
}
若有错误请指出,大家共同学习谢谢