前言
哇,转眼就到了第三章,在前两章内(如果想看前两章的内容,请点击系列文章目录的链接),我们主要讲解了时间复杂度为O(N^2)的排序算法(冒泡排序,选择排序,插入排序),不知道大家看的如何?
在这一章,我们主要讲述二分法的使用,讲述有关二分法的三道编程题来打破大家对二分法只能用在有序数组的思想。
一、二分法的讲解与实现(C语言)
1.1 为什么在有序数组中用二分法查找?
一般地,我们在一个有序数组中查找一个数字,如果我们一一查找是要根据数据量的,有时会很快,有时会很慢!这就比较麻烦,所以我们不能用一一查找。
为什么说有时很快,有时很慢呢?假如,我们现在有一个有序数组(1,2,3,4,5,6,7,8,9,10,11,12,13),如果你要查找1的话, 一一查找只需遍历一位即可;但如果你要查找13的话,则需要将整个数组遍历完。
此时,这个要查找的数组是一个有序数组,我们就可以利用有序数组的特性去使用二分查找去寻找我们所要查找的数字。
1.2 二分查找的讲解:
在上面的引子中说在一个有序数组中使用二分查找,那么二分查找是如何实现的呢?
二分查找的基本思路是:先找到中间的数与要查找数进行比较,然后分情况:
1)如果比要查找数小,那么就舍弃左边一半的数,在右边一半的数再找到中间的数与要查找数进行比较,重复此过程;
2)如果比要查找数大,那么就舍弃右边一半的数,在左边一半的数再找到中间的数与要查找数进行比较,重复此过程;
最后,直到查找到最后一个数。
接下来,进行估算二分查找的时间复杂度,用最坏的数据进行计算,因为一次代码流程要舍弃一半的数,所以时间复杂度为O(logN)。
1.3 二分法的代码实现:
经过上面的详细分析后,小编觉得各位看官已经充分了解了二分法的思路了,那么接下来,和小编进行二分法的实现吧!
实现二分法的代码如下:
int left = 0; int right = n - 1; while (left <= (right - 1)){ //判断条件是左边指向的位置大于右边指向的位置 int mid = (left + right) / 2;//中点的位置要随时更新,以便求出最新的中点坐标 if(k > arr[n - 1]) //如果要查找的数大于数组的最后一个数时,则不需要继续查找 break; if (arr[mid] == k){ right = mid; if(arr[mid - 1] < arr[mid]) //与中间数进行比较 ans = mid; } if(arr[mid] > k) //随时更新中点坐标 right = mid; if(arr[mid] < k) left = mid; }
二、二分法的应用(不止在有序数组中)
2.1 常规用法
在上面我们已经进行过在一个有序数组中查找某一个数,接下来,我们将写出一个变式:查找某个位置。
题目:你需要输入一个n,一个数k,然后输入一个长度为n个大小的数组arr,然后你需要在arr上找满足>=K的最左位置,并且输出这个位置,如果不存在这个位置就输出-1。
看到这道题面,小编我的第一个反应是将这个有序数组进行遍历, 只要找到第一个数字等于k,则是我们所要查找的位置,那么这种方法就和上面小编写的第一道体题的思想一样,就是有时很慢。有时很快。
这篇文章中,我们学习了二分法,就要利用二分法进行解题。基本思路是:先找到中间的数与要查找数进行比较,然后分情况:
1)如果比要查找数小,>=K的最左位置应该在右边,那么就舍弃左边一半的数,在右边一半的数再找到中间的数与要查找数进行比较,重复此过程;
2)如果比要查找数等于或大于,>=K的最左位置应该在右边,那么就舍弃右边一半的数,如果等于k的话进行标记,在左边一半的数再找到中间的数与要查找数进行比较,重复此过程;最后将整个数组进行遍历,找到第一个标记的数字。
最后,直到查找到最后>=K一个数。
实现题目的代码如下:
int main() { int n = 0, k = 0; scanf("%d %d", &n, &k); int a[1000000]; for (int i = 0; i < n; i++) scanf("%d", &a[i]); int left = 0; int right = n - 1; int mid = 0; int reslut = -1; //因为如果找不到,就输出-1 while (left <= right){ mid = (left + right) / 2; if (a[mid] >= k){ reslut = mid; right = mid - 1; } else left = mid + 1; } printf("%d\n", reslut); return 0; }
2.2 非常规做法(在无需数组中进行)
二分法真的只能用在有序数组中吗?小编在了解完这个题目后,也是大吃一惊!答案是可以的!
接下来,跟随小编的步伐进行学习吧!看这道题目:局部最小值问题。
局部最小的概念:1)数组中0位置的数比1位置的数要小;
2)数组中N-1位置的数比N-2位置的数要小;
3)数组中第i位置的数要比两边位置的数都要小。
题目: 给定无序数组arr,已知arr中任意两个相邻的数都不相等,只需要返回arr中任意一个局部最小出现的位置即可,如果不存在这个位置就输出-1。
小编进行分析可得:
先判断0位置的数和1位置的数,再判断N-1位置的数和N-2位置的数。如果这两种情况都不成立,那么一定会出现一开始是下降的,在最后是上扬的,又因为数组中每两个数都不相同,所以在1到N-2中一定有局部最小。
接下来,我们就可以利用二分进行舍弃数字。找到中间位置的数与两边位置的数进行比较,如果都小于,则返回中间位置的数;如果有一边不小于,则舍弃一半,在另一半继续寻找。
实现题目的代码如下:
int main() { int n = 0; scanf("%d", &n); int a[100000]; for (int i = 0; i < n; i++) { scanf("%d", a + i); } int left = 0; int right = n - 1; int mid = -1; if (n == 1 || a[0] < a[1]) { printf("%d\n", 0); return 0; } else if (a[n - 1] < a[n - 2]) { printf("%d\n", n - 1); return 0; } else { while (left <= right) { mid = (left + right) / 2; if (a[mid] > a[mid - 1]) { right = mid - 1; } else if (a[mid] > a[mid + 1]) { left = mid + 1; } else { //flag = 1; printf("%d\n", mid); return 0; } } } return 0; }
总结
以上就是今天要讲的内容,本文介绍了二分法及其在无序数组中的应用,过几天小编会进行编写关于二分法的一些习题,希望大家看完以后,进行点评,谢谢大家!