算法-二分查找(折半查找)

本文深入讲解二分查找的基本思想、关键条件、时间复杂度及优缺点,并提供多种代码示例,包括基本二分查找、递归实现、边界查找、旋转数组查找等。适合初学者快速掌握并进阶。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.二分查找基本思想

       在有序的序列里,先将目标和中间的数值比较,如果大于中间数值,则在后半段的中间继续比对;如果小于中间数值,则在前半段的中间继续比对。以此类推,直至找到目标,或者结束查找没有找到。

二.关键条件

     (1)有序序列

     (2)顺序存储结构

三.时间复杂度

      O(logn)

四.优点和不足

    优点是比较次数少,查找速度快,平均性能好;

   缺点是要求待查表为有序表,且插入删除困难

五.代码示例:(下面例子皆以升序为例)

1.最基本的二分查找:

int binarySearch(int arr[],int arr_L,int N)
{
	int min=0;
	int max=arr_L-1;
	int mid=0;

	while(min<=max)//注意这里有等于=,不然会丢失边界的情况
	{
		//mid=int(1.0/2*(min+max));//这里有可能会产生溢出,而且需要强制类型转换,不建议使用
		mid=min+(max-min)/2;
		if(N==arr[mid])
		{
			return mid;				
		}
		else if(N<arr[mid])
		{
			max=mid-1;
		}
		else
		{
			min=mid+1;
		}
	}
	return -1;
}

这里有几个问题需要注意:

1.循环判定的条件:min<=max(如果直接是min<max就会丢失边界)

2.为了防止数据溢出:mid=min+(max-min)/2

3.当N!=arr[mid]时,min=mid+1,max=mid-1

 

2.递归实现二分查找

//递归二分查找
int binarySearch2(int arr[],int high,int low,int N)
{
	//int mid=int(1.0/2*(high+low));
	int mid=low+(high-low)/2;
	
	if(low>high)
	{
		return -1;		
	}
	if(N==arr[mid])
	{
		return mid;
	}
	else if(N>arr[mid])
	{
		low=mid+1;
		return binarySearch2(arr,high,low,N);
	}
	else
	{
		high=mid-1;
		return binarySearch2(arr,high,low,N);
	}	
}

需要注意的问题同上。

3.二分法的几种变形

(1)查找目标值区域的左边界(查找与目标值相等的第一个位置)(查找第一个不小于目标值数的位置)

//查找目标值区域的左边界(查找与目标值相等的第一个位置)(查找第一个不小于目标值数的位置)
int binarySearch(int arr[],int arr_L,int N)
{
	int min=0;
	int max=arr_L-1;
	int mid=0;

	while(min<=max)//注意这里有等于=,不然会丢失边界的情况
	{
		//mid=int(1.0/2*(min+max));//这里有可能会产生溢出,而且需要强制类型转换
		mid=min+(max-min)/2;
		if(N<=arr[mid])
		{
			max=mid-1;
		}
		else
		{
			min=mid+1;
		}
	}
	if(min<arr_L&&arr[min]==N)
	{
		return min;
	}
	else
	{
		return -1;
	}
}

(2)查找目标值区域的右边界(查找与目标值相等的最后一个位置)(查找最后一个不大于目标值数的位置)

//查找目标值区域的右边界(查找与目标值相等的最后一个位置)(查找最后一个不大于目标值数的位置)
int binarySearch(int arr[],int arr_L,int N)
{
	int min=0;
	int max=arr_L-1;
	int mid=0;

	while(min<=max)//注意这里有等于=,不然会丢失边界的情况
	{
		//mid=int(1.0/2*(min+max));//这里有可能会产生溢出,而且需要强制类型转换
		mid=min+(max-min)/2;
		if(N>=arr[mid])
		{
			min=mid+1;
		}
		else
		{
			max=mid-1;
		}
	}
	if(max>0&&arr[max]==N)
	{
		return max;
	}
	else
	{
		return -1;
	}
}

(3)查找第一个大于目标值的数/查找比目标值大但是最接近目标值的数的位置(第二题变形)

int binarySearch(int arr[],int arr_L,int N)
{
	int min=0;
	int max=arr_L-1;
	int mid=0;

	while(min<=max)//注意这里有等于=,不然会丢失边界的情况
	{
		//mid=int(1.0/2*(min+max));//这里有可能会产生溢出,而且需要强制类型转换
		mid=min+(max-min)/2;
		if(N>=arr[mid])
		{
			min=mid+1;
		}
		else
		{
			max=mid-1;
		}
	}
        return low<arr_L?low:-1;	
}

注意此处就是大于目标值的最后一位,也就是max+1,也就是退出循环的 min

(4)查找最后一个小于目标值的数/查找比目标值小但是最接近目标值的数的位置(第(1)题变形)

int binarySearch(int arr[],int arr_L,int N)
{
	int min=0;
	int max=arr_L-1;
	int mid=0;

	while(min<=max)//注意这里有等于=,不然会丢失边界的情况
	{
		//mid=int(1.0/2*(min+max));//这里有可能会产生溢出,而且需要强制类型转换
		mid=min+(max-min)/2;
		if(N<=arr[mid])
		{
			max=mid-1;
		}
		else
		{
			min=mid+1;
		}
	}
	return max>0?max:-1;
}

(6).旋转数组(无重复项)

(什么是旋转数组:将一组有序的序列左移,将移出来的部分依次连接到原序列后边(1 2 3 4 5--------->4 5 1 2 3))

int findMin(int arr[],int N)
{
	int left=0;
	int right=N-1;
	int mid=0;

	if(N==0)
	{
		return -1;
	}
	while(left<right)
	{
		mid=left+(right-left)/2;
		if(arr[mid]>arr[right])
		{
			left=mid+1;
		}
		else
		{
			right=mid;
		}
	}
	return arr[left];
}

和二分法的区别:(1)条件是right<left

                               (2)如果arr[mid]<arr[right],说明前半部分有序,最小值在后半部分;反之,说明最小值在前半部分,最后left会指向最小项

(7)旋转数组(有重复项)

int findMin(int arr[],int N)
{
	int left=0;
	int right=N-1;
	int mid=0;

	if(N==0)
	{
		return -1;
	}
	while(left<right)
	{
		mid=left+(right-left)/2;
		if(arr[mid]>arr[right])
		{
			left=mid+1;
		}
		else if(arr[mid]<arr[right])
		{
			right=mid;
		}
		else
		{
			right--;
		}
	}
	return arr[left];
}

                                    

区别就是当arr[mid]==arr[right]时,不知道最小值在右边还是左边,就让right-1;

(8)旋转序列中搜索(不含重复项)

int search(int arr[],int arr_L,int N)
{
	int left=0;
	int right=arr_L-1;
	int mid=0;
	int offset,remid;

	if(arr_L==0)
	{
		return -1;
	}
	while(left<right)
	{
		mid=left+(right-left)/2;
		if(arr[mid]>arr[right])
		{
			left=mid+1;
		}
		else
		{
			right=mid;
		}
	}
	offset=left;

	left=0,right=arr_L-1;
	while(left<=right)
	{
		mid=left+(right-left)/2;
		remid=(mid+offset)%arr_L;
		if(N==arr[remid])//arr[remid]为有序序列的中点值
		{
			return remid;
		}
		else if(N>arr[remid])
		{
			left=mid+1;//这里需要特别注意
		}
		else
		{
			right=mid-1;
		}
	}
	return -1;
}

先找到最小的值,即找到分界点,然后根据remid=(mid+offset)%len求得真实的mid位置,即arr[remid]是有序序列的中点,arr[mid]为当前序列的中点(旋转数组),因此特别需要注意二分查找时left=mid+1,而不是left=remid+1;

不需要找分界点的方法:

int search(int arr[],int arr_L,int N)
{
	int left=0;
	int right=arr_L-1;
	int mid=0;

	if(arr_L==0)
	{
		return -1;
	}
	while(left<=right)
	{
		mid=left+(right-left)/2;
		if(N==arr[mid])
		{
			return mid;
		}
		else if(arr[left]<=arr[mid])//从这里直接判断
		{
			if(N<arr[mid]&&N>=arr[left])
			{
				right=mid-1;
			}
			else
			{
				left=mid+1;
			}
		}
		else if(arr[mid]<=arr[right])
		{
			if(N>arr[mid]&&N<=arr[right])
			{
				left=mid+1;
			}
			else
			{
				right=mid-1;
			}
		}
	}
	return -1;
}

(9)旋转序列中搜索(有重复项)

int search(int arr[],int arr_L,int N)
{
	int left=0;
	int right=arr_L-1;
	int mid=0;

	if(arr_L==0)
	{
		return -1;
	}
	while(left<=right)
	{
		mid=left+(right-left)/2;
		if(N==arr[mid])
		{
			return mid;
		}
		else if(arr[mid]>arr[left])
		{
			if(N<arr[mid]&&N>=arr[left])
			{
				right=mid-1;//
			}
			else
			{
				left=mid+1;
			}
		}
		else if(arr[mid]<arr[right])
		{
			if(N>arr[mid]&&N<=arr[right])
			{
				left=mid+1;
			}
			else 
			{
				right=mid-1;
			}
		}
		else
		{
			right--;
		}
	}
	return -1;
}

待补充:二维数组中的查找

练习题目:

https://leetcode.com/problems/search-insert-position/description/

https://weiweiblog.cn/getnumberofk/

https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/

https://leetcode.com/problems/find-minimum-in-rotated-sorted-array-ii

https://weiweiblog.cn/minnumberinrotatearray/

https://leetcode.com/problems/search-in-rotated-sorted-array

https://leetcode.com/problems/search-in-rotated-sorted-array-ii/

https://weiweiblog.cn/find2array/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值