前言
我是一个只学了两个月C语言的小白,刚开始学指针。
老师上课讲“二分查找”的时候强调只能用于有序数组,课后我本着巩固知识的态度自己写了一遍二分查找的代码,并思考能否对无序数组进行排序操作后,用二分查找法找到目标数,并找到它在无序数组中的下标。
但这样就违背了二分查找法的初衷。二分查找法本来是为了在处理一个巨大数组时减少运算量,提高效率,比如在40多亿个数据中找数,运用二分查找只需要三十几次就够了,这比逐一排查快得多的多。但如果是一串很长的无序数组,仅排序的运算量就比逐一排查要多,效率更低。
不过还是本着用于练习的想法,我决定写一写。同时对“有重复数字”的情况进行了处理。
大家随便看看,图一乐就行。
二分查找法
二分查找,也叫折半查找,它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
二分查找的基本思想是将n个元素分成大致相等的两部分,取a[n/2]与x做比较,如果x=a[n/2],则找到x,算法中止;如果x<a[n/2],则只要在数组a的左半部分继续搜索x,如果x>a[n/2],则只要在数组a的右半部搜索x.
大致思路如下:
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int left = 0;
int right = sizeof(arr)/sizeof(arr[0])-1;
int key = 7;//要找的数字
int mid = 0;//记录中间元素的下标
int find = 0;
while(left<=right)
{
mid = (left+right)/2;
if(arr[mid]>key)
{
right = mid-1;
}
else if(arr[mid] < key)
{
left = mid+1;
}
else
{
find = 1;
break;
}
}
if(1 == find )
{
printf("找到了,下标是%d\n", mid);
}
else
{
printf("找不到\n");
}
return 0;
}
这里有一点要注意:
mid = (left + right) / 2
这一行代码,mid是int类型变量,如果left和right数据过大,可能导致溢出,而运行出错。因此可做如下改进:
mid = left + (right - left) / 2
这两种运算结果相同,但第二种在运算过程中的产生的最大值会比第一种小得多,不容易出错。
无序数组的二分查找法
我在写代码过程中遇到的困难:如何在找到目标数字后,再找到它在原来的无序数组中的位置(下标)。由于我刚开始接触指针,所以无法用指针解决。经过长时间的思考,我认为核心在于将a[](输入的数组)中每个元素与其相应的下标绑定在一起。所以,我想到了二维数组。
大致思路:
1.输入a[n]数组(方便起见,用预定义将n赋值为10)
2.将a[]的元素与对应下标绑定,放入二维数组b[n][2]中
3.对b[i][1]进行排序(这里用的是冒泡法排序)
4.对排序后的b[i][1]进行二分查找,从而确定i(mid),得到原数组下标b[i][0]
#include<stdio.h>
#define n 10
int main()
{
//1.输入a[]数组
int a[n];
printf("请输入:\n");
for(int i = 0; i < n; i++)
{
scanf("%d",&a[i]);
}
//2.将a[]的下标及对应的元素绑定放入b[n][2]中
int b[n][2];
for(int i = 0; i < n; i++)
{
b[i][0] = i;
b[i][1] = a[i];
}
//3.对b[i][1]进行排序。需要交换时,记得两个数都要交换
for(int j = 1; j < n; j++)
{
for(int i = 0; i < n-j; i++)
{
if(b[i][1] > b[i+1][1])
{
int t;
//交换第一列(a[])下标
t = b[i][0];
b[i][0] = b[i+1][0];
b[i+1][0] = t;
//交换第二列(元素)
t = b[i][1];
b[i][1] = b[i+1][1];
b[i+1][1] = t;
}
}
}
//输出排序后的数组(这一步不需要)
printf("排序后的数组:\n");
for(int i=0;i<n;i++)
{
printf("%d ",b[i][1]);
}
printf("\n");
//4.对b[i][1]进行二分查找
int left = 0;
int right = n - 1;
int mid = 0;
int find = 0;//0表示没找到
int key;//要找的数
printf("请输入要找的数:\n");
scanf("%d",&key);
while(left <= right)
{
mid = left + (right - left) / 2;
if(b[mid][1] > key)
{
right = mid - 1;
}
else if(b[mid][1] < key)
{
left = mid + 1;
}
else
{
printf("找到了,a[%d]是要找的数。", b[mid][0]);
find = 1;
break;
}
}
if(find == 0)
{
printf("未找到要找的数。");
}
return 0;
}
运行结果截图:
有重复数字的情况
这里使用正常的二分查找。由于是有序数组,所以重复数字必然是连在一起的。我们需要从找到的一个数开始,往左右依次找其他数。
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,6,6,6,7,8,9,10};
int left = 0;
int right = sizeof(arr)/sizeof(arr[0])-1;
int key = 6;//要找的数字
int mid = 0;//记录中间元素的下标
int find = 0;
while(left<=right)
{
mid = (left+right)/2;
if(arr[mid]>key)
{
right = mid-1;
}
else if(arr[mid] < key)
{
left = mid+1;
}
else
{
find = 1;
break;
}
}
if(1 == find )//至少找到一个数了
{
int MID = mid + 1;//MID是找到的第一个数右侧第一个数,方便向两侧查找
printf("找到了,下标是");
while(arr[mid] == key)//向左查找
{
printf("%d ", mid);
mid--;
}
while(arr[MID] == key)//向右查找
{
printf("%d ", MID);
MID++;
}
}
else
{
printf("找不到\n");
}
return 0;
}
运行结果截图:
这里下标没按顺序输出,因为是从中间向两边查找的。如果要按顺序的话,可以用数组存一下然后再按顺序输出。也可以先向左查找,找到最左边的之后,再向右查,依次输出。
下面用第二种演示一下
if(1 == find )//至少找到一个数了
{
for(;arr[mid] == key; mid--);
mid++;
printf("找到了,下标是");
for(;arr[mid] == key; mid++)
{
printf("%d ", mid);
}
}
运行结果:
结束!