具体讲解练习:在一个有序数组中查找具体的某个数字n
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
//找7 在一个有序数组中查找具体的某个数字n
int k = 7;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);//用整个数组的大小除以第一个元素大小就是元素个数
for (i = 0; i < sz; i++)
{
if (arr[i] == k)
{
printf("找到了,下标是:%d", i);
break;
}
}
if (i == sz)
{
printf("找不到\n");
}
return 0;
}
这个代码没有很好的利用这个数组是有序的,上面这个写法,无序的数组也可以这样写,一个一个遍历,这种效果其实是比较慢的。
假设我买了一双鞋,这双鞋的价格呢,你不知道,你问我呢,你说,这双鞋的价格是多少呢,我说,不超过300,你会怎么猜,你肯定不会猜1块、2块、3块......,这里呢不要这样去猜,不建议,那这样你可能会猜150,我告诉你,小了,那这个时候就在150到300之间猜一个225,这个时候就会发现,你猜150的时候告诉你小了,那这个时候1到150就不用再去猜了,就去掉了一半,这样猜的效率就比较高了,而如果这个时候告诉你225也小了,那这个时候就在226到300之间再去猜一个数字,这样猜的速度就块很多。我就猜,每次猜你这个数的中间这个值。
此时让你去找1 2 3 4 5 6 7 8 9 10 里面的那个值,你会怎么找。口头说很简单,但写成代码要怎么做呢。
我先拿出中间的元素跟我要找的比一下,我看是在左边还是在右边,就可以直接去掉一半。
那首先找到这个数字里面的中间元素,怎么找呢,
我先找一个左边的下标,左边元素的下标我们叫left,这个要注意,我们知道这个下标是0
那我再找这个数组的右边元素的下标,叫right,这个下标是9
中间元素的下标是什么呢,那我们就说left和right的平均值,9和0的平均值,是4,数组元素是5
然后发现这个5比我们要找的这个k(k=7)要小,说明我要找的在5的右边,本来被查找的范围其实是1 2 3 4 5 6 7 8 9 10 ,而当我确定在我的右边时,我被查找的范围就变成了 6 7 8 9 10,这个时候我查了一次,数据就少了一半,我被查找的范围就变成了 6 7 8 9 10 的时候,被查找范围的左下标就变了,右下标没变此时left应该指到6这里,前面这个中间5的下标是4,如果我们叫mid的话,那这个时候的left应该被改成 mid+1 ,(4+1就是5嘛),5作为下标找到了我们的元素6,这个时候新的范围就是left和right锁定的范围,范围就少了一半
所以这种查找算法我们就叫折半查找,或者叫二分查找
当我发现这个地方新的范围的下标变成了5到9,左下标是5,右下标是9的时候,在元素6到10里面去找那个7,怎么找呢,再把左右下标的平均值求出来,是7,7作为下标找到是这个元素8,,然后再拿这个元素8和我要找的元素7比一下,比我找的元素7要大,说明我要找的元素在元素8的左边,这个时候被查找的范围一下就缩减到了元素6到7,
这个元素6到7的左下标还是left,但是右下标就变成了元素7的下标,也就是下标6,注意,刚刚这个6 7 8 9 10里面的这个中间元素下标mid,这个 mid-1 其实就是我们这个新的范围的right的下标,
所以就会发现在这个过程中,我们总是通过左右下标,确定一个中间元素下标,中间元素如果不是我要找的那个元素,那这个地方我们再来确定我们新的查找范围,新的查找范围的左右下标,
这个时候我们确定了左下标是5,右下标是6,5和6的平均值是5,5锁定一个元素的时候是元素6,
比我找的元素7要小,说明我要找的元素在元素6的右边,这个时候我们查找的范围就是就是元素7了,7的左下标是6,右下标还是6,6和6的平均值是6,6作为下标锁定的元素是7,和我们要找的7一样,
我找了,如果这个元素7都不是我要找的元素,那这个元素就再也找不到了,我已经把我的范围缩小到不能再缩小了。
注意,我划过的圈,就是我找过的元素,现在十个数里边,如果我要遍历,从前往后去遍历,我要找一个数字的话,十个数字最坏的情况下,我要找10次,而如果是刚刚这个情况,我折半查找算法,会发现,元素7已经是这个时候已经是刚刚最坏的情况了,如果7不是我要找的元素,就再也找不到了,会发现我找了4次,会发现效率明显提升了很多。前提条件是这个数据必须是有序的。
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
//找7 在一个有序数组中查找具体的某个数字n
int k = 7;
int sz = sizeof(arr) / sizeof(arr[0]);//用整个数组的大小除以第一个元素大小就是元素个数
//sz保存的是数组元素个数
int left = 0;//左下标
int right = sz-1;//右下标 ,如果这个数组是10个元素的话,right下标就是10-1=9,就是9,
//也就是说这个跟元素个数有关系
//当我这个左右下标都确定的时候,要找出中间元素的下标
while (left <= right) //当left小于等于right时,说明中间还有元素,还有待元素被查找
{
int mid = (left + right) / 2;//中间元素下标
if (arr[mid] < k)
{
left = mid + 1;
}
else if (arr[mid] > k)
{
right = mid - 1;
}
else
{
printf("找到了,下标是:%d\n",mid);
break;
}
}
if (left > right)
{
printf("找不到\n");
}
return 0;
}
这个代码虽然比前面写的代码要麻烦些,但这个效率要比前面的要高
查找一次,数据少一半,查找一次,数据少一半......,会发现,这个数据越多,这个效率其实越高
比如说这里有42亿个数据,查到一半,就把21亿多的数据给干掉了,查一次少一半,查一次少一半,这个效率非常快的,
这里的时间复杂度是,如果n=, 把这个n代到=32,也就是说,你有个有序的数据放在我的面前,我也只查32次就能搞定,(是42个多亿个数字)
当然,这个折半查找不一定是最快的,他的条件是比较苛刻的,是要有序的。