首页 > 编程语言 >二分查找算法

二分查找算法

时间:2024-10-09 10:23:03浏览次数:7  
标签:二分 arr target int mid len 算法 查找 left

二分查找算法

基本思路

二分查找的基本思路如下:

  1. 找到中间元素:查找过程从数组的中间元素开始,如果中间元素恰好是目标元素,则查找过程结束。
  2. 比较并缩小范围:如果中间元素不是目标元素,那么将中间元素与目标值进行比较:
    1. 如果目标值小于中间元素,则说明目标值位于当前搜索范围的左半部分。
    2. 如果目标值大于中间元素,则说明目标值位于当前搜索范围的右半部分。
  3. 重复上述过程。不断重复上述过程,每次都将搜索范围缩小一半,直到找到目标值或者发现目标元素不存在。

二分查找的关键在于每次比较后都能排除一半的搜索范围,这使得查找效率非常高,尤其是在大型数组中。

这种算法的时间复杂度为 O(log n),其中 n 是数组的长度。

代码实现

递归实现

递归实现二分查找
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define ARR_LEN(arr) (sizeof(arr)/sizeof(arr[0]))
int bs_recursion(int arr[], int left, int right, int target) {
	// 递归的出口
	// 如果区间已不存在, 则二分查找结束, 没找到目标元素
	if (left > right) {
		return -1;
	}
	int mid = left + (right - left >> 1);
	int diff = target - arr[mid];
	if (diff < 0)
		return bs_recursion(arr, left, mid - 1, target);
	if (diff > 0)
		return bs_recursion(arr, mid + 1, right, target);
	return mid;
}
int binary_search(int arr[], int len, int target) {
	bs_recursion(arr, 0, len - 1, target);
}
int main(void) {
	int arr[] = { 0,3,5,7,7,9,10, 20,100 };
	int len = ARR_LEN(arr);
	int target = 7;
	int idx = binary_search(arr, len, target);
	printf("查找目标元素%d, 它在数组中的索引是%d\n", target, idx);
	system("pause");
	return 0;
}

循环实现

循环实现二分查找
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define ARR_LEN(arr) (sizeof(arr)/sizeof(arr[0]))
int binary_search(int arr[], int len, int target) {
	int left = 0, right = len - 1;
	while (left <= right) {
		int mid = left + (right - left >> 1);
		int diff = target - arr[mid];
		if (diff < 0)	   
			right = mid - 1;
		else if (diff > 0) 
			left = mid + 1;
		else
			return mid;
	}
	return -1;
}
int main(void) {
	int arr[] = { 0,3,5,7,7,9,10, 20,100 };
	int len = ARR_LEN(arr);
	int target = 100;
	int idx = binary_search(arr, len, target);
	printf("查找目标元素%d, 它在数组中的索引是%d\n", target, idx);
	system("pause");
	return 0;
}

二分查找变体

查找数组中第一个与目标值相等的元素
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define ARR_LEN(arr) (sizeof(arr)/sizeof(arr[0]))
int binary_search_first(int arr[], int len, int target) {
	int left = 0, right = len - 1;
	while (left <= right) {
		int mid = left + (right - left >> 1);
		int diff = target - arr[mid];
		if (diff < 0)
			right = mid - 1;
		else if (diff > 0)
			left = mid + 1;
		else {
			if (mid == left || arr[mid - 1] < target)
			{
				return mid;
			}
			right = mid - 1;
		}
	}
	return -1;
}
int main(void) {
	int arr[] = { 0,3,5,7,7,9,10, 20,100 };
	int len = ARR_LEN(arr);
	int target = 7;
	int idx = binary_search_first(arr, len, target);
	printf("查找目标元素%d, 它在数组中的第一个索引是%d\n", target, idx);
	system("pause");
	return 0;
}
查找数组中最后一个与目标值相等的元素
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define ARR_LEN(arr) (sizeof(arr)/sizeof(arr[0]))
int binary_search_last(int arr[], int len, int target) {
	int left = 0, right = len - 1;
	while (left <= right) {
		int mid = left + (right - left >> 1);
		int diff = target - arr[mid];
		if (diff < 0)
			right = mid - 1;
		else if (diff > 0)
			left = mid + 1;
		else {
			if (mid == right || arr[mid + 1] > target)
			{
				return mid;
			}
			left = mid + 1;
		}
	}
	return -1;
}
int main(void) {
	int arr[] = { 0,3,5,7,7,7,9,10, 20,100 };
	int len = ARR_LEN(arr);
	int target = 7;
	int idx = binary_search_last(arr, len, target);
	printf("查找目标元素%d, 它在数组中的最后一个索引是%d\n", target, idx);
	system("pause");
	return 0;
}
查找数组中第一个大于等于目标值的元素
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define ARR_LEN(arr) (sizeof(arr)/sizeof(arr[0]))
int binary_search_first_gt(int arr[], int len, int target) {
	int left = 0, right = len - 1;
	while (left <= right) {
		int mid = left + (right - left >> 1);
		if (arr[mid] < target)
			left = mid + 1;
		else {
			if (mid == left || arr[mid - 1] < target)
			{
				return mid;
			}
			right = mid - 1;
		}
	}
	return -1;
}
int main(void) {
	int arr[] = { 0,3,5,7,7,7,9,10, 20,100 };
	int len = ARR_LEN(arr);
	int target = 7;
	int idx = binary_search_first_gt(arr, len, target);
	printf("查找数组中第一个大于等于目标值%d的元素, 它是%d, 在索引%d\n", target,arr[idx], idx);
	system("pause");
	return 0;
}
查找数组中最后一个小于等于目标值的元素
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define ARR_LEN(arr) (sizeof(arr)/sizeof(arr[0]))
int binary_search_last_lt(int arr[], int len, int target) {
	int left = 0, right = len - 1;
	while (left <= right) {
		int mid = left + (right - left >> 1);
		if (arr[mid] > target)
			right = mid - 1;
		else {
			if (mid == right || arr[mid + 1] > target)
			{
				return mid;
			}
			left = mid + 1;
		}
	}
	return -1;
}
int main(void) {
	int arr[] = { 0,3,5,7,7,7,9,10, 20,100 };
	int len = ARR_LEN(arr);
	int target = 8;
	int idx = binary_search_last_lt(arr, len, target);
	printf("查找数组中最后一个小于等于目标值%d的元素, 它是%d, 在索引%d\n", target, arr[idx], idx);
	system("pause");
	return 0;
}

典型应用

IP归属地查询

改进插入排序

二分查找是可以改进插入排序的,其核心思路是,每一轮插入排序都会有一张"新抓手牌",而"旧手牌"是一个已经有序的数组。传统的做法是:

让"新手牌"逐一和"旧手牌"有序数组中的元素逐一比较,然后确定"新手牌"的插入位置,向后移动元素,将"新手牌"插入。如下图所示:

二分查找-改进插入排序

当数据量比较大时,"旧手牌"比较多,有序数组很长时,可以考虑用二分查找,替代逐一比较查找待插入位置。那么这个二分查找是在找什么呢?

当然是查找最后一个小于等于"新手牌"值的元素,这样就可以直接将它后面的所有元素后移一位,然后再它后面直接插入"新手牌"。

当然,这种改进在实际应用中并不常见:

  1. 插入排序本身就应用于小数据集,大数据集情况下大多不会选择插入排序。
  2. 既然是应用于小数据集,数组本身较短,用二分查找的意义就不大了。对效率的提升不明显,而且还增加了复杂性。
使用二分查找改进的插入排序
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define ARR_LEN(arr) (sizeof(arr)/sizeof(arr[0]))
int binary_sort(int arr[], int left,int right, int target) {
    while (left <= right) {
        int mid = left + (right - left >> 1);
        if (arr[mid]>target)
        {
            right = mid - 1;
        }
        else
        {
            if (mid == right||arr[mid+1]>target)
            {
                return mid;
            }
            left = mid + 1;
        }
    }
    return -1;
}
void binary_insertion_sort(int arr[], int len) {
    for (int i = 1; i < len; i++)
    {
        int value = arr[i];
        // 使用二分查找找到应插入的位置
        // idx(不包括)后面的所有元素都有后移一位,idx + 1是value要插入的位置
        int idx = binary_sort(arr, 0,i-1, value);
        int j = i - 1;  // j在这里赋值为有序数组的最后一个元素
        while (j > idx) {       // 有序数组中的元素只要下标大于idx就后移 也就是不包括idx,但idx后面的所有元素都要后移
            arr[j + 1] = arr[j];
            j--;
        } // j == idx;
        arr[j + 1] = value;
    }
}
int main(void) {
    int arr[] = { 9, 5, 2, 7, 1, 3, 4, 0, 8, 6 };
    int len = ARR_LEN(arr);
    binary_insertion_sort(arr, len);
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    system("pause");
    return 0;
}

标签:二分,arr,target,int,mid,len,算法,查找,left
From: https://www.cnblogs.com/Invinc-Z/p/18453711

相关文章

  • 【无人机】基于改进粒子群算法的无人机路径规划研究[和遗传算法、粒子群算法进行比较]
      ......
  • 二分搜索与二分答案
    二分前提条件数组的有序的数组数组中无重复元素,否则查找的元素下标可以不算唯一的二分答案二分答案时需要满足答案的有界性二分答案的思路:首先判断这个解是否为可行解,然后我们”认为“这个可行解的最优解,然后以这个可行解为标准去左(右)区间寻找最优解时间复杂......
  • 代码随想录算法训练营第四天|24. 两两交换链表中的节点、19.删除链表的倒数第N个节点
    24.两两交换链表中的节点力扣题目链接(opensnewwindow)给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。输入:head=[1,2,3,4]输出:[2,1,4,3]示例2:输入:head=[]输出:[]示例3:输入:head=[1]......
  • 代码随想录算法训练营第三天|链表理论基础、203.移除链表元素、707.设计链表、206.反
    链表理论基础链表的类型单链表每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。链表的入口节点称为链表的头结点也就是head。双链表单链表中的指针域只能指向节点的下一个节点。双链表:每一个节点有......
  • 二分图的判定-染色法
    二分图如果一张无向图的N个节点可以分成A.B两个不相交的非空集合,并且同一集合内的点之间没有边相连,那么称该无向图为二分图(BipartiteGraph)。定理:二分图不存在奇环(长度为奇数的环)。因为每一条边都是从一个集合走到另一个集合,只有走偶数次才可能回到同一个集合。染色......
  • 算法:前缀和算法模版
    一维前缀和题目链接:一维前缀和模版题思路分析一:暴力O(q*N)对于每一次询问,我们都可以用一个循环计算[l,r]区间内的元素和,时间复杂度,O(q*N)每一次计算一个区间都需要去循环一次,这是不是非常的麻烦呢?我们能不能想一个办法把这个某个区间的和给存起来呢?进行反复利......
  • <免费开题>基于Python二维码生成算法研究和实现|全套源码+文章lw+毕业设计+课程设计+数
    <免费开题>基于Python二维码生成算法研究和实现|全套源码+文章lw+毕业设计+课程设计+数据库+ppt摘要随着网络应用技术的普及和发展,计算机以及移动应用系统正在飞速的发展,通过互联网平台和移动端的应用技术帮助实现了智能化及数字化的管理模式,借助系统平台实现了高效便捷的管......
  • 算法day1
    https://leetcode.cn/problems/evaluate-reverse-polish-notation/为什么string&要加&:在string&token=tokens[i];中,&表示传递的是引用(reference),而不是值。这样做的好处是避免创建token的副本,从而提高效率,特别是在处理像string这种占用较大内存的对象时。传引......
  • 【数据结构与算法】线性表
    文章目录一.什么是线性表?二.线性表如何存储?三.线性表的类型我们知道从应用中抽象出共性的逻辑结构和基本操作就是抽象数据类型,然后实现其存储结构和基本操作。下面我们依然按这个思路来认识线性表一.什么是线性表?定义线性表(LinearList)是由n(n>=0)个具有相同特性......
  • NOIP2024集训Day47 生成树+二分图
    NOIP2024集训Day47生成树+二分图B.[THUPC2022初赛]最小公倍树直接建边显然不行,考虑优化建边。对于两个点\(u,v\),\((u,v)\)的边权为\(\displaystyle\operatorname{lcm}(u,v)=\frac{u\timesv}{\gcd(u,v)}\),显然应该选择\(\gcd(u,v)\)尽可能大的点对连边,也就是......