二分法查找的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;
	}
}

 

若有錯誤請指出,大家共同學習謝謝

 

 

 

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