一、二分查找算法介绍
1、二分查找简介
二分查找(Binary search)也称折半查找,当我们需要在一个有序序列中寻找一个元素,我们很快就能想到按序查找的方法--顺序查找法(即从前往后寻找),但使用顺序查找法时,当有序序列中的元素较多,需要查找的元素处于序列的后端,这样查找起来就会费时费力, 这时二分查找就是一种简单且效率高的查找元素的方法
2、 二分查找的本质
很多人认为二分查找的本质是单调性,但其实不是,二分查找与单调性的关系:单调性的序列一定可以使用二分查找,而能使用二分查找的序列不一定满足单调性。
二分查找的本质其实是边界性,即判断所要寻找的元素在哪个满足条件的区域,直到区域内的元素 = 所要寻找的元素。
确定所要查找的元素k,寻找这个序列的中间值mid
区分元素k所在的区域
3、 二分查找的基本思路
确定序列的中间值mid,将 mid 与将要寻找的元素 k 进行比较,如果 mid<k ,说明 k 处于序列中间值 mid 的右侧。反之,如果 mid>k ,则元素 k 处于 mid 的左侧,这时我们就能缩小寻找的范围。当寻找的范围改变时,重新计算新范围的中间值 mid,再与元素 k 进行比较,找到 k 处于新的区域,以此类推下去,直到找到元素k在序列中所处的位置。
在进行二分查找时,假如我们需要查找的序列元素为2^32个,那我们所需要查找的次数最多为32次(即每次对于区域范围进行/2缩小处理),使用顺序查找时,我们需要查找的次数最多为4,294,967,296次!
当只需要查找一次时,时间复杂度为O(1),当需要查找的次数为32次时(假设查找的元素为2^32个),时间复杂度为O()。
以下为二分查找的演示:
↓ 要查找的元素k
0 1 2 3 4 5 6 7 8 9
确定mid
↓ mid ↓k
0 1 2 3 4 5 6 7 8 9
mid<k 区域更新为mid的右侧
↓k ↓ mid
5 6 7 8 9 mid>k 区域更新为mid的左侧
↓ mid ↓k
5 6 mid<k 区域更新为mid的右侧
↓ mid
6 mid=k 找到所要寻找的元素
↑k
二、二分查找算法代码解析
1,建立一个单调性的序列或数组
int main()
{
//创建一个存放int类型数据的数组
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { -3,-1,0,2,5,7,8,9,10,15,15 };
int arr3[20] = { 10,9,8,7,6,5,4,3,2,1 };
return 0;
}
创建的数组/序列只要满足单调性即可,即不必按照+1的顺序进行排序。
2,计算数组/序列的长度
int main()
{
//创建一个存放int类型数据的数组
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr1) / sizeof(arr1[0]);
//数组字节大小/数组单个元素的字节大小
// 40/4
return 0;
}
这里用arr1[ ] 数组进行举例说明。
一个int类型的数据为4个字节,arr1[ ] 数组存放了10个int类型的数据,整个数组大小为40,创建一个int类型的sz来接受计算之后得出的数组长度大小。
3,确定区域的左右边界
int main()
{
//创建一个存放int类型数据的数组
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr1) / sizeof(arr1[0]); //数组字节大小/数组单个元素的字节大小 40/4
int left = 0;
int right = sz - 1;
return 0;
}
数组的第一个元素下标为0,第十个元素下标为 10-1 ,因此可以确定数组的左右边界:
left=0, right = sz - 1
4,计算中间值并开始进行比较
int main()
{
//创建一个存放int类型数据的数组
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr1) / sizeof(arr1[0]); //数组字节大小/数组单个元素的字节大小 40/4
int left = 0;
int right = sz - 1;
int k = 7; //要查找的元素k
int mid = (left + right) / 2;
if (k > arr1[mid])
{
}
else if(k < arr1[mid])
{
}
else
{
}
return 0;
}
将要查找的元素与数组中间值的元素进行比较
当k为7时,k<mid 即k处于mid的右侧,这时候区域的边界就开始发生改变 ,因为k是大于mid的,因此mid元素所在的位置及左边的区域都已经不需要再排查了。
所以left 左边边界值就改变为mid的后一个区域位置,即 mid + 1,而right的值不需要改变,并计算新的mid值。
当right的边界要改变时,与left边界的改变是同理的,改变为mid的前一个区域位置,即 mid - 1
int main()
{
//创建一个存放int类型数据的数组
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr1) / sizeof(arr1[0]); //数组字节大小/数组单个元素的字节大小 40/4
int left = 0;
int right = sz - 1;
int k = 7; //要查找的元素k
int mid = (left + right) / 2;
if (k > arr1[mid])
{
left = mid + 1;
}
else if(k < arr1[mid])
{
right = mid - 1;
}
else
{
}
return 0;
}
5 ,多次循环直到找到元素k
上述的比较为一次比较的步骤,当然,我们有时候要查找的时候需要进行多次查找,这时候就需要判断什么时候查找出来或者查找结束。
按照上述的描述我们可以知道,使用二分查找时是运用区间边界来进行比较判断,当我们的左右边界重合时,是不是意味着我们找到了元素k,或者没有找到即将要退出程序。
那我们的判断语句可以将 left < right 作为条件,而我们的比较又不止一次,应该比较到条件不满足为止,因此可以使用while循环进行不断地判断,直到不满足 left < right 的条件。当我们找到了元素k,就意味着mid值就是我们所要寻找的元素k,返回mid的值,当找不到时就会返回0。
完整代码:
int main()
{
//创建一个存放int类型数据的数组
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr1) / sizeof(arr1[0]); //数组字节大小/数组单个元素的字节大小 40/4
int left = 0;
int right = sz - 1;
int k = 7; //要查找的元素k
while (left < right)
{
int mid = (left + right) / 2;
if (k > arr1[mid])
{
left = mid + 1;
}
else if(k < arr1[mid])
{
right = mid - 1;
}
else
{
printf("找到了,下标为:%d", mid);
return mid;
}
}
return 0;
}
由于mid因为数组区域的改变是不断变化的,所以应该将mid放入到while循环中,将mid进行不断的更新数据,直到找到所需要的元素k。