编程之美系列之关于数组的二分查找

有序的数组,其中的元素可以通过二分查找得到,可以如果一个数组是由有序数组左旋得到的,能不能还是用二分查找得到呢?答案是肯定的。
为了简化问题,我先假设数组是由升序的数组通过左旋得到了,比如说{1,2,3,4,5,6}=>{4,5,6,1,2,3}。只要我们找到这个最大值的下标,那么就可以把数组分成两个升序的有序数组了。找这个最大值很简单,但是我想你应该不会说全部遍历一遍数组吧!这里讲的重点是二分查找。
怎么二分查找最大值的下标?由于数组是有序的,定义查找的开始为b,结束点为e,mid=(b+e)/2.OK,二分的必备神器呵!
1、arr[mid]<arr[nLen-1],更新e。
2、arr[mid]>arr[0],更新b。
问题来了,万一数组中有重复的元素怎么比如说{4,5,6,6,1,2,3}.把上面更新的条件加上等号就可以了,但是还是有bug,万一整个数组都只有一个数呢?比如说{6,6,6,6,6,6,6}。OK,我们需要找到的是最右边的那个最大值的下标。所以这里优先更新b就可以了,也就是:
1、arr[mid]>=arr[0],更新b。
2、arr[mid]<=arr[nLen-1],更新e。
好了,给定key之后,由于数组中的元素可能有重复(没有重复的情况很简单,不需要我废话了),所以如果找到key,返回最右的下标。
1、如果key==arr[nLen-1],下标直接就是nLen-1了。
2、如果key<arr[nLen-1],则在前面一部分的数组中查找这个元素。
3、否则,在后半部分查找这个元素。
各位看官们,我敢保证这个是网上绝无仅有的,所以保护一下偶这个弱女子,你也应该转载注明出处吧!小女子跪谢:http://blog.csdn.net/kay_zhyu/article/details/8867095
关于这个部分的代码如下:

#include<stdio.h>
#include<assert.h>
//arr有递增序列旋转得到,找到arr中最右边的最大值的下标
//e.g.{1,2,3,4,5,6,6}->{4,5,6,6,1,2,3};=>ans:3
int IncreaseFindMax(int *arr, int nLen)
{
	assert(arr && nLen > 0);
	int b = 0;
	int e = nLen - 1;
	int mid;
	while(b < e - 1)
	{
		mid = (b + e)>>1;
		//由于求最右的下标,故优先更新b,否则优先更新e
		if(arr[mid] >= arr[0])
			b = mid;
		else if(arr[mid] <= arr[nLen - 1])
			e = mid;
	}
	return arr[b] > arr[e] ? b : e; 
}
//在递增的序列中查找,返回等于key的最大的下标
//e.g.{1,2,3,3,3,4,5,6,6}中找3,ans=4.
int IncreaseFindKey(int *arr, int b, int e, const int key)
{
	if(!arr || b > e || b < 0)
		return -1;
	int mid;
	while(b < e - 1)
	{
		mid = (b + e)>>1;
		if(arr[mid] <= key)
			b = mid;
		else
			e = mid;
	}
	if(arr[e] == key)
		return e;
	else if(arr[b] == key)
		return b;
	else
		return -1;
}
题目扩展:
1、如果数组是由降序数组左旋得到的呢?
这个好办,依葫芦画瓢就可以了。代码如下:
//arr有递减序列旋转得到,找到arr中最右边的最小值的下标
//e.g.{6,5,4,3,2,1,1}->{3,2,1,1,6,5,4};ans=>3
int DecreseFindMin(int *arr, int nLen)
{
	assert(arr && nLen > 0);
	int b = 0;
	int e = nLen - 1;
	int mid;
	while(b < e - 1)
	{
		mid = (b + e)>>1;
		//由于求最右的下标,故优先更新b,否则优先更新e
		if(arr[mid] <= arr[0])
			b = mid;
		else if(arr[mid] >= arr[nLen - 1])
			e = mid;
	}
	return arr[b] < arr[e] ? b : e;
}
//在递减的序列中查找,返回等于key的最大的下标
//e.g.{6,5,4,3,3,3,2,1}中找3,ans=5.
int DecreaseFindKey(int *arr, int b, int e, const int key)
{
	if(!arr || b > e || b < 0)
		return -1;
	int mid;
	while(b < e - 1)
	{
		mid = (b + e)>>1;
		if(arr[mid] >= key)
			b = mid;
		else
			e = mid;
	}
	if(arr[e] == key)
		return e;
	else if(arr[b] == key)
		return b;
	else
		return -1;
}

2、如果数组只告诉你是有有序数组左旋得到的,但没有说有序是升序还是降序。
OK,那就用一个函数来判断数组的升序和降序情况。
分别从数组的两端进行遍历,如果有一对数不同,OK,它们的大小关系就决定了整个数组的序关系。
如果数组中全部元素相同,则默认为升序降序都无所谓。
3、另外,又想到了一种扩展,关于这个二分查找的,记录在这里了。给定排序的数组,给定一个数字,求这个数字在数组中出现的次数。二分查找到这个数字,然后以这个数字向左向右遍历,然后用下标相减即可。

bool Judge(int *arr, int nLen)
{
	assert(arr && nLen > 0);
	int b,e;
	for(b = 0, e = nLen - 1; b <= e; ++b, --e)
	{
		if(arr[b] != arr[e])//过滤掉相等的元素
			return arr[b] > arr[e];
	}
	return false;//true或false无所谓,自己高兴就好
}
//附上main函数的调用测试
int main()
{
	const int N = 30;
	int arr[N];
	int n,i,ans,key;
	bool IsIncrease;
	while(scanf("%d", &n) != EOF)
	{
		for(i = 0; i < n; ++i)
			scanf("%d", &arr[i]);
		printf("Enter the key:");
		scanf("%d", &key);
		if(key == arr[n - 1])//如果等于最后一个元素
		{
			ans = n - 1;
			printf("The position is %d\n", ans);
			continue;
		}
		IsIncrease = Judge(arr, n);
		if(IsIncrease)
		{
			printf("序列由递增序列旋转得到\n");
			ans = IncreaseFindMax(arr, n);
			printf("最右边的最大值的下标为%d\n",ans);
			if(key > arr[n - 1])
				ans = IncreaseFindKey(arr, 0, ans, key);
			else
				ans = IncreaseFindKey(arr, ans + 1, n - 1, key);
		}
		else
		{
			printf("序列由递减序列旋转得到\n");
			ans = DecreseFindMin(arr, n);
			printf("最右边的最小值的下标为%d\n",ans);
			if(key < arr[n - 1])
				ans= DecreaseFindKey(arr, 0, ans, key);
			else
				ans = DecreaseFindKey(arr, ans + 1, n - 1, key);
		}
		if(ans > -1)
			printf("The position is %d\n", ans);
		else
			printf("No found!\n");
	}
}

测试结果:











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值