二分查找及有重复值的查找

二分查找

1.二分查找

二分法查找,也称为折半法,是一种在有序数组中查找特定元素的搜索算法。他的时间复杂度为O(logN)

2.在一组没有重复值的升序数组中查找所求数
//代码及其运行截图
int Binarysearch(int* str,int strlen, int target){
	int left = 0;
	int right = strlen - 1;
	while (left <= right){//如果只是left<right,则会少一次比较
		int mid = left+((right-left)>>1);//(left+right)/2容易溢出
		if (*(str + mid) < target){//要找的数在中点的右边
			left = mid+1;
		}
		else if (*(str + mid) > target){//要找的数在中点的左边
			right = mid-1;
		}
		else
		{
			return mid;
			break;
		}
	}
	return -1;
}

int main(){
	int str[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 23, 45 };
	int size = sizeof(str) / sizeof(str[0]);
	int x = Binarysearch(str, size, 11);

	cout << x << endl;
}

这段代码,在没有重复值的升序数组中,运行结果会准确输出,如下运行结果:
!

但是,如果升序数组arr[]={1,2,3,4,4,4,5,5,6,7,8}则输出的结果不会准确,此时我们需要的是出现所找的数的第一个下标或者最后一个下标。

3.在一组有重复值的升序数组中查找所求数

首先我们可以想到,通过二分法拿到一个值,然后再通过这个值向左或向右进行遍历查询,但是此时的时间复杂度为O(logN),因此,我们可以继续通过二分查找法,查找最左或者最右目标值。
查找最左目标值:

//在一组有重复值的升序数组里
int search(int* str,int strlen, int target){
	int left = 0;
	int right = strlen - 1;
	while (left <= right){
		int mid = left+((right-left)>>1);
		if (*(str + mid) >= target)  //此时,假设当mid下标所指的值等于我们所查找的值,先不进行返回,此时最高位指向mid且左移一位
			right = mid - 1;
		else
			left = mid + 1;
		if (*(str + right)<target)  //此时进行上一个条件中right下标所存的数据与我们target值的比较,如果此时此时的值是小于target,那么mid下标所存的数据则为所求数的最左值,进行输出,否则继续循环
			return mid;
		else
			continue;
	}
		return -1;
}

此时我们所求的最左值为3
查找最右目标值:

//在一组有重复值的升序数组里
//查找最右目标值与查找最左目标值的思路相似
int search(int* str,int strlen, int target){
	int left = 0;
	int right = strlen - 1;
	while (left <= right){
		int mid = left+((right-left)>>1);
		if (*(str + mid) <= target)  
			left = mid + 1;
		else
			right=mid-1;
		if (*(str + right)>target) 
			return mid;
		else
			continue;
	}
		return -1;
}

最终代码及结果:

#include<iostream>
using namespace std;
//二分查找,有重复值的二分查找


//在一组有重复值的升序数组里
int right_search(int* str, int strlen, int target){
	int left = 0;
	int right = strlen - 1;
	while (left <= right){
		int mid = left + ((right - left) >> 1);
		if (*(str + mid) <= target)
			left = mid + 1;
		else
			right = mid - 1;
		if (*(str + right) == target )
			return right;
		else
			continue;
	}
	if (left > right)
		return -1;
}


//在一组有重复值的升序数组里
int left_search(int* str, int strlen, int target){
	int left = 0;
	int right = strlen - 1;
	while (left <= right){
		int mid = left + ((right - left) >> 1);
		if (*(str + mid) >= target)  //此时,假设当mid下标所指的值等于我们所查找的值,先不进行返回,此时最高位指向mid且左移一位
			right = mid - 1;
		else
			left = mid + 1;
		if (*(str + left) == target)  //此时进行上一个条件中right下标所存的数据与我们target值的比较,如果此时此时的值是小于target,那么mid下标所存的数据则为所求数的最左值,进行输出,否则继续循环
			return left;
		else
			continue;
	}
	if (left > right)
		return -1;
}



int main(){
	int str[] = { 1, 2, 3, 4, 4, 4, 4, 4, 5, 5, 6, 7, 8, 9, 10 };
	int size = sizeof(str) / sizeof(str[0]);
	cout << "找打5的位置:" << endl;
	int right_x5 = right_search(str, size, 5);
	int left_x5 = left_search(str, size, 5);
	if (left_x5 == right_x5)
	cout << "可知所求数在此数组里只有一个,且下标为" << right_x5 << endl;
	else{
		cout << "他的最左下标值为:" << left_x5 << endl;
		cout << "他的最右下标值为:" << right_x5 << endl;
	}
	cout << "找打2的位置:" << endl;
	int right_x2 = right_search(str, size, 2);
	int left_x2 = left_search(str, size, 2);
	if (left_x2 == right_x2)
		cout << "可知所求数在此数组里只有一个,且下标为" << right_x2 << endl;
	else{
		cout << "他的最左下标值为:" << left_x2 << endl;
		cout << "他的最右下标值为:" << right_x2 << endl;
	}
}

运行截图:
在这里插入图片描述

以上都是我个人所思所得,如果有错误,欢迎在评论区指正。

### 关于二分查找在存在重复值情况下的实现 当数组中可能存在重复值时,传统的二分查找可能无法唯一确定目标值的位置。为了应对这种情况,可以通过修改标准的二分查找逻辑来找到第一个或最后一个匹配的目标值。 #### 找到第一个等于目标值的索引 通过调整比较条件,在满足 `arr[mid] >= target` 的情况下继续向左收缩右边界,从而确保最终返回的是第一个等于目标值的索引位置[^1]。 ```java public int findFirst(int[] arr, int target) { int left = 0; int right = arr.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] >= target) { right = mid - 1; // 向左侧逼近 } else { left = mid + 1; } } if (left < arr.length && arr[left] == target) { return left; } return -1; // 如果未找到则返回-1 } ``` #### 找到最后一个等于目标值的索引 与此相反,可以在满足 `arr[mid] <= target` 的条件下向右侧移动左指针,直到锁定最后一位等于目标值的索引[^2]。 ```java public int findLast(int[] arr, int target) { int left = 0; int right = arr.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] <= target) { left = mid + 1; // 向右侧逼近 } else { right = mid - 1; } } if (right >= 0 && arr[right] == target) { return right; } return -1; // 如果未找到则返回-1 } ``` 以上两种方法分别解决了寻找首个和末位相同元素的问题,适应了含有重复项的数据结构需求[^3]。 ### 时间与空间复杂度分析 无论是上述哪种变体形式,它们都保持了原生二分查找的时间复杂度 \(O(\log n)\),这是因为每一次迭代都将搜索范围缩小一半所致。然而需要注意的是,递归版本会引入额外的空间开销达到 \(O(\log n)\);而采用循环的方式,则可维持常数级别的辅助存储消耗即 \(O(1)\)。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值