C/C++ 二分查找算法(非递归实现)

Posted cpp_learner

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C/C++ 二分查找算法(非递归实现)相关的知识,希望对你有一定的参考价值。

一直都想学一下二分查找算法,奈何一直没时间;刚好最近,我的某位粉丝私信找我解答一道题目,里面要求要使用二分查找算法去实现,所以趁着这个机会,赶紧把二分查找算法学了一遍,现在记录一下自己的学习心得!


一、查找的的定义

首先简单了解一下查找算法的定义。

查找 又称检索或查询,是指在查找表中找出满足一定条件的结点或记录对应的操作。

查找表 在计算机中,是指被查找的数据对象是由同一类型的记录构成的集合,如顺序表,链表、二叉树和哈希表等。

查找效率 查找算法中的基本运算是通过记录的关键字与给定值进行比较,所以查找的效率同常取决于比较所花的时间,而时间取决于比较的次数。通常以关键字与给定值进行比较的记录个数的平均值来计算。

查找操作及分类
操作

  1. 查找某个“特定的”数据元素是否存在在查找表中
  2. 某个“特定的”数据元素的各种属性
  3. 在查找表中插入一个数据元素
  4. 从查找表中删除某个数据元素

分类
若对查找表只进行(1) 或(2)两种操作,则称此类查找表为静态查找表

若在查找过程中同时插入查找表中存在的数据元素,或者从查找表中删除已存在的某个数据元素,则称此类查找表为动态查找表

即上面操作项中的1,2是静态查找表。而3,4是动态查找表。


二、二分查找

二分查找法实质上是不断地将有序数据集进行对半分割,并检查每个分区的中间元素。再重复根据中间数确定目标范围并递归实行对半分割,直到中间数等于待查找的值或是目标数不在搜索范围之内!

下面是二分查找的核心代码:

int BinarySearch(int *sorted, int len, int search) {
	int left = 0, right = 0, middle = 0;

	// 初始化left和right为边界值
	left = 0;
	right = len - 1;

	/* 循环查找,直到左右两个边界重合 */
	while (left <= right) {

		// 找到中间值下标
		middle = (left + right) / 2;

		/* middle索引处的值等于目标值 */
		if (sorted[middle] == search) {
			// 返回目标的索引值middle
			return middle;

			/* middle索引处的值大于目标值 */
		} else if (sorted[middle] > search) {
			// 移动到middle的左半区查找
			right = middle - 1;

			/* middle索引处的值小于目标值 */
		} else {
			// 移动到middle的右半区查找
			left = middle + 1;
		}
	}

	// 没有找到
	return -1;
}

通过一个while循环进行操作,判断条件是数组左边的索引值必须小于右边的的索引值。
里面的if判断都是一些简单的逻辑处理,我们以这个数组为例:int arr[] = { 1, 3, 5, 7, 9, 11, 13};

刚开始left是指向数组索引0的,也就是指向1的位置,right指向最后一个索引,也就是13的位置;
然后获取中间值middle = (left + right) / 2; ==>middle被赋值索引3,也就是7的位置;对应下方图一

然后进行判断,通过middle获取数组的数值与查询数值(search)进行比较,如果相等,那么就是已经找到了,返回当前索引;如果中间数值大于查询数值(search),那么查询的数值(search)就是处于左边,需要将right赋值middle的前一个位置,这也就是为什么要减一的原因,因为当前的中间值不需要再判断了,然后继续while 循环判断;
否则就是中间数值小于查询数值(search),那么查询的数值(search)就处于右边,需要将left赋值middle的后一个位置,这就还需要加一,中间值不需要判断;对应下方图二

然后回到while循环判断,(left <= right) --> (4 <= 6),继续执行while循环;
获取新的中间值,(4 + 6) / 2 最后得到中间值的索引是5,然后与查找值(search)进行比较,结果相等,返回当前中间值,也就是返回对应数值的索引;对应下方图三

到此,二分查找结束,已经找到了。

执行流程如下图:

运行截图:


三、测试代码

代码有两个版本,第一个版本是简单的,单一的二分查找,也就是只是支持int类型的;第二个版本就是支持其它类型的二分查找算法,有兴趣的可以看看!

第一个版本:

#include <stdlib.h>
#include <stdio.h>


/**************************************************************
 * 功能:
 *			使用while循环,使用二分查找算法找到值的索引返回
 *
 * 参数:
 *			sorted		- 待查找的数组
 *			len			- 待查找的数组元素个数
 *			search		- 查找的元素
 *
 *返回值:
 *			找到,返回在数组中的索引小标;未找到,返回-1
 ****************************************************************/
int BinarySearch(int *sorted, int len, int search) {
	int left = 0, right = 0, middle = 0;

	// 初始化left和right为边界值
	left = 0;
	right = len - 1;

	/* 循环查找,直到左右两个边界重合 */
	while (left <= right) {

		// 找到中间值下标
		middle = (left + right) / 2;

		/* middle索引处的值等于目标值 */
		if (sorted[middle] == search) {
			// 返回目标的索引值middle
			return middle;

			/* middle索引处的值大于目标值 */
		} else if (sorted[middle] > search) {
			// 移动到middle的左半区查找
			right = middle - 1;

			/* middle索引处的值小于目标值 */
		} else {
			// 移动到middle的右半区查找
			left = middle + 1;
		}
	}

	// 没有找到
	return -1;
}


/**************************************************************
 * 功能:
 *			单元测试 - 测试二分查找算法
 *
 * 参数:
 *			arr				- 待测试的数组
 *			arrLen			- 待测试的数组元素个数
 *			search			- 测试元素数组
 *			searchLen		- 测试元素数组元素个数
 *
 *返回值:
 *			无
 ****************************************************************/
void unitTest(int *arr, int arrLen, int *search, int searchLen) {
	printf("---单元测试开始---\\n");

	for (int i = 0; i < searchLen; i++) {
		int index = BinarySearch(arr, arrLen, search[i]);
		printf("使用二分查找算法查找数组arr中的%d,其索引是%d\\n", search[i], index);
	}
}

int main(void) {
	int arr[] = { 1, 3, 5, 7, 9, 11, 13};
	//int search[] = { -1, 0, 1, 3, 7, 11, 15 };
	int search = 11;

	int index = BinarySearch(arr, sizeof(arr) / sizeof(arr[0]), search);
	printf("使用二分查找算法查找数组arr中的%d,其索引是%d\\n", search, index);

	//unitTest(arr, sizeof(arr) / sizeof(arr[0]), search, sizeof(search) / sizeof(search[0]));

	return 0;
}

===

第二个版本:

#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <string>


/***************************************************************************
 * 功能:
 *			比较两个int类型数值的大小,使用两数相减的方式(参数一减去参数二)
 *
 * 参数:	
 *			key1 - const void *类型,比较值1
 *			key2 - const void *类型,比较值2
 *
 *返回值:
 *			负数,参数二大于参数一;0,两数相等;正数,参数一大于参数二
 ***************************************************************************/
int int_compare(const void *key1, const void *key2) {
	const int *com1 = (const int *)key1;
	const int *com2 = (const int *)key2;

	return (*com1 - *com2);
}

int char_compare(const void *key1, const void *key2) {
	const char *com1 = (const char *)key1;
	const char *com2 = (const char *)key2;

	return (*com1 - *com2);
}

int str_compare(const void *key1, const void *key2) {
	const std::string *com1 = (const std::string *)key1;
	const std::string *com2 = (const std::string *)key2;

	return ((*com1).size() - (*com2).size());
}


/**************************************************************
 * 功能:
 *			使用while循环,使用二分查找算法找到值的索引返回
 *
 * 参数:
 *			sorted		- 待查找的数组
 *			len			- 待查找的数组元素个数
 *			elemSize	- 待查找数组的类型
 *			search		- 查找的元素
 *			compare		- 比较两数大小的函数指针
 *
 *返回值:
 *			找到,返回在数组中的索引小标;未找到,返回-1
 ****************************************************************/
int BinarySearch(void *sorted, int len, int elemSize, void *search, int (*compare)(const void *key1, const void *key2)) {
	int left = 0, right = 0, middle = 0;

	// 初始化left和right为边界值
	left = 0;
	right = len - 1;

	/* 循环查找,直到左右两个边界重合 */
	while (left <= right) {
		int ret = 0;

		// 找到中间值下标
		middle = (left + right) / 2;

		// 比较两个数的大小((char *)sorted + (elemSize * middle) -> 找到middle在sorted数组位置的值)
		ret = compare((char *)sorted + (elemSize * middle), search);

		/* middle索引处的值等于目标值 */
		if (ret == 0) {
			// 返回目标的索引值middle
			return middle;

			/* middle索引处的值大于目标值 */
		} else if (ret > 0) {
			// 移动到middle的左半区查找
			right = middle - 1;

			/* middle索引处的值小于目标值 */
		} else {
			// 移动到middle的右半区查找
			left = middle + 1;
		}
	}

	// 没有找到
	return -1;
}



int main(void) {
	int arr[] = { 1, 3, 5, 7, 9, 11 };
	int search[] = { -1, 0, 1, 3, 7, 11, 15 };

	printf("\\n 整数查找测试开始。。。\\n");
	for (int i = 0; i < sizeof(search) / sizeof(search[0]); i++) {
		int index = BinarySearch(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), &search[i], int_compare);

		printf("使用二分查找算法查找数组arr中的%d,其索引是%d\\n", search[i], index);
	}


	char arr1[] = { 'a','c','d','f','j' };
	char search1[] = { '0', 'a', 'e', 'j' , 'z' };

	printf("\\n 字符查找测试开始。。。\\n");
	for (int i = 0; i < sizeof(search1) / sizeof(search1[0]); i++) {
		int index = BinarySearch(arr1, sizeof(arr1) / sizeof(arr1[0]), sizeof(char), &search1[i], char_compare);

		printf("使用二分查找算法查找数组arr1中的%c,其索引是%d\\n", search1[i], index);
	}


	// 还需要一些逻辑判断,目前只支持字符串的大小查找,而不是相同的查找
	//std::string arr2[] = { "abc", "1234", "qwert", "qpopo", "ffffff" };
	//std::string search2[] = { "", "abc", "qwert", "ffffff", "c" };

	//printf("\\n 字符串查找测试开始。。。\\n");
	//for (int i = 0; i < sizeof(search2) / sizeof(search2[0]); i++) {
	//	int index = BinarySearch(arr2, sizeof(arr2) / sizeof(arr2[0]), sizeof(std::string), &search2[i], str_compare);

	//	//printf("使用二分查找算法查找数组arr2中的%s,其索引是%d\\n", search2[i], index);
	//	std::cout << "使用二分查找算法查找数组arr2中的" << search2[i] << ",其索引是" << index << std::endl;
	//}

	return 0;
}

四、总结

二分查找算法,以我这种实现方式其实也是很好理解的,需要认真调试一下,多看看right和left和middle 这三个变量都有哪些变化,应该都可以看得懂!

最后需要注意:二分查找算法是基于已经排好序的数组,如果顺序是乱的,那么是查找不了的!其次,我写的这个二分查找算法是基于升序排序的,如果是降序的话,需要将 else if 的大于号改成小于号即可!如下:

		/* middle索引处的值等于目标值 */
		if (sorted[middle] == search) {
			// 返回目标的索引值middle
			return middle;

			/* middle索引处的值大于目标值 */
		} else if (sorted[middle] < search) {		// 改成小于号!!!!
			// 移动到middle的左半区查找
			right = middle - 1;

			/* middle索引处的值小于目标值 */
		} else {
			// 移动到middle的右半区查找
			left = middle + 1;
		}

最后,奉上我粉丝私聊我解答的题目,有兴趣的也可以作答一下,可以私聊我拿我写的答案代码!

题目描述

在一个从大到小排列的有序序列(下标从0开始)中查找一个给定的值,并输出该值的下标。

思考:从大到小的顺序序列是否也可以应用二分查找的方法呢?

输入描述:

第一行包含一个正整数 n,表示序列中元素个数。1 <= n <= 10000。

第二行包含 n 个整数,依次给出序列的每个元素,相邻两个整数之间用单个空格隔开。请注意各个元素的绝对值不超过 10000。

第三行包含一个整数 x ,为需要查找的特定值。请注意此特定值 x 的绝对值不超过 10000。

输出描述:

若序列中存在 x,则输出 x 的下标;否则输出 -1。

输入样例1:

10
92 90 83 69 65 54 37 28 12 6 
37
输出样例1:

6
样例输入2

10
92 90 83 69 65 54 37 28 12 6 
66

样例输出2

-1

以上是关于C/C++ 二分查找算法(非递归实现)的主要内容,如果未能解决你的问题,请参考以下文章

C/C++ 分治算法(二分查找算法递归实现)

二分查找非递归算法实现

算法拾遗二分查找递归非递归实现

Day589.二分查找(非递归) -数据结构和算法Java

JavaScript实现二分查找(搜索)算法(非递归实现)

图解二分查找的递归和非递归实现