设为首页 加入收藏

TOP

二分查找及其应用 (一)
2014-11-23 20:00:37 来源: 作者: 【 】 浏览:14
Tags:二分 查找 及其 应用

(1) 二分查找:


使用二分查找(Binary Search)的前提有:(1)线性表必须是关键码有序(通常是从小到大有序)。(2)其次,线性表必须是顺序存储。所以链表不能采用二分查找。

二分查找(Binary Search)基本思想:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,知道查找成功,或者所有查找区域无记录,查找失败为止。

例题:现有这样一个有序表数组{1, 16, 24, 35, 47, 59, 62, 73, 88, 99},对它进行查找是否存在62这个数。算法实现如下:

#include 
using namespace std;

/*
函数名:	BinarySearch
函数功能:	在数组array内查找值为key的元素,返回该元素所在数组的位置
函数参数:	int *array	数组指针
			int length	数组长度
			int key		要查找的key值。
返回值:	如果key存在数组内,返回其在数组中的位置;否则返回-1。
*/
int BinarySearch(int *array, int length, int key)
{
	/* 对参数的合法性进行判断 */
	if (array == NULL || length <= 0)					
	{
		return -1;
	}

	int low, middle, high;
	low = 0;
	high = length - 1;
	while (low <= high)
	{
		/* middle取low和high中间元素 */
		middle = low + (high - low) / 2;				

		/* array[middle] > key,故在middle的左边查找key */
		if (key < array[middle])						
		{
			high = middle - 1;
		}
		/* array[middle] < key,故在middle的右边查找key */
		else if (key > array[middle])					
		{
			low = middle + 1;
		}
		/* array[middle] = key,故返回middle */
		else											
		{
			return middle;
		}
	}

	/* 如果数组中不存在key值,则返回-1 */
	return -1;											
}

void Test(const char *testName, int *array, int length, int key, int expectedIndex)
{
	cout << testName << " : ";
	if (BinarySearch(array, length, key) == expectedIndex)
	{
		cout << "Passed." << endl;
	}
	else
	{
		cout << "Failed." << endl;
	}
}

int main()
{
	int array[] = {1, 16, 24, 35, 47, 59, 62, 73, 88, 99};
	Test("Test1", array, sizeof(array) / sizeof(*array), 62, 6);
	Test("Test2", array, sizeof(array) / sizeof(*array), 1, 0);
	Test("Test3", array, sizeof(array) / sizeof(*array), 99, 9);
	Test("Test4", array, sizeof(array) / sizeof(*array), 100, -1);
	Test("Test5", array, sizeof(array) / sizeof(*array), 0, -1);

	return 0;
}

下面我们考虑,如何使用二分查找的基本思想来解决以下问题。

例1:把一个数组最开始的若干元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。(剑指offer,面试题8,页数:66)

旋转数组的性质有:旋转数组实际上将其分成两个子数组,前面子数组是递增的,后面子数组也是递增的,但是前面子数组的元素大于或等于后面子数组的元素,其中最小元素在后面子数组的第一个元素。例数如组{3, 4, 5, 1, 2}分成两个子数组{3,4,5}和{1, 2},则最小值为1。

我们可以尝试使用二分查找的思想,用两个指针分别指向数组的第一个元素和最后一个元素。按照题目中旋转的要求,则第一个元素应该是大于或者等于最后一个元素。

然后我们找到中间元素,如果该中间元素位于前面的递增子数组,那么它应该大于或者等于第一个指针指向的元素。此时数组中最小的元素应该位于该中间元素后面,所以我们把第一个指针指向该中间元素,缩小查找范围。移动之后的第一个指针仍然位于前面的递增子数组之中。

同样,如果中间元素位于后面的递增子序列,那么它应该小于或者等于第二个指针指向的元素。此时该数组中最小的元素应该位于该中间元素的前面。我们可以把第二个指针指向该中间元素,这样也可以缩小查找范围。移动之后的第二个指针仍然位于后面的递增子数组之中。

不管是移动第一个指针,还是第二个指针,查找范围都会缩小到原来的一半。接下来我们再用更新之后的两个指针,重新做新的一轮的查找。

按照上述的思路,第一个指针总是指向前面递增数组的元素,而第二个指针总是指向后面递增数组的元素。最终第一个指针将指向前面子数组的最后一个元素,而第二个指针会指向后面子数组的第一个元素。也就是它们最终会指向两个相邻的元素。而第二个指针指向刚好是最小的元素。这就是循环结束的条件。

这里有一个特殊情况,就是如果中间元素array[middle] == array[low] == array[high]这种情况,如果是这样的话,那我们就只能在low和high之间,遍历查找最小值。

整个算法实现如下:

#include 
using namespace std;


/* 
函数名:	GetMinestValue
函数功能:	获取数组rotatedArray[low...high]之间最小值所在数组的位置。
函数参数:	int *rotatedArray	旋转数组
			int low				查找范围为[low, high]的low
			int high			查找范围为[low, high]的high
返回值:	如果存在,则返回最小值所在数组的位置;否则返回-1。
*/
int GetMinestValue(int *roatedArray, int low, int high)
{
	/* 检测参数的合法性 */
	if (roatedArray == NULL || low < 0 || high < 0 || low > high)
	{
		return -1;
	}

	/* 遍历数组rotatedArray[low...high],找到最小值所在的位置 */
	int min = low;
	for (int i = low; i <= high; i++)
	{
		if (roatedArray[i] < roatedArray[min])
		{
			min = i;
		}
	}

	return min;
}

/*
函数名:	GetMinestValue
函数功能:	在旋转数组RotatedArray中找到最小值,返回该值所在的位置
函数参数:	int *rotatedArray	旋转数组指针
			int length			旋转数组长度
返回值:	如果存在最小值,则返回该值所在位置;否则,返回-1。
*/
int GetMinestValue(int *roatedArray, int length)
{
	/* 检查参数的合法性 */
	if (roatedArray == NULL || length <= 0)
	{
		return -1;
	}

	int low, middle, high;
	low = 0;
	middle = 0;
	high = length - 1;

	/* 这个数组是递增的,旋转0个元素,则直接返回0 */
	if (roatedArray[low] < roatedArray[high])			
	{
		return low;
	}

	while (low < high)
	{
		/* 如果low和high是相邻的,则返回high */
		if (high - low == 1)							
		{
			middle = high;
			break;;
		}

		middle = low + (high - low) / 2;

		/* 如果中间元素和两边元素都相等的情况下,只能循环遍历数组,找到最小的值 */
		if (roatedArray[low] == roatedArray[middle] && roatedArray[middle] == roatedArray[high])
		{

		}

		/* 如果roatedArray[middle] >= roatedArray[low],说明middle在前面递增子数组,故low = middle */
		if (roatedArray[middle] >= roatedArray[low])
		{
			low = middle;
		}
		/* 如果roatedArray[middle] <= roatedArray[high],说明middle在后面递增子数组,故high = middle */
		else if (roatedArray[middle] <= roatedArray[high])
		{
			high = middle;
		}
	}

	return middle;
}

void Test(const char *testName, int *rotatedArray, int length, int expectedIndex)
{
	cout << testName << " : ";
	if (GetMinestValue(rotatedArray, length) == expectedIndex)
	{
		cout << "Passed." << endl;
	}
	else
	{
		cout << "Failed." << endl;
	}
}

int main()
{
	int rotatedArray1[] = {1, 2, 3, 4, 5};
	Test("Test1", rotatedArray1, sizeof(rotatedArray1) / sizeof(int), 0);
	int rotatedArray2[] = {3, 4, 5, 1, 2};
	Test("Test2", rotatedArray2, sizeof(rotatedArray2) / sizeof(int), 3);
	int rotatedArray3[] = {1, 1, 1, 0, 1};
	Test("Test3", rotatedArray3, sizeof(rotatedArray3) / sizeof(int), 3);
	int rotatedArray4[] = {5};
	Test("Test4", rotatedArray4, sizeof(rotatedArray4) / sizeof(int), 0);
	return 0;
}

例2:统计一个数字在排序数组中出现的次数。例如输入排序数组{1, 2, 3, 3, 3, 3, 4, 5}和数字3,由于3在这个数组中出现了4次,因此输出4。(剑指offer,面试题38,页数204)

如何查找排序数组中k的个数,首先我们分析如何用二分查找算法在数组中找到第一个k,二分查找算法总是先拿数组中间的数字和k作比较。如果中间的数组比k大,那么k只有可能出现在数组的前半段,下一轮我们只在数组的前半段查找就可以了。如果中间的数字比k小,那么k只有可能出现在数组的后半段,下一轮我们只在数组的后半段查找就可以了。那么如果中间的数字和k相等呢?我们先判断这个数字是不是第一个k。如果位于中间数字的前面一个数字不是k,此时中间的数字刚好就是第一个k。如果位于中间数字的前面一个数字也是K,也就是说第一个k肯定在数组的前半段,下一轮我们仍然需要在数组的前半段查找。

基于这个思路,我们很容易实现该算法找到第一个k:

/*
函数名:	GetFirstK
函数功能:	从排序数组sortedArray[start ... end]中寻找第一个值为k的位置。
函数参数:	int *sortedArray	排序数组指针
			int k				即要查找的数字k
			int start			[start,end]起始位置start
			i
首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇hdu 1598 find the most comforta.. 下一篇CF 336C(Vasily the Bear and Seq..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容:

·Python爬虫教程(从 (2025-12-26 16:49:14)
·【全269集】B站最详 (2025-12-26 16:49:11)
·Python爬虫详解:原 (2025-12-26 16:49:09)
·Spring Boot Java: (2025-12-26 16:20:19)
·Spring BootでHello (2025-12-26 16:20:15)