博主声明:
转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。
本文首发于此 博主:威威喵 | 博客主页:https://blog.youkuaiyun.com/smile_running
我们学习数据结构与算法时,肯定会学到过折半查找,它也称作二分搜索,是一种在有序数组中查找某一特定元素的搜索算法。这里有一个前提:有序数组,必须牢记。
折半查找的原理是:在一个数组的中间位置开始查找,如果要查找的目标值恰好等于数组中间的数值,则查找成功,这也是最理想的一种情况。当然也有可能小于它,这时要查找的目标值必定会在前半截数组中,因为数组是有序的。反之,若大于它,则必定在后半截的数组中。以此一直循环或递归操作,直到找到目标值的下标,或者这个数组查无此值为止。
二分查找算法在生活中的例子也比比皆是,比如我之前在一个综艺节目上看到的一个猜物品价格的小游戏。规定这个物品的价格在 800 ~ 1200 元之间,然后猜的离答案最接近的一个获胜,每一位参与者有 5 次机会。虽然是靠运气,也许你盲目的猜 5 次,可能会接近价格。但是用二分查找法来猜,那肯定是比盲目瞎猜来的好。
比如,我们第一次可以先猜 1000 元,然后看价格高了还是低了。低了话就猜 1100 元,高了话就猜 900 元,以此类推下去,虽然只有 5 次机会,但总能无限地接近真实的价格。最后再讲一个笑话,之前在网上看段子的时候无意间看到的。
小故事:【威威猫】从书店出来时,手里抱着一堆书,可能有一本没有消磁,警报响了。他一本一本拿出来靠近警报检查底是哪一本。这个时候,在一旁的保安大哥一把把书抢过来,把所有的书分成两堆,果然只有一堆警报会响,然后把响的那一堆再分成两堆。以此类推下去,于是很快的找出了那一本未消磁的书。
从这个例子我们可以得出一个结论,保安大哥是一位程序员,哈哈。保安大哥用二分查找算法,很快的找出了目标,而【威威猫】一本一本的尝试,循环遍历找出目标,可是花费时间比较长,这就是算法的时间复杂度的比较了。接下来呢,我们来看看代码究竟该如何写。
首先来看看折半查找的动态图,更加形象生动,容易理解。
我们来看 Java 代码如何写吧,代码也非常简单。如下:
package com.xww;
/**
* 二分法查找(前提:有序数组或集合)
*
* @author xww 博主:威威喵 博客:https://blog.youkuaiyun.com/smile_Running
*/
public class BinarySearch {
// 有序数组
static int arr[] = new int[] { 2, 5, 7, 11, 19, 35, 46, 50, 64, 73, 81, 96 };
public static void main(String[] args) {
int len = arr.length;
// 从数组中找目标为 51
int result1 = binarySearch(arr, 51);
if (result1 > 0) {
System.out.println("所在位置:" + result1);
} else {
System.out.println("没有找到");
}
// 从数组中找目标为 50
int result2 = binarySearchRecursion(arr, 0, len, 50);
if (result2 > 0) {
System.out.println("所在位置:" + result2);
} else {
System.out.println("没有找到");
}
}
/**
*
* @param arr
* 查找的数组目标
* @param key
* 查找的值
*/
static int binarySearch(int arr[], int key) {
int low = 0;
int high = arr.length;
// 若low一直小于high,说明查找还在持续中
while (low <= high) {
// 取得low与high的中间位置
int mid = (low + high) / 2;
// 如果要查找的目标key正好等于数组arr[mid]的值,则查找成功
if (key == arr[mid])
return mid;
// 若key比较大,往arr[mid]右边查找
else if (key > arr[mid])
low = mid + 1;
// 若key比较小,往arr[mid]左边边查找
else
high = mid - 1;
}
return -1;
}
/**
* 递归方式
*
* @param arr
* 查找的数组目标
* @param low
* 二分起始位置
* @param high
* 二分结束位置
* @param key
* 查找的值
*/
static int binarySearchRecursion(int arr[], int low, int high, int key) {
// 如果low>high时,说明查找结束了,并未查找成功
if (low > high)
return -1;
int mid = (low + high) / 2;
if (key == arr[mid]) {
return mid;
} else if (key > arr[mid]) {
low = mid + 1;
return binarySearchRecursion(arr, low, high, key);
} else {
high = mid - 1;
return binarySearchRecursion(arr, low, high, key);
}
}
}
其输出结果如下:
如上部分代码,我定义了一个有序的数组,分别用两种方式实现了二分查找法。一种是循环遍历,另一种是递归调用方式。两种方式的目的都是一样的,查找目标 key 的值,返回在数组所在的位置 index。若返回 -1 ,则表示该数组中不存在此 key 值。代码都有了注释, 想必也很简单,无需过多解释了。
这几天开始捡起之前大一学的 C 语言来,虽然 C 和 Java 的语法差不多类似,比较有一点区别在那,刚好复习了这个二分搜索法,索性一起写了这个 C 语言版的例子。代码如下:
#include <stdio.h>
#define LEN 10
/** 冒泡排序 **/
int* sort(int *arr)
{
int i,j;
for(i=0; i<LEN; i++)
for(j=0; j<LEN-i-1; j++)
if(arr[j] > arr[j+1])
{
arr[j] ^= arr[j+1];
arr[j+1] ^= arr[j];
arr[j] ^= arr[j+1];
}
return arr;
}
/** 二分法查找 **/
int binarySearch(int arr[], int key)
{
int low = 0;
int high = LEN;
while(low <= high)
{
/** 折半 **/
int mid = (low + high) / 2;
if(arr[mid] == key)
return mid;
else if(arr[mid] < key) /** key 在数组的右半截 **/
low = mid + 1;
else /** key 在数组的左半截 **/
high = mid - 1;
}
return -1;
}
void printArray(int* arr)
{
int i;
for(i=0; i<LEN; i++)
printf("%d\t", arr[i]);
printf("\n");
}
void main()
{
int arr[LEN] = {5,3,-7,16,5,6,-11,14,65,96};
int index;
/** 进行排序 **/
int *new_arr = sort(arr);
printArray(new_arr);
/** -1 表示没有找到 **/
index = binarySearch(new_arr, 65);
printf("查找结果,index = %d\n", index);
}
其输出结果如下:
关键性算法都是差不多类似的,只不过 C 语言的一些语法一时忘记了,脑子里还存有一点点印象,看一看基础语法还是能回忆起来的。想当初刚学 C 语言还是一脸懵逼的,很简单的算法都不会写,哈哈,现在要开始一点一点的捡起来了。