【C语言】析半查找(二分查找)

我们学习二分查找也好,或者其它的算法或者排序也好。最重要的一点是搞清楚算法的思想,只有我们搞明白算法的思想以后,我们才能真正的脑子中记住它,知道它的使用条件或者在实现过程中有哪些坑,这是最重要的。不仅仅是本篇的二分查找,其它的算法也是如此。实现并不重要,理解,清楚才是重中之重。

二分查找简介:

二分查找是一种快速从大量有序数据中查找目标数据的一种算法,当然二分查找也是一种高效的查找方法。
要使用二分查找必须满足的两个前提:

  1. 数据必须是顺序存储(比如顺序表、数组这样的顺序存储结构)
  2. 待排序列必须有序

算法思想:

  1. 通过两个变量left和right同时标记数组的第一个位置的,和数组的最后一个位置。
  2. 每一次取到数组最中间数的下标mid对应的那个数nums[mid],与需要查找的数x进行比较
  3. 如果 nums[mid] < x , 那么下标【0,mid】对应的数肯定都小于x,所以目标数字不会在【0,mid】,将区间范围缩小到【mid+1,right】
  4. 如果 nums[mid] > x, 那么下标【mid, right】对应的数肯定都大于x,所以目标数字不会在【mid,right】,将区间范围缩小到【0,mid-1】
  5. 如果 nums[mid] == x, 那么直接返回目标数字x对应的下标mid

栗子
我们要从以下有序数组中找到目标数字8
在这里插入图片描述

算法的分析:

1. 时间复杂度的分析

相较于遍历数组查找,找出数组中是否含有特定的值x,也就是需要从头到尾遍历一个数组。时间复杂度为O(N).。
而二分查找,查找一次若一次直接找到特定值x,直接返回这个数的下标;若第一次没有找到特定值x,则数组中间的值与x进行比较,选择符合条件的一半区间,淘汰掉一半区间。 即数组长度为n,则2^M = n , 那么M= log以2为底的n, 所以二分查找的时间复杂度记为O(lgN)(lgN 其实就是log以2为底N的对数)

为了进一步理解二分查找的优势: 我们其实可以想象,如果在30亿个数据的有序数组中,找到一个数, 普通的遍历查找需要查找30亿次(最坏情况下),而二分查找只需要32次(最坏情况下)。 我们便可以知道二分查找算法的性能多么高效。

2. 空间复杂度的分析

二分查找是在原数组上进行查找,并没有开额外的空间,所以二分查找的空间复杂度为O(1).

	下面我们看一下代码是如何实现的? 

算法代码实现:

实现的方法一: 闭区间【0,n-1】的情况

int BinaryFind(int *nums, int n, int x)
{
	int left = 0;
	int right = n - 1;
	while(left <= right)
	{
		//不推荐这一种写法,容易发生溢出,超过int的范围
		//int mid = (left + right)/2;
		//这样的写法不会产生溢出,但是还需要改进,位运算符的效率比加减乘除的效率要高 
		//还可以优化
		//int mid = left + (right-left)/2;
		int mid = left + ((right - left) >> 1);
		if(nums[mid] < x)
		{
			left = mid + 1;
		}
		else if(nums[mid] > x)
		{
			right = mid - 1;
		}
		else 
		{
			return mid;
		}
	}
	return -1;
}

实现方法二:左闭右开的情况 【0,n)

int BinaryFind(int *nums, int n, int x)
{
	int left = 0;
	int right = n;
	while(left < right) //注意  左闭右开的时候不能取 = 号 !!!
	{
		//不推荐这一种写法,容易发生溢出,超过int的范围
		//int mid = (left + right)/2;
		//这样的写法不会产生溢出,但是还需要改进,位运算符的效率比加减乘除的效率要高 
		//还可以优化
		//int mid = left + (right-left)/2;
		int mid = left + ((right - left) >> 1);
		if(nums[mid] < x)
		{
			left = mid + 1;  //保持它是一个闭区间
		}
		else if(nums[mid] > x)
		{
			right = mid  ;  //保持它是一个开区间
		}
		else 
		{
			return mid;
		}
	}
	return -1;
}

int  main()
{
	int nums[] = { 1, 3, 4, 5, 6, 8, 9, 10};
	printf("%d\n", BinaryFind(nums, sizeof(nums) / sizeof(int), 1));
	printf("%d\n", BinaryFind(nums, sizeof(nums) / sizeof(int), 2)); //不存在 输出-1
	printf("%d\n", BinaryFind(nums, sizeof(nums) / sizeof(int), 3));
	printf("%d\n", BinaryFind(nums, sizeof(nums) / sizeof(int), 4));
	printf("%d\n", BinaryFind(nums, sizeof(nums) / sizeof(int), 5));
	printf("%d\n", BinaryFind(nums, sizeof(nums) / sizeof(int), 6));
	printf("%d\n", BinaryFind(nums, sizeof(nums) / sizeof(int), 8));
	printf("%d\n", BinaryFind(nums, sizeof(nums) / sizeof(int), 9));
	printf("%d\n", BinaryFind(nums, sizeof(nums) / sizeof(int), 10));
	system("pause");
}

结果: 运行正确。在这里插入图片描述
运行失败的栗子:
如果while循环中取到’=’号while(left<=right),程序到找目标数字2的时候则崩溃。
在这里插入图片描述
崩溃原因
程序走到最后一步left = 1, right = 1, mid也等于1, 但是nums[mid] = 3 > x(2) , right = mid = 1. 就这样一直死循环,所以程序崩溃。

两种实现方法需要注意的地方:

  • 当取闭区间【0,n-1】时

  1. while循环中必须是left <= right 因为如果循环条件中是while(left < right)的时候,假如【3,3】的时候,我们还需要在比较一次mid =(3+3)/2, 而不是直接进不去while循环,直接返回-1.
  2. 当缩小区间的时候必须是left = mid+1,或者是right = mid-1。left或right不能等于mid
    当某些情况发生时, left和right取【7,8】时, x的值要比mid对应的值要大,而如果left = mid = (7+8)/2 = 7。 那么left的值一直会是7,这样就会发生死循环。
    总结:当每次比较的时候,都要保证它的比较的区间是闭区间,所以mid每次都比较过了,所以要left=mid+1或者right = mid-1。
  • 当取左闭右开区间【0,n)时

  1. while循环中必须是left<right,要不然可能引起程序一直死循环 如代码实现方法二中的运行崩溃的原因。
  2. 一直要保持下一个查找区间是左闭右开,所以要left = mid+1,营造左边是闭区间的能取到mid+1; 或者right = mid,营造右边开区间不能取到mid
发布了55 篇原创文章 · 获赞 12 · 访问量 5235

猜你喜欢

转载自blog.csdn.net/weixin_43939593/article/details/103992240
今日推荐