对半切快速解题——二分法

333 阅读1分钟

如何快速提高算法效率?

在某些实际问题中,我们可以考虑二分法。

1. 二分法求解问题

1.1 存在问题

题目:查找某个数在有序数组中是否存在

1.1.1 算法原理

找到数组中间位置mid,对比 arr[mid]=x 和题目所求的数 num 的大小。如果 num>x,说明所找的数一定在数组右半部分;如果 num<x,说明所找的数一定在数组左半部分。接着在右半部分/左半部分继续进行二分,直到找到 x 或二分到只剩一个数。

1.1.2 算法过程图示

假设有序数组为[2 3 4 8 16 19 23 35 39 45 56],num=45。

数组长度为N=11。

首先min=0,max=10,mid=N/2=11/2=5,找到arr[5]=19,比较19<45,因此45如果存在,那么一定是在19右侧的数组中,因为数组是有序的,19左侧的数组是全部小于19的。接着min=mid+1=6,max=10,mid=(min+max)/ 2 = 8 ,找到 arr[8]=39,比较39<45,因此45如果存在,那么一定是在39右侧的数组中。接着min=mid+1,max=10,mid=(min+max)/ 2 = 9,找到 arr[9]=45,因此找到所求数字。

时间复杂度为O(logN)。

1.1.3 算法核心代码


import java.util.Arrays;

public class BSExist {
	public static boolean exist(int[] sortedArr, int num) {
		if(sortedArr==null||sortedArr.length==0){
			return false;
		}
		int min=0;
		int max=sortedArr.length-1;
		int mid=0;
		while(min<max) {
			mid=(min+max)/2;
			if(sortedArr[mid]==num) {
				return true;
			}else if(sortedArr[mid]>num) {
				max=mid-1;
			}else {
				min=mid+1;
			}
		}
		return sortedArr[min]==num;
	}
	
	
	
	public static void main(String[] args) {
		int[] arr = {2,3,4,8,16,19,23,35,39,45,56};
		System.out.println(Arrays.toString(arr));
		System.out.print(exist(arr,9));
		
	}

}

1.2 无序数组局部最小问题

1.2.1 局部最小问题

数组长度为N,无序,任意相邻数不相等

①数组头部(下标为0、1):如果arr[0]<arr[1],则arr[0]是局部最小

②数组尾部(下标为N-2、N-1):如果arr[N-1]<arr[N-2],则arr[N-1]是局部最小

③数组中部(非头部,非尾部):如果arr[x]<arr[x-1],arr[x]<arr[x+1],则arr[x]是局部最小

1.2.2 算法原理

第一步判断①、②情况能否找到局部最小,如果不能,则局部最小一定在中间,如下图所示,趋势一开始是下降,最后是上升,说明中间部分一定存在极值,即局部最小。

接着二分数组,按照第一步继续判断局部最小,以此类推。

1.2.3 算法过程图示

例如数组[6 5 1 2 4 3 7 8]

第一步判断下标为0、1以及下标为6、7是否存在局部最小,如图可知不存在。接着对数组进行二分,min=0,max=7,mid=(0+7)/2=3,此时查看下标为3的数arr[3]=2左右两边的变化趋势,如图得到左边可能存在局部最小。再对左边部分数组进行二分,min=0,max=3,mid=(0+3)/2=1,此时查看下标为1的数arr[1]=5左右两边的变化趋势,如图得到右边可能存在局部最小,此时只有三个数,且变化趋势满足局部最小,则中间的数arr[2]=1为局部最小。

时间复杂度为O(N)。

1.2.4 算法核心代码

public class BSAwesome {
	public static int getLessIndex(int[] arr) {
		if(arr == null || arr.length ==0) {   //判断数组是否为空
			return -1;
		}
		if(arr.length ==1 || arr[0]<arr[1]) {    //查看头部
			return 0;
		}
		if(arr[arr.length-1]<arr[arr.length-2]) {      //查看尾部
			return arr.length-1;
		}
		int left=1;    //设置最左,方便二分
		int right =arr.length-2;   //设置最右,方便二分
		int mid = 0;
		while(left<right) {
			mid = (left+right)/2;    //二分
			if(arr[mid]>arr[mid-1]) {   //二分左侧为上升趋势则说明左侧存在局部最小
				right = mid -1;
			}else if(arr[mid]>arr[mid+1]) {   //二分右侧为下降趋势则说明右侧存在局部最小
				left=mid+1;
			}else {
				return mid;//找到局部最小
			}
		}
		return left;
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] arr= {9,5,2,9,8,6,7,1,10};
		System.out.print(getLessIndex(arr));
	}
}