二分查找及其扩展

目录

二分查找算法思想:

循环

递归

有多个与key相等数据的查询,找最左边与关键字相等的那个

找第一个大于key的元素的下标

有序循环数组的二分查找


二分查找算法思想:

二分查找也叫折半查找,查找效率较高。但是查找的线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

       线性表元素升序排列,将线性表中间位置的关键字与查找关键字比较,如果两者相等,则查找成功,否则将线性表一分为二。分治策略

如果中间位置的值大于查找关键字,则在左子表继续查找,否则查找右子表。

重复以上过程直到查找成功,返回位置信息。若子表为空了仍未找到返回-1,查找失败。

二分查找的时间复杂度是O(log(n)),最坏情况下的时间复杂度是O(n)。


循环

int binary_search(const int* nums, int n, int key)
{
	int left = 0;
	int right = n - 1;
	int mid = 0;
	int pos = -1;
	while (left <= right)//left和right是数据的规模,==时还有一个数据在里面,所以必须有=而非left<right
	{
		mid = (left + right) / 2;
		//也可以写成  mid = (right-left)/2+left,+left因为他是一个绝对规模,不能相对相减
		if (nums[mid] > key)
			right = mid - 1;
		else if (nums[mid] < key)
			left = mid + 1;
		else
		{
			pos = mid;
			break;
		}
	}
	return pos;
}

递归

        分治策略处理一个区域时,不能只用一个参数控制

int Find(const int* nums, int left, int right, int key)
{
	if (left <= right)
	{
		int mid = left + ((right - left) >> 1);
		if (nums[mid] > key)
			return Find(nums, left, mid - 1, key);
		else if (nums[mid] < key)
			return Find(nums, mid + 1, right, key);
		else
			return mid;
	}
	return -1;
}
int binarySearch(int* nums, int n, int key)
{
	assert(n > 0);
	int left = 0;
	int right = n - 1;
	return Find(nums, left, right, key);
}

有多个与key相等数据的查询,找最左边与关键字相等的那个

算法思想:先用二分查找找到一个与key相同的元素,然后顺序查看其左边的元素是否也与key相同,直到找到最左边的(数组查完/左边元素不等于key)。

算法改进:找到与key相同的元素后查看其左边第一个(不越界的情况下)是否也相同,如果不相同则没有多个,直接返回。如果相同则继续用二分查找找左边子序列与key相同的。

这样是为了避免有如:在序列3 3 3 3 3 3 3 3中查找3,完全没有了二分的优势。

int binary_search(const int* nums, int n, int key)
{
	int left = 0;
	int right = n - 1;
	int mid = 0;
	int pos = -1;
	while (left <= right)
	{
		mid = left + (left + right >> 1);
		if (nums[mid] > key)
			right = mid - 1;
		else if (nums[mid] < key)
			left = mid + 1;
		//else//二分查询到之后线性查询,一个挨着一个找
		//{
		//	while (mid>left && nums[mid-1] == key )//这个调节不能写反,否则越界
		//	{
		//		--mid;
		//	}
		//	pos = mid;
		//	break;
		//}
		//改进
		else
		{
			if (mid == left || nums[mid - 1] != key) return mid;//已经是最左边了
			else
			{
				right = mid - 1;
				continue;
			}
		}
	}
	return pos;
}

找第一个大于key的元素的下标

                mid<key,则不用在left-mid区间进行查找。

                mid==key时,如果下一个元素不等于key,则下一个为所找元素;

                                        如果下一个也等于key,则在右子序列继续二分查找。

                mid>key,则与上一题同理。

找第一个小于反之。

int binary_search(const int* nums, int n, int key)
{
	int left = 0;
	int right = n - 1;
	int mid = 0;
	int pos = -1;
	while (left <= right)
	{
		mid = left + (right-left >> 1);
		if (nums[mid] >= key)
		{
			if (nums[mid] == key)
			{
				if (nums[mid + 1] == key)
					left = mid + 1;
				else
					return mid + 1;
			}
			else 
			{
				if (nums[mid - 1] > key)
				{
					right = mid - 1;
				}
				else
					return mid;
			}
		}
		else if (nums[mid] < key)
			left = mid + 1;
	}
	return pos;
}
int main()
{
	int arr[10] = {0,1,2,3,4,5,6,6,8,9};
	printf("%d ", binary_search(arr, 10, 5));
	return 0;
}

有序循环数组的二分查找


有序循环数组如:4,5,6,7,8,9,10,1,2,3;查询给定的key是否在顺序表中,在哪。
先找到循环的点,再二分查找。

        在一个有序子串中取一值,left一定比它小;在另一子串中取值,left一定比它大。这样产生mid指向任意值,就可以判断循环的断点在mid的左边还是右边了。

 

int binary_search(const int* nums, int n, int key)
{
	int left = 0;
	int right = n - 1;
	int mid=0,bmid=0;
	int pos = -1;
	while (left < right)
	{
		bmid = left + (right - left >> 1);
		if (nums[bmid] == key)
			return bmid;
		else if (nums[left] < nums[bmid])//循环点在bmid右边
		{
			left = bmid;
		}
		else if (nums[left] > nums[bmid])//在bmid左边
		{
			right = bmid;
		}
		else
		{
			bmid++;
			break;
		}
	}
	printf("bmid==%d\n",bmid);
	//找到循环点了
	if (nums[0] <= key)//key应该在左边
	{
		left = 0;
		right = bmid - 1;
	}
	else
	{	
		left = bmid;
		right = n - 1;
	}
	while (left <= right)
	{
		mid = left+(right-left>>1);
		if (nums[mid] > key)
			right = mid - 1;
		else if (nums[mid] < key)
			left = mid + 1;
		else
		{
			pos = mid;
			break;
		}
	}
	return pos;
}
int main()
{
	int arr[10] = { 4,5,6,7,8,9,10,1,2,3};
	printf("%d \n", binary_search(arr, 10, 5));
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

曦樂~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值