C语言程序与设计——函数(二)递归练习

在上一篇文章中接触到了递归这种编程方法,下面我们将用几个程序加深以下对递归的理解。

递归实际上就是程序调用自身的编程技巧
递归程序的组成:

  1. 边界条件处理
  2. 针对于问题的处理过程递归过程
  3. 结果返回

二分查找

首先分析二分查找的查找逻辑:

  1. 需要三个指针分别指向数组:head,tail和mid。
  2. 当arr[mid] > aim : 说明目标数据在mid 和head之间,让head指向mid ,mid 重新计算
  3. 当arr[mid] < aim : 说明目标数据在mid 和head之间,让tail指向mid ,mid 重新计算
  4. 如果出现head > tail 的情况说明,说明该数组没有该值,则返回false
    在这里插入图片描述

所以由此,我们设计二分查找函数的时候,可以确认需要传入四个参数,分别为数组arr、头指针head、尾指针tail以及目标值aim。
tip:使用二分查找时需要保证数组内的数据是单调的,我写的这一版是单调递增的。

#include<stdio.h>
#define SIZE 10

int binary_search(int *arr, int aim,int start, int len){
    if(len < start ){
        return 0;
    }
    int mid = (start + len) >> 1;
    if(*(arr + mid) == aim) return mid;
	else if(*(arr + mid) > aim) return binary_search(arr, aim, start, mid - 1);
    else if(*(arr + mid) < aim) return binary_search(arr, aim, mid + 1, len);
}

int main(){
    int arr[SIZE] = {4,5,8,9,10,15,23,34,56,96};
    int n;
    while(~scanf("%d", &n)){
        if(!binary_search(arr, n, 0, SIZE)) {
            printf("it is not existed!\n");
            continue;
        }
        printf("arr[%d] = %d\n", binary_search(arr, n, 0, SIZE), n);
    }
}

运行结果:
在这里插入图片描述

二分查找的两种特殊情况

在二分查找中有两种特殊情况

000000000011111111111:查找该数列的第一个1
111111111110000000000:查找该数列的最后一个1
这两种情况在对于抽象现实的问题,可以提供解决思路,例如当我们去筛选一组单调数据中满足条件的数据时,找到第一个(不)满足要求的数据位置,即找到分界位置。

二分查找数列需满足的条件就是需要数列是单调的,但是当数列出现两个或多个相同的元素时,该数列依然是单调的,但是我们所查找到的元素位置就是不确定的,那么对于找到分界位置,是极具意义的,具体寻找过程如下图演示过程。

- 000000000011111111111:查找该数列的第一个1

在这里插入图片描述
在这里插入图片描述

按照图示过程,可以看出,当mid值为1时,将tail指针指向mid,当mid值为0时,head指向mid+1的位置。当三个指针重合的时候即为所求位置,另外可以看出mid是向下取整的。在这其中呢还需要考虑两个特殊情况,即全为0和全为1的情况,需要考虑返回的结果与位置的正确性。这里着重需要考虑全为0的情况,全为0时依旧会返回一个索引位置,但是整个数列是都不满足的,所以我在返回处加了一组特判解决,也可以采用更优雅的方式——虚拟位(会在文末演示)。

#include<stdio.h>
#define SIZE 10
//000000000000111111111111找第一个1
int bin_search1(int *arr, int aim, int len){
    int head = 0, tail = len, mid = (head + tail) >> 1;
    while(head < tail){
       printf("head = %d, mid = %d, tail = %d\n", head, mid, tail);
        if(*(arr + mid) == aim) tail = mid;
        else if(*(arr + mid) < aim) head = mid + 1;
        mid = (head + tail ) >> 1;
    } 
    return arr[head] == aim? head:-1;
}
//递归写法
int b_search1(int *arr, int aim,int head, int tail){
    if(head >= tail){
        return arr[head] == aim ? head : -1;
    }
    int mid = (head + tail) >> 1;
    if(*(arr + mid) == aim) return b_search1(arr, aim, head, mid);
    else if(*(arr + mid) < aim) return b_search1(arr, aim, mid + 1, tail);
}
int main(){
    int arr[SIZE + 5] = {0};
    int n, num;
    scanf("%d", &n);
    for(int i = 0;i < n; i++){
        scanf("%d", &arr[i]);
    }
    printf("the first 1 is located %d\n", b_search1(arr ,1 ,0 ,n));      
}

运行结果:
在这里插入图片描述

- 111111111110000000000:查找该数列的最后一个1
在这里插入图片描述
可以看到与上一中情况不同的时tail的更新变成了mid - 1,head更新为mid。而且mid也变成了向上取整(若不使用向上取整,当head与tail相邻的时候需要会陷入死循环)

#include<stdio.h>
#define SIZE 5
//111111111111000000000000找最后一个1
int bin_search2(int *arr, int aim, int len){
    int head = 0, tail = len, mid = (head + tail + 1) >> 1;
    while(head < tail){
       printf("head = %d, mid = %d, tail = %d\n", head, mid, tail);
        if(*(arr + mid) ==  aim) head = mid;
        else tail = mid - 1;

        mid = (head + tail + 1) >> 1;
    }
    return *(arr + head) == aim ? head:-1;
}
//递归写法
int b_search2(int *arr, int aim, int head, int tail){
    if(head >= tail){
        return *(arr + head) == aim ? head : -1;
    }
    int mid = (head + tail + 1) >> 1;
    if(*(arr + mid) == aim) return b_search2(arr, aim, mid, tail);
    else return b_search2(arr, aim, head, mid - 1);
}

int main(){
    int arr[SIZE + 5] = {0};
    int n, num;
    scanf("%d", &n);
    for(int i = 0;i < n; i++){
        scanf("%d", &arr[i]);
    }
    printf("the last 1 is located %d\n", b_search2(arr ,1, 0 ,n));
}

运行结果:
在这里插入图片描述

总结
当我们在一个单调的且只有两种元素的序列找分界线的时,mid取整方向与“1”所在方向相反,mid取整方向很重要,否则会使得程序陷入死循环。同时应该考虑全为0或全为1的两种特殊情况。

欧几里得算法(辗转相除法)

  1. 欧几里得算法又名辗转相除法
  2. 迄今为止已知最古老的算法,距今2324年
  3. 用于快速计算两个数字的最大公约数
  4. 还可以用于快速求解 a ∗ x + b ∗ y = 1 a*x+b*y = 1 ax+by=1方程的一组整数解

欧几里得算法是,两个整数a和b通过不断的相除取余从而最后得出a与b的最大公约数,下面是欧几里得算法的公式推导
{ a = ε 1 r b = ε 2 r ,(其中 ε 1 ε 2 互素) a % b = ⌊ a − k b ⌋ = ⌊ ε 1 − k ε 2 ⌋ r ⇒ 可证明 r 为 a 和 b 的引述,只需证明 r 最大 若使 r 最大,则只需证明 ε 1 与 k ε 2 互素即可 ⇒ g c d ( ε 1 , k ε 2 ) = 1 \begin {cases} a = \varepsilon_1 r\\ b = \varepsilon_2r \end {cases} ,(其中\varepsilon_1\varepsilon_2互素) \\ a \% b = \lfloor a - kb \rfloor =\lfloor\varepsilon_1 - k\varepsilon_2\rfloor r \Rightarrow 可证明r为a和b的引述,只需证明r最大 \\若使r最大,则只需证明\varepsilon_1 与k\varepsilon_2互素即可 \Rightarrow gcd(\varepsilon_1 , k\varepsilon_2) = 1 {a=ε1rb=ε2r,(其中ε1ε2互素)a%b=akb=ε1kε2r可证明rab的引述,只需证明r最大若使r最大,则只需证明ε1kε2互素即可gcd(ε1,kε2)=1
由此可以求出a,b的最小公因数,由上述公式也可退出求解a,b的最大公倍数
已知 { a = ε 1 r b = ε 2 r ,(其中 ε 1 ε 2 互素) a ∗ b = ε 1 r ∗ ε 2 r = ε 1 ε 1 r 2 因为 r 为 a , b 的公约数,且 ε 1 ε 2 互素,则最小公倍数为 ε 1 ε 2 r = a ∗ b r 已知\begin {cases} a = \varepsilon_1 r\\ b = \varepsilon_2r \end {cases} ,(其中\varepsilon_1\varepsilon_2互素) \\a * b = \varepsilon_1 r * \varepsilon_2 r = \varepsilon_1\varepsilon_1 r^2 \\ 因为r为a,b的公约数,且\varepsilon_1\varepsilon_2互素,则最小公倍数为\varepsilon_1\varepsilon_2r = \frac{a*b}{r} 已知{a=ε1rb=ε2r,(其中ε1ε2互素)ab=ε1rε2r=ε1ε1r2因为ra,b的公约数,且ε1ε2互素,则最小公倍数为ε1ε2r=rab
代码很简洁,如下:

#include<stdio.h>

int gcd(int a, int b){
    return b ? gcd(b, a % b) : a;
}

int lcm(int a, int b){
    return a * b / gcd(a , b);
}

int main(){
    int a, b;
    while(~scanf("%d %d", &a, &b)){
        printf("the greatest common divisor is %d and the lcm is %d about %d and %d \n", gcd(a, b),lcm(a, b), a , b);
    }
}

运行结果:
在这里插入图片描述

拓展计算平方根

我们已经了解二分查找的查找方式,那么现在我们拓展一下,使用二分查找来计算平方根

思路:

  1. 传入被开方数n,查找范围就是从0~n
  2. 考虑到精度问题,定义一个误差范围,当tail与head的差小于误差时就可输出
  3. 注意数据类型,这里推荐全都使用double

#include<stdio.h>
#include<math.h>
#define EPSL 1e-6

double my_sqrt(double n){

    double mid = n / 2.0, tail = n, head = 0;
    while(tail - head > EPSL){
        if(mid * mid < n) head = mid;
        else tail = mid;
        mid = (head + tail) / 2.0;
    }
    return mid;
}

int main(){
    double n;
    while(~scanf("%lf", &n)){
        printf("my_sqrt(%lf) = %lf\n", n, my_sqrt(n));
        printf("sqrt(%lf) = %lf\n", n, sqrt(n));
    }
}

在上述代码中存在一个bug,该程序只能计算大于1的程序,这里我们可以通过加一个特判的形式,通过if用两部分代码来进行实现全部情况,但是这里有一个更为简洁的方式就是令tail = n + 1。
因为主要问题在于小于0的被开方数是小于开放结果的,所以导致我们head和tail往相反的两边跑,使得不能逼近开方结果,所以只要先令tail大于1也就可以使得head和tail从两侧逼近开方结果,下面结果与c语言的sqrt()函数有些许的小出入,是我们定义的精度问题。可以让精度更小就更贴近真实值。
在这里插入图片描述

牛顿法

在计算机中实现sqrt()函数,开方运算的实际上就是使用的牛顿法,在这里我们也实现一下。
如下图所示,以求 4 \sqrt4 4 为例子,我们定义一个函数
f ( x ) = x 2 − 4 f(x) = x^2 - 4 f(x)=x24
求出f(x)的零点,大于0的 x 1 x_1 x1即为平方结果,首先就是我们可以在函数上取 ( 4 , f ( 4 ) ) (4, f(4)) (4,f(4))的点然后通过求导得出该点切线的斜率,通过点斜式的方法求出切线方程 l 1 l_1 l1,然后求出 l 1 l_1 l1的零点,求得 x 1 x_1 x1然后将 x 1 x_1 x1带入到原函数 f ( x ) f(x) f(x)中求得第二点 ( x 1 , f ( x 1 ) ) (x_1, f(x_1)) (x1,f(x1))在通过之前的方法求出 x 3 x_3 x3,如此往复使得, x n x_n xn不断逼近所求平方根,当 x n − 0 < E S P L x_n - 0 < ESPL xn0<ESPL时, x n x_n xn即为所求

#include<stdio.h>
#include<math.h>
#define EPSL 1e-6

double y(double x, double n){
    return x * x - n;
}
double partial_y(double x){
    return 2 * x;
}

double newton(double (*F)(double, double), double (*f)(double),double n){
    double x = 1.0;
    while(fabs(F(x, n)) > EPSL){
        x -= F(x, n) / f(x);
    }
    return x;
}

int main(){
    double n;
    while(~scanf("%lf", &n)){
        printf("mysqrt(%lf) = %lf\n", n, newton(y, partial_y,n));
        printf("sqrt(%lf) = %lf\n", n, sqrt(n));
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/446299.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

XXE漏洞基本原理(原理+靶场复现漏洞)

一、XXE漏洞与xml&#xff1a; 1、XXE漏洞的概念与基本原理&#xff1a; XXE漏洞&#xff0c;全称&#xff1a;"XML External Entity Injection"。 这种漏洞发生在应用程序解析XML输入数据时&#xff0c;如果没有禁止或限制对外部实体的引用和加载&#xff0c;那么…

【基于HTML5的网页设计及应用】——float实现页面布局

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

MySQL-----存储过程

▶ 介绍 存储过程是事先经过编译并存储在数据库中的一段SQL语句的集合&#xff0c;调用存储过程可以简化应用开发人员的很多工作&#xff0c;减少数据在数据库和应用服务器之间的传输&#xff0c;对于提高数据处理的效率是有好处的。 存储过程思想上很简单&#xff0c;…

字典树、并查集

字典树 字典树&#xff08; T r i e Trie Trie 树&#xff09;是一种由 “节点” 和 “带有字符的边” 构成的树形结构。典型应用是用于统计和排序大量的字符串&#xff08;但不仅限于字符串&#xff09;&#xff0c;经常被搜索引擎系统用于文本词频统计。优点&#xff1a;最大…

【三两波折】指向函数的指针

函数占用内存&#xff0c;在虚拟内存中属于txt段&#xff08;只读&#xff09;&#xff0c;函数也是有地址的。 函数指针的定义&#xff1a; (返回值类型)(*函数指针名)(参数列表) 当我们调用Proc函数时&#xff0c;一般写作&#xff1a; double ans Proc(6, 7.8f); 实际上是C…

Intel® Extension for PyTorch*详细安装教程

最近在研究Intel的pytorch的加速拓展Intel Extension for PyTorch*,但是发现官网的文档全是英文的&#xff0c;不太好找安装教程。所以特此分享Intel Extension for PyTorch*的详细安装教程。 文章目录 一、安装所需系统要求1.1 硬件需求1.2 软件需求 二、准备2.1 安装驱动程序…

搭建nacos集群,并通过nginx实现负载均衡

nacos、eureka、consul、zookeeper等都是常用的微服务注册中心&#xff0c;这篇文章详细介绍一下在Ubuntu操作系统上搭建一个nacos的集群&#xff0c;以及通过nginx的反向代理功能实现nacos的负载均衡。 目录 一、安装nacos 1、安装nacos 2、修改nacos配置文件 3、创建naco…

【Hadoop大数据技术】——HDFS分布式文件系统(学习笔记)

&#x1f4d6; 前言&#xff1a;Hadoop的核心是HDFS&#xff08;Hadoop Distributed File System&#xff0c;Hadoop分布式文件系统&#xff09;和MapReduce。其中&#xff0c;HDFS是解决海量大数据文件存储的问题&#xff0c;是目前应用最广泛的分布式文件系统。 目录 &#x…

智慧公厕_智慧化公厕_智慧的公厕_公厕智慧化_智能智慧公厕_智慧化的公厕

在当代城市发展中&#xff0c;智慧公厕作为公共厕所信息化的主要表现形式&#xff0c;正在以惊人的速度推动着城市公共环境卫生的智慧化进程。作为智慧城市体系的重要组成部分&#xff0c;智慧公厕不仅提供方便、卫生的公共厕所服务&#xff0c;还提升了城市整体形象&#xff0…

H5带建站时长可自定义背景官网/引导页源码

源码名称&#xff1a;带建站时长可自定义背景官网/引导页源码 源码介绍&#xff1a;一款带动态时间显示建站时长的引导页源码&#xff0c;可用于引导页、工作室官网、个人主页等。源码为H5自适应手机端、电脑端。 需求环境&#xff1a;H5 下载地址&#xff1a; https://www.…

java学习(集合)

一.集合(主要是单列集合和双列集合) 1.集合的框架体系&#xff08;两大类&#xff09; 2.collection接口是实现类的特点&#xff1a; 1)collection实现子类可以存放多个元素&#xff0c;每个元素可以是Object 2)有效Collection的实现类&#xff0c;可以存放重复的元素&#…

Vue.js+SpringBoot开发海南旅游景点推荐系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户端2.2 管理员端 三、系统展示四、核心代码4.1 随机景点推荐4.2 景点评价4.3 协同推荐算法4.4 网站登录4.5 查询景点美食 五、免责说明 一、摘要 1.1 项目介绍 基于VueSpringBootMySQL的海南旅游推荐系统&#xff…

MVCC------Mysql并发事务控制的工具

这里写目录标题 一.MVCC是什么二.原理1.隐藏的默认字段2.Undo log3.Undo log版本链4. ReadView 三.回答 一.MVCC是什么 MVCC 是 Multi-Version Concurrency Control&#xff08;多版本并发控制&#xff09;的缩写&#xff0c;是数据库系统中常用的一种并发控制方法。在MVCC 中…

高效管理百万级数据:MySQL备份与恢复实战指南

简介 在当今数字化时代&#xff0c;数据是企业不可或缺的核心资产之一&#xff0c;而MySQL作为一种流行的关系型数据库管理系统&#xff0c;其百万级数据的高效管理显得尤为重要。本实战指南将深入探讨MySQL备份与恢复的关键策略&#xff0c;为您提供全面而实用的解决方案。通…

SpringBoot中RestTemplate 发送http请求

SpringBoot中RestTemplate 发送http请求 引入fastjson <!--fastjson--> <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.47</version> </dependency>创建配置文件 新建c…

链表中的经典问题——反转链表

经典问题 对于链表的结构还不太清晰的同学&#xff0c;可以看我的另一篇文章&#xff0c;实践总结&#xff1a;一篇搞懂链表——单链表和双指针技巧 反转链表 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 方法一&#xff0c;迭代法…

牛客周赛 Round 36

赛况 C题可惜&#xff0c;比赛时模拟没有想明白&#xff0c;只对了一半&#xff0c;赛后看了大佬们的题解后恍然大悟&#xff0c;而F题是压根没思路&#xff0c;况且F题部分分也比较难拿。 题目列表 A-小红的数位删除 思路 将读入的数字整除10做三次后输出即可 参考代码 #inc…

【数据结构】详解时间复杂度和空间复杂度的计算

一、时间复杂度&#xff08;执行的次数&#xff09; 1.1时间复杂度的概念 1.2时间复杂度的表示方法 1.3算法复杂度的几种情况 1.4简单时间复杂度的计算 例一 例二 例三 1.5复杂时间复杂度的计算 例一&#xff1a;未优化冒泡排序时间复杂度 例二&#xff1a;经过优化…

蓝桥杯备战刷题-滑动窗口

今天给大家带来的是滑动窗口的类型题&#xff0c;都是十分经典的。 1&#xff0c;无重复字符的最长子串 看例三&#xff0c;我们顺便来说一下子串和子序列的含义 子串是从字符串里面抽出来的一部分&#xff0c;不可以有间隔&#xff0c;顺序也不能打乱。 子序列也是从字符串里…

5分钟教你使用pyarmnn推理引擎识别一只可爱猫咪~

视频 5分钟教你使用pyarmnn推理引擎识别一只可爱猫咪&#xff5e; 添加仓库 sudo apt install software-properties-common sudo add-apt-repository ppa:armnn/ppa sudo apt update 安装pyarmnn sudo apt-get install -y python3-pyarmnn armnn-latest-all python3-pip安装…