数据结构与算法学习--二分查找

二分查找(Binary Search)算法,也叫折半查找算法。二分查找针对的时一个有序的数据集合,查找思想有点类似于分治。每次都是通过和区间的中间元素进行比较,将待查区缩小为原来的一半,直到将元素找到或者区间缩小为0。
我们可以通过2种方式实现:递归和非递归。

/*************************************************************************
 > File Name: bsearch.c
 > Author:  jinshaohui
 > Mail:    [email protected]
 > Time:    18-10-21
 > Desc:    
 ************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>


int mybsearch(int a[],int size,int value)
{
	int mid = 0;
	int left = 0;
	int right = size - 1;

	while(left <= right)
	{
		/*防止size数量太大是,(left + right)数据翻转,导致问题*/
		mid = left + ((right - left)>>1);

		if (a[mid] == value)
		{
			return mid;
		}
		else if (a[mid] < value)
		{
			left = mid + 1;
		}
		else
		{
			right = mid - 1;
		}
	}

	return -1;
}

int helper(int a[], int left,int right,int value)
{
	int mid = 0;

	if (left > right)
	{
		return -1;
	}
	/*防止size数量太大是,(left + right)数据翻转,导致问题*/
	mid = left + ((right - left)>>1);
	if (a[mid] == value)
	{
		return mid;
	}
	else if (a[mid] < value)
	{
		return helper(a,mid + 1,right,value);
	}
	else
	{
		return helper(a,left,mid - 1,value);
	}
    return -1;
}
/*递归实现*/
int mybsearch_2(int a[],int size,int value)
{

	return helper(a,0,size-1,value);
}

int main()
{
	int a[10] = {5,6,8,9,10,11,23,42,53,123};
    int data = 0;
	int res = 0;

	printf("\r\n输入一个整数");
	scanf("%d",&data);
    res = mybsearch(a,10,data);
	printf("data[%d] %s 在数据中,下标是%d",data,(res != -1)?"":"不",res);
	
	printf("\r\n输入一个整数");
	scanf("%d",&data);
    res = mybsearch_2(a,10,data);
	printf("data[%d] %s 在数据中,下标是%d",data,(res != -1)?"":"不",res);
	return;
}

看了上面的代码,我们觉得很容易吧。但是还有几个地方需要注意:
1、循环退出条件
这里必须是left <= right,而不是left < right
2、mid的求值
还有一个是为了程序的更好的健壮性:求中间值的时候,我们多用mid=(left+right)/2; 但是这样的可能会越界,比如 left+right超int型的范围,所以我们用更好更安全的写法 :mid=left+(right-left)/2; 起初还钻牛角尖不理解为什么等式相同。。。 为了进一步将性能优化到极致的话,我们采用位移操作。>>1 位移优先级比较低,我们必须考虑到加括号。mid=left+((right-left) >>1).
3、left和right的更新
left = mid + 1;right = mid - 1;这里必须是+1 和-1,不能直接等于mid,否则可能会导致死循环。

二分查找的变形问题
我们一直以为二分查找比较简单,但是写出完全正确的二分查找算法确很难。首先引用一下《编程珠玑》中的两句话:
1、尽管给了那么充裕的时间,只有大约10%的专业程序员能够写出正确的二分查找。
2、尽管第一个二分查找程序于1946年就公布了,但是第一个没有bug的二分查找程序在1962年才出现。
二分查找的变形问题很多,我们重点来看下面四种
1、找出第一个等于给定数值的元素
2、找出最后一个等于给定数值的元素
3、找出第一个大于等于给定数值的元素
4、找出第一个小于等于给定数值的元素。

/*************************************************************************
 > File Name: bsearch.c
 > Author:  jinshaohui
 > Mail:    [email protected]
 > Time:    18-10-21
 > Desc:    
 ************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

/*二分查找算法的变形问题
 *1、查找第一个等于给定数值的元素
 *2、查找最后一个等于给定数值的元素
 *3、查找第一个大于等于给定数值的元素
 *4、查找第一个小于等于给定数值的元素
 * */


 /*1、查找第一个等于给定数值的元素*/
int mybsearch_1(int a[],int size,int value)
{
	int mid = 0;
	int left = 0;
	int right = size - 1;

	while(left <= right)
	{
		/*防止size数量太大是,(left + right)数据翻转,导致问题*/
		mid = left + ((right - left)>>1);

		if (a[mid] < value)
		{
			left = mid + 1;
		}
		else if (a[mid] > value)
		{
			right = mid - 1;
		}
		else
		{
			if ((mid == 0) || (a[mid - 1] != value))
			{
                return mid;
			}
			else
			{
				right = mid - 1;
			}
		}
	}

	return -1;
}

 /*2、查找最后一个等于给定数值的元素*/
int mybsearch_2(int a[],int size,int value)
{
	int mid = 0;
	int left = 0;
	int right = size - 1;

	while(left <= right)
	{
		/*防止size数量太大是,(left + right)数据翻转,导致问题*/
		mid = left + ((right - left)>>1);

		if (a[mid] < value)
		{
			left = mid + 1;
		}
		else if (a[mid] > value)
		{
			right = mid - 1;
		}
		else
		{
			if ((mid == (size - 1)) || (a[mid + 1] != value))
			{
                return mid;
			}
			else
			{
				left = mid + 1;
			}
		}
	}

	return -1;
}
 /*3、查找第一个大于等于给定数值的元素*/
int mybsearch_3(int a[],int size,int value)
{
	int mid = 0;
	int left = 0;
	int right = size - 1;

	while(left <= right)
	{
		/*防止size数量太大是,(left + right)数据翻转,导致问题*/
		mid = left + ((right - left)>>1);

		if (a[mid] < value)
		{
			left = mid + 1;
		}
		else
		{
			/*a[mid] >= value 当mid==0 或者a[mid-1] > value 说明是第一个大于等于value*/
			if ((mid == 0) || (a[mid - 1] < value))
			{
                return mid;
			}
			else
			{
				right = mid - 1;
			}
		}
	}

	return -1;
}

 /*4、查找第一个小于等于给定数值的元素*/
int mybsearch_4(int a[],int size,int value)
{
	int mid = 0;
	int left = 0;
	int right = size - 1;

	while(left <= right)
	{
		/*防止size数量太大是,(left + right)数据翻转,导致问题*/
		mid = left + ((right - left)>>1);

		if (a[mid] > value)
		{
			right = mid - 1;
		}
		else
		{
			/*a[mid] <= value 时,当前mid == size -1 数组中最大的数值;
			 *                    或者a[mid + 1] 大于vlaue,就是mid就第一个小于等于value*/
			if ((mid == (size - 1)) || (a[mid + 1] > value))
			{
                return mid;
			}
			else
			{
				left = mid + 1;
			}
		}
	}

	return -1;
}
int main()
{
	int a[10] = {5,6,6,9,10,11,11,22,33,33};
    int data = 0;
	int i = 0;
	int res =0;

	printf("\r\n");
    for(i = 0; i < 10 ; i++)
	{
		printf("%d ",a[i]);
	}
	printf("\r\n");
	printf("\r\n输入一个整数");
	scanf("%d",&data);
    res = mybsearch_1(a,10,data);
	printf("第一个等于data[%d],下标是%d",data,res);
	
	printf("\r\n输入一个整数");
	scanf("%d",&data);
    res = mybsearch_2(a,10,data);
	printf("最后一个等于data[%d],下标是%d",data,res);

	printf("\r\n输入一个整数");
	scanf("%d",&data);
    res = mybsearch_2(a,10,data);
	printf("第一个大于等于data[%d],下标是%d",data,res);

	printf("\r\n输入一个整数");
	scanf("%d",&data);
    res = mybsearch_2(a,10,data);
	printf("第一个小等于data[%d],下标是%d",data,res);
	return;
}

课后思考:
1、如何求一个数的平方根

/*************************************************************************
 > File Name: sqrt.c
 > Author:  jinshaohui
 > Mail:    [email protected]
 > Time:    18-10-31
 > Desc:    
 ************************************************************************/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
/*求解精度设置*/
#define E 0.000001 
double mybsearch(double num)
{
	double start = 1.0;
	double end = num;
    double mid = 0.0;
	while(1)
	{
	    mid = (start + end)/2;
        if(((mid*mid - num) <= E) && ((mid*mid - num) >= -E))
		{
			return mid;
		}

		if ((mid*mid - num) > E)
		{
			end = mid;
		}
		else
		{
			start = mid;
		}
 	}
    return 0;
}
int main()
{
	double num = 0.0;

	/*这里需要注意:double的输入方式*/
	scanf("%lf",&num);
	printf("\r\n num %lf的平方根是%lf",num,mybsearch(num));

	return 0;
}

2、如果使用链表存储数据,二分查找的时间复杂度怎么计算?
假设链表长度为n,二分查找每次都要找到中间点(计算中忽略奇偶数差异):
第一次查找中间点,需要移动指针n/2次;
第二次,需要移动指针n/4次;
第三次需要移动指针n/8次;

以此类推,一直到1次为值
总共指针移动次数(查找次数) = n/2 + n/4 + n/8 + …+ 1,这显然是个等比数列,根据等比数列求和公式:Sum = 2n - 1.
最后算法时间复杂度是:O(2n-1),忽略常数,记为O(n),
3、快速定位IP地址对应的省份
当我们要查找202.102.133.13对应的IP地址的省份的时候,发现IP地址在[202.102.133.0, 202.102.133.255] 这个范围就返回山东东营市。那我们如何在庞大的地址库中找到指定IP的范围呢?

[202.102.133.0, 202.102.133.255]  山东东营市 
[202.102.135.0, 202.102.136.255]  山东烟台 
[202.102.156.34, 202.102.157.255] 山东青岛 
[202.102.48.0, 202.102.48.255] 江苏宿迁 
[202.102.49.15, 202.102.51.251] 江苏泰州 
[202.102.56.0, 202.102.56.255] 江苏连云港

我们可以运用上面的第3或第四来通过二分查找来在有序集合中找到。
我们可以将ip地址转换成一个int型:202.102.133.13 = 202256+102256+133256+13256,然后在地址库中找到最后一个小于等于这个地址的数值,他对应的就是我们要查找的省份。

猜你喜欢

转载自blog.csdn.net/jsh13417/article/details/83652233