目录
情境设置:
从一组有序数组中1,2,3,4,5,6,7,8,9,10中找数字。
第一种方案:一一对比
Step1,将有序的数字放到数组中
Step2,设置循环,一个一个的与要找的数进行比较。
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};//将有序的数字放到数组中。
int i = 0;//要查找的数字
int n = 0;
int sz = sizeof(arr) / sizeof(arr[0]);//求数组中元素个数
scanf("%d", &i);
for (n = 0; n < sz; n++)
{
if (arr[n] == i)
{
printf("找到了,下标为%d", n);
break;
}
}
//没找到的时候下标为sz-1
if (n > sz-1)
printf("找不到");
return 0;
}
函数递归:
所谓递归,拆开理解,既存在递推,也存在回归,两个过程。
什么时候可以用函数递归呢?
在我看来,重复执行某一些相同或者类似的”动作“,可以考虑函数递归。
函数递归的中心思想就是大事化小。
个人理解:将我们重复执行的一些相同的或者类似的“动作”,写成函数,设置好限制条件,每一次递推进函数的时候,接近限制条件,最后不满足限制条件,结束递推,开始回归。回归到第一次调用我们函数的时候,递归才算结束。(有点像循环,但不一样)
函数递归有两个必要条件:
1.每一次递归之后,我们都要离递归结束的条件(限制条件)越来越近。
2.需要有一个限制条件,使我们的递归结束
第二种方案一一对比的进阶版--函数递归
我们来分析下:
重复动作:一个一个比较,我们一直在重复这个动作
限制条件的设置:我们什么时候会不比较了呢?要么是找到了,要么是没找到的时候。
我们来看代码
#include <stdio.h>
int search(int* pa,int k,int sz)// pa是指针指向首元素(arr[0])的地址
{
//sz 是最大下标 也就是 9
//比较的是 当前进入函数pa所指向的地址,与&arr[9](arr[9]的地址)比较
//pa+sz 一直指向的是 arr[9]的地址,下面有详细说明
//限制条件:每一次递归进来,判断有没有越界
if ( pa <= (pa+sz) ) //对10个元素一一找到(下标从0-9)
{
//判断当前地址所指向的元素是不是与要找的k相同
if (*pa != k)
//不是的话继续递归
//pa+1-->每递归一次,pa指向后面一个元素的地址
//sz-1-->保证了每次递归之后 pa+sz 指向的都是arr[9]的地址
//原因很简单,我们pa在递推过程中每次都向后一步,pa+sz也会后走,
//为了让pa+sz指向&arr[9]保持不变,sz必须-1
return 1 + search(pa + 1, k,sz-1);
else
//当找到了,就返回0
return 0;
}
else
//返回值可以是任意自然数,下图有说明
//不可以是负数,结合主函数中if(ret>sz)条件来看的
return 0;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]) - 1;
// sz-->数组的最大下标-->先求的是数组元素个数,再减一。
int k = 0;
scanf("%d", &k);
int ret = search(arr,k,sz);
//参数分别为 数组首元素的地址 要找的数 数组的最大下标
//ret 表示找到元素的下标 ,如果serch返回的是大于sz(9)的数,那么就是没找到
//因为最大下标为9
if (ret > sz)
printf("找不到");
else
printf("找到了,下标为%d", ret);
return 0;
}
图解:
①②...表示每一次递推的顺序
紫色的1,2,3,4...是返回的值
这里是k > 10 的情况
红色线代表递推出去,紫色线代表回归。
为什么ret接收的是10呢?(可以打开监视测试一下),我们search函数进去了11次, ,回去的时候也是11次。回去的时候呢,有10次是return 1+search() , 1次是 结束函数递归的时候,return 0 ;
所谓递归,一去一回。从哪里进来的,回哪里去,每一次回归,都是回到 return 1+ search(pa+1,k,sz-1)的地方
第三种方案:二分查找
一一对法,太过繁琐,每一次都需要比较很多的值才能确定有没有我们要找的数字。
我想大家应该有玩过猜数字的游戏吧。1-100之间的数字,让你去猜,你为了缩小范围先猜50,在看看大了还是小了,在缩小一半,以此类推。
这就是我们的二分查找。
二分查找主要的流程
知左(右)下标,求中间下标,在比较。比较一次,没找到,看k(要找的数)是在中间数的左边还是右边,调整左下标或者右下标,再求中间下标,再比较。一直重复这个动作。直到有一次,我们左下标大于右下标,结束,或者找到k也会结束。
int mid_search(int arr[],int sz,int k)
{
//最左边的下标
int left = 0;
//最右边的下标
int right = sz - 1;
//循环的限制条件
//在每一次找不到的时候,left或者right会发生改变,直到不满足条件为止,跳出循环
while (left <= right)
{
//求中间元素的下标
int mid = left + (right - left) / 2;
//用中间元素与k进行比较
//中间值大,则说明k在中间值的左边
//调整我们的最右边的下标
if (arr[mid] > k)
right = mid - 1;
//中间值小,则说明k在中间值的右边
//调整我们最左边的下标
else if (arr[mid] < k)
left = mid + 1;
//找到了就会返回中间元素的下标
else
return mid;
}
//如果没找到,我们的 left 将会大于 right 跳出循环
//返回一个sz
return sz;
}
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);//求出数组中元素的个数
int k = 0;
scanf("%d", &k);
//函数参数分别为 数组首元素地址,元素个数,要找的数
int ret = mid_search(arr,sz,k);
//ret 表示找到元素的下标。因为数组下标从0开始,最大下标为[sz-1]
//所以当没找到,返回sz,是可以判断找不到的
if (ret>sz-1)
printf("找不到");
else
printf("找到了,下标为%d",ret);
return 0;
}
图解
图片带你深入理解二分查找
下标,标记的时候是从矩形左侧的边开始标记的。也就是说,要从矩形左边从0开始数。
第四种方案二分查找进阶方案--函数递归
int mid_search(int left, int right,int* pa,int k)
{
int mid = left + (right - left) / 2; //找中间的数的下标
if (left <= right)
{
//中间元素比k要小
if (*(pa + mid ) < k)
{
left = mid + 1;
return mid_search(left, right, pa, k);
}
//中间元素比k要大
else if (*(pa + mid ) > k)
{
right = mid - 1;
return mid_search(left, right, pa, k);
}
//找到了
else
return mid;
}
//找不到
//返回一个大于sz-1的数,超过最大下标[sz-1],这里sz = 10(sz元素总个数)
return 10;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int k = 0;
int left = 0;
int right = sizeof(arr) / sizeof(arr[0]) - 1;//最大下标
scanf("%d", &k);
int ret = mid_search(left,right,arr,k);
//参数为左下标,右下标,首元素地址,要查找的数
if (ret>right)
printf("找不到");
else
printf("找到了,下标为%d", ret);
return 0;
}
图解
如何返回?看下面图理解
总结:
我们讲了从有序数组中查找数字的4种方法。
一一对比和进阶版,二分查找和进阶版
让我们更深入了解函数递归。
函数递归,既有递推,又有回归。回归的时候,从哪里来回哪里去。