二分法查找的java实现

                                算法 .二分法

二分法也就是折半查找,在 有序 的数列中查找指定的元素,设定最小索引(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的两个端点28,最后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;
	}
}

 

若有错误请指出,大家共同学习谢谢

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章