前言
本章总结二分查找、旋转数组及扩展。
☆☆☆ 算法目录导航页,包含基础算法、高级算法、机器学习算法等☆☆☆
1.概念
二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求:
① 线性表必须采用顺序存储结构;
② 而且表中元素按关键字有序排列。
2.原理
将数组分为3部分:中值左、中值、中值右。将要查找的数据跟中值比较,小于中值在左半部分找,等于中值返回,大于中值在右半部分查找。左右部分查找过程同理,是一个递归过程。如图,某数组中查找元素6:
3.代码示例
/**
* 测试
* @param args
*/
public static void main(String[] args) {
int [] arr = {2,4,6,9,10,20,30,50,60};
int k = 6;
System.out.println("查找K为:"+ k + ",位置为:"+binarySearch(arr, k, 0, arr.length - 1));
}
/**
* 二分查找
* @param k
* @return k的索引
*/
static int binarySearch(int[] arr ,int k,int left ,int right){
while(left <= right){
int mid = (left +right) >> 1;//中值
if(k < arr[mid]){ //比较
right = mid -1 ;//移动右指针到 mid-1位置
}
else if(k > arr[mid]){
left = mid + 1; //移动左指针到 mid+1位置
}
else{
return mid;
}
}
return -1;
}
结果打印:
以上为最简单的二分查找原理,我相信理解起来应该很容易,下面我们要举一反三,看一下二分查找的变种或应用。
4.旋转数组中查找最小值
我相信很多人对这个并不陌生,没错,它来自剑指offer,这个考察的是对二分查找的理解和应用能力。原题为:输入一个递增排序的数组的一个旋转,输出最小元素。例如,输入{3,4,5,1,2},输出1。
我们使用效率较高的二分查找,为什么使用二分?因为数组是部分递增排序,而且前半部分比后半部分值都大。所以,基本符合二分的前提条件。
1. 首先,简单分析一下二分查找都干了些什么?不就是确定中值,然后进行比较么。所以我们还是从中值的角度去考虑,那么此时mid会有两种情况:
① mid位于左边升序部分,② mid位于右边升序部分。
2. 旋转数组求最小值过程图解
特殊情况,mid、right和left值都相等,此时只能进行顺序查找:
3. 代码示例
package com.cnepay.algorithm;
/**
* 旋转数组中查询最小值
* @author wxq
*
*/
public class SearchRotateArray {
/**
* 测试
* @param args
*/
public static void main(String[] args) {
int [] arr = {10,20,30,50,60,2,4,6,9};
// int [] arr = {};
int index = findMinNumInRotateArray(arr, 0, arr.length-1);
System.out.println(index);
if(index != -1){
System.out.println("最小值位置:" + index + ",值为:"+arr[index] );
}
else{
System.out.println("请检查入参!");
}
}
/**
* 查找旋转数组的最小值
* @param array
* @param left
* @param right
* @return
*/
static int findMinNumInRotateArray(int [] array,int left,int right) {
//校验
if(array == null || array.length <= 0) return -1;
int mid = 0;
while(left < right ){
mid = (left+right) >> 1;//中值
//左右指针相邻,终止
if(right - left == 1){
mid = right;
break;
}
//特殊情况
if(array[mid] == array[left] && array[left] == array[right]){
return findInOrder(array,left,right);
}
//mid在左边递增数组中,最小值在右边,移动左指针
if(array[mid] >= array[left]){
left = mid ;
}
//mid 在右边递增数组中,最小值在左边,移动右指针
else if(array[mid] <= array[right]){
right = mid ;
}
}
return mid;
}
//顺序查找
static int findInOrder(int [] array,int left,int right){
int minNum = array[left];
for(int i= left+1;i<right;i++){
if(array[i] < minNum){
minNum = array[i];
}
}
return minNum;
}
}
4.区别于普通二分查找:
① 查找k值的方式不同,普通的是直接比较k,而这里k的确定是根据左右指针相邻 和 旋转数组的特性,比如30 2 ,此时2肯定为最小值;
② 有特殊情况,需要单独处理。
③ 共同点是:都是通过比较大小,然后移动指针。
5.旋转数组中查找指定k
这个看上去又复杂一点了,上一小节中查找最小值,确定的条件就是right - left = 1,即左右指针相邻,但是这里的查找k值情况就多了:判断mid位置的同时,还要判断k值的位置。
其实,思路跟旋转数组是一致的。只不过增加了k值位置判断的情况。具体就不画图分析了,直接上代码吧。代码示例如下:
/**
* 旋转数组中查找指定k值
* @author wxq
*
*/
public class SearchKRotateArray {
/**
* 测试
* @param args
*/
public static void main(String[] args) {
// int [] arr = {10,20,30,50,60,2,4,6,9};
int [] arr = {30};
int k = 30;
int index = findKInRotateArray(arr,k, 0, arr.length-1);
System.out.println("查找k为:" + k + ",位置为:"+index );
}
/**
* 旋转数组中查找k值
* @param arr
* @param k
* @param left
* @param right
* @return
*/
static int findKInRotateArray(int[] arr,int k,int left ,int right){
//校验
if(arr == null || arr.length <= 0) return -1;
while (left <= right){
int mid = (left + right) >> 1;//中值
//k正好等于mid,返回
if (k == arr[mid])
return mid;
//特殊情况,顺序查找
if (arr[mid] == arr[left] && arr[left] == arr[right]){
return findInOrder(arr,k);
}
//左边部分是递增数组
if (arr[mid] >= arr[left]){
//k值位置判断
if (k >= arr[left] && k <= arr[mid])
right = mid - 1;
else
left = mid + 1;
}
//右边部分是递增数组
else{
//k值位置判断
if (k >= arr[mid] && k <= arr[right])
left = mid + 1;
else
right = mid - 1;
}
}
return -1;
}
/**
* 顺序查找
* @param arr
* @param k
* @return
*/
static int findInOrder(int[] arr, int k) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == k)
return i;
}
return -1;
}
}