【C语音】——函数递归

一、 什么是递归

1.1 递归是什么

  
  学习C语言函数,递归是永远绕不过去的一个话题,那么。什么是递归呢?
  递归其实是一种解决问题的方法,在C语言中,递归就是自己调用自己
  
  下面列举一个最简单的C语言递归代码:

#include<stdio.h>

int main()
{
	printf("hello world\t");
	main();
	return 0;
}

  上述代码即是main函数不断调用main函数,即不断自己不断调用自己。但上面的代码只是为了演示递归的基本形式,不是为了解决问题。因为代码不断自己调用自己,一直无法停止,所以该代码最终会陷入死循环,导致栈溢出1(Stack overflow)
  
  注:Stack overflow同时也是一个程序员技术问答社区网站

1.2 递归的思想

  把一个复杂的问题转换为由多个小问题的组成,同时,分割的无数个小问题又与原问题类似,如此来进行求解;当小问题不能再被拆分,问题解决,递归结束。递归的思想就是大事化小,小事化了
  
  递归中的递就是递推的意思,归就是回归的意思。

  • 递:程序不断深入地调用自身,通常传入更小或更简化的参数,直到达到“终止条件”。
  • 归:触发“终止条件”后,程序从最深层的递归函数开始逐层返回,汇聚每一层的结果。

1.3 递归的必要条件

  
  相信大家看了前面的例子,都会意识到:递归不能让他自己乱跑,必须有一个限制条件,不然程序容易陷入死循环。
  其实,限制条件正是递归的其中一个必要条件。
  
递归的两个必要条件:

  • 递归存在限制条件,当满足这个限制条件时,递归终止。
  • 每一次递归之后,需越来越接近这个限制条件。

二、 递归举例

2.1、例一: 计算 N 阶乘

  计算 n n n 的阶乘,用以前学过的知识想必大家都会
  
参考代码:

#include<stdio.h>

int main()
{
	int n = 0;
	scanf("%d", &n);
	int sum = 1;
	int i = 1;
	for (i = 1; i <= n; i++)
	{
		sum *= i;
	}
	printf("%d\n", sum);
	return 0;
}

  那么运用递归的方法又该怎么写呢?
  
  我们都知道,阶乘的公式: n ! = n ∗ ( n − 1 ) n! = n*(n-1) n!=n(n1),同理,即可退出 ( n − 1 ) ! = ( n − 1 ) ∗ ( n − 2 ) (n-1)! = (n-1)*(n-2) (n1)!=(n1)(n2)
  那么,递归的思路就有了,当 n = = 0 n==0 n==0 时,结果为 1 ,而当 n > 1 n>1 n>1 时,都可以通过公式进行递推。
  
图示:

在这里插入图片描述

  
参考代码:

#include<stdio.h>
//递归函数
int Fact(int n)
{
	if (n == 0)
		return 1;
	else
		return n * Fact(n - 1);
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int sum = Fact(n);
	printf("%d\n", sum);
	return 0;
}
  •  递归的条件: n > 0 n > 0 n>0
  •  限制条件: n = 0 n = 0 n=0
  •  每递推一次, n n n减一,不断逼近限制条件


画图推演:

在这里插入图片描述

  

2.2、 例二:逆序打印一个整数的每一位

  
  用以前的知识,我们可以这样写:

#include<stdio.h>

int main()
{
	int n = 0;
	scanf("%d", &n);
	while (n)
	{
		printf("%d ", n % 10);
		n /= 10;
	}
	return 0;
}

  其实,运用以前的知识,不难发现里面也隐藏着递归的思想:当该数为个位数时,打印他自身;当不是个位数,打印他最后一位数,并让他的位数不断减小就行了。
  
参考代码:

#include<stdio.h>

void Print(int n)
{
	if (n < 10)
		printf("%d ", n);
	else
	{
		printf("%d ", n % 10);
		return Print(n / 10);
	}
}

int main()
{
	int m = 0;
	scanf("%d", &m);
	Print(m);
	return 0;
}

  当然,Print函数还可以如下简化:

void Print(int n)
{
	printf("%d ", n % 10);
	if(n >= 10)
	{
		return Print(n / 10);
	}
}

  
图片演示:

这里是引用

  

三、 递归与迭代

3.1、 递归的缺点

  
  递归是一种解决问题的好方法,但我们也需要了解递归的缺点,防止递归被误用。
  
  像前面的求 n n n 的阶乘:在C语言中,函数的每一次调用,都用为函数申请一块空间,该空间称为函数栈帧
  而函数递归,在递推阶段时,函数每一次调用自身都用像内存申请空间,如果函数不返回,这些空间会被一直占用,直到回归阶段,函数才开始返回,才开始由深到浅逐层释放栈帧空间。
  当递归的层次太深,就会浪费太多栈帧空间,也有可能引起栈溢出问题(stack overflow)。
  
  事实上,递归解决问题的效率并不高。

3.2、 用递归解斐波那契数列

  下面我们用一个例子可以更加形象理解这一点:
  
求第 n n n 个斐波那契函数,公式如下:

在这里插入图片描述

  相信看到了公式,大家的解题思路呼之欲出了吧
  
参考代码:

#include<stdio.h>

int Fib(int n)
{
	if (n <= 2)
		return 1;
	else
		return Fib(n - 1) + Fib(n - 2);
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	printf("%d\n", Fib(n));
	return 0;
}

  但是,执行代码我们发现:当输入一个较大的数时,计算结果要很长时间才能求出来,这表明计算过程太过冗长,效率太低

  为什么会这么慢呢?我们来统计一下计算 n = = 40 n == 40 n==40 时, F i b ( 3 ) Fib(3) Fib3计算了多少次

#include<stdio.h>

int count = 0;
int Fib(int n)
{
	if (n == 3)
		count++;
	if (n <= 2)
		return 1;
	else
		return Fib(n - 1) + Fib(n - 2);
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	printf("结果 = %d\n", Fib(n));
	printf("计算了%d次\n", count);
	return 0;
}

  
运行结果:
在这里插入图片描述  如图,仅仅是 n = = 3 n == 3 n==3就计算了这么多次,那还有 4 , 5 , 6 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 4, 5, 6······ 4,5,6⋅⋅⋅⋅⋅⋅可想而知计算过程是有多么冗余了。
  
  为什么会这样呢?
图解:

在这里插入图片描述

注:虽然这代码进行了许多重复计算,但并没有栈溢出,因为他的层次并不是特别深,当 n = = 50 n == 50 n==50 时,层次最多50层。

3.3、 什么是迭代

  
迭代的定义

  迭代是重复反馈过程的活动,其目的通常时为了逼近所需目的或结果。每一次对过程的重复称为一次迭代,而每一次迭代得到的结果会作为下一次迭代的初始值。
  
  可以说迭代就是一种不断用变量的旧值递推出新值得过程,一般会用一个计数器来判断是否结束迭代。
  
  在现阶段的学习中,我们可以简单地认为迭代通常就是循环

3.4、 用迭代解斐波那契数列

  
解题思路:
  我们都知道,斐波那契数列前两个数是 1 1 1 ,后面的数是前两个数的相加,那么只要我们从前往后,不断更新前两个数的值,从小到大计算就行了。
  
参考代码:

#include<stdio.h>

int Fib(int n)
{
	int a = 1;
	int b = 1;
	int c = 1;
	while (n > 2)
	{
		c = a + b;
		a = b;
		b = c;
		n--;
	}
	return c;
}
int main()
{
	int m = 0;
	scanf("%d", &m);
	int ret = 0;
	ret = Fib(m);
	printf("%d\n", ret);
	return 0;
}

  用迭代的方法去实现这个代码,效率就要高很多了。
  

3.5、 迭代与递归总结

  可能我们看到的许多问题都是用递归的思想进行解释的,是因为递归思想理解起来更容易、更清晰。但事实上,递归解决问题的效率并不高。
  
  有时候,写一个代码,虽然递归难以想到,但是使用递归写出的代码会非常简单,往往一个代码使用递归可能就是几行代码,而写成非递归(迭代)的方式,就得十几行甚至几十行代码。因此递归实现的这种简洁性,就弥补了效率低下的缺点。
  但如果递归的不恰当书写,会导致一些无法接受的后果,我们应该放弃使用递归,使用迭代来解决问题。
  
  后期学习数据结构的时候,经常会使用递归
  在面试(笔试)的时候,你使用递归很快的解决了代码问题,但遇到“变态”的面试官,想考察你的代码能力,就会让你改成非递归(迭代)~

  
  

  好啦,本期关于函数递归就介绍到这里啦,希望本期博客能对你有所帮助,同时,如果有错误的地方请多多指正,让我们在C语言的学习路上一起进步!


  1. 栈溢出:每一次函数的调用都会向内存(栈区)申请空间来保存函数调用期间各种变量的值,称为:函数栈帧,而内存是有限的,超出内存的存储空间则为栈溢出↩︎

### 关于语音识别解码函数的实现方法 在语音识别领域,解码过程是一个核心环节,它负责将声学模型输出的概率分布转换为目标文本序列。以下是关于解码函数的一些常见实现方式及其背景: #### 1. **基于注意力机制的解码** 对于端到端的语音识别系统,尤其是那些依赖注意力机制的方法,解码通常涉及一个递归神经网络(RNN)或者Transformer架构中的自回归模块。这些模型能够逐步生成目标单词或字符序列,每次预测下一个最可能的词元[^1]。 ```python import torch.nn as nn class AttentionDecoder(nn.Module): def __init__(self, hidden_size, output_size): super(AttentionDecoder, self).__init__() self.attention = nn.MultiheadAttention(hidden_size, num_heads=8) self.output_layer = nn.Linear(hidden_size, output_size) def forward(self, encoder_outputs, context_vector): attention_output, _ = self.attention(context_vector, encoder_outputs, encoder_outputs) prediction_scores = self.output_layer(attention_output) return prediction_scores ``` 上述代码展示了一个简单的多头注意力解码器框架,其中`encoder_outputs`代表编码后的特征向量集合,而`context_vector`则是当前时间步的状态输入。 #### 2. **CTC (Connectionist Temporal Classification) 解码** CTC是一种无须显式对齐的方式训练序列模型的技术,在这种情况下,解码可以简单地取每帧的最大概率作为最终结果——即贪婪搜索;也可以利用束搜索算法来探索更优路径组合。 ```python from collections import deque def ctc_beam_search_decoder(probs_seq, beam_width=10): beams = [(tuple(), 1.0)] for t in range(len(probs_seq)): new_beams = [] current_probs = probs_seq[t] for prefix, prob in beams: # Option to extend the sequence with a blank or any other character. next_chars = set([c for c in range(len(current_probs))]) for c in next_chars: p_char = current_probs[c]*prob if c == len(current_probs)-1: # Blank symbol case new_prefix = prefix elif not prefix or (prefix[-1]!=c): new_prefix = tuple(list(prefix)+[c]) else: new_prefix = prefix[:-1]+(c,) new_beams.append((new_prefix,p_char)) sorted_beams = sorted(new_beams,key=lambda x:x[1],reverse=True)[:beam_width] beams = sorted_beams best_sequence,_ = max(beams,key=lambda x:x[1]) return list(best_sequence) ``` 此Python脚本提供了一种基础形式的CTC波束搜索解码逻辑,允许指定宽度参数控制候选数量大小。 #### 3. **DNN-HMM混合系统的Viterbi解码** 传统上,HMM-DNN结合使用的场景下,会运用维特比(Viterbi)算法完成最佳状态序列的选择操作。这一步骤实际上就是在给定观测条件下找到可能性最高的隐藏状态轨迹。 ```pseudo-code function VITERBI_DECODE(ObservationSequence O, HMM Model λ=(A,B,π)): N ← number of states in model; T ← length of observation sequence; Delta ← array[N,T]; Psi ← array[N,T]; // Initialization step For each state i from 1 to N do: Delta[i][1] := πi * bi(o₁); Psi[i][1] := 0; // Recursion steps For time point t from 2 to T do: For each state j from 1 to N do: Find k maximizing {Delta[k][t−1]*akj}; Set Δ[j][t]:=Δ[k][t−1]*akj*bj(ot),Ψ[j][t]:=k; // Termination & Path backtracking P*:=max{Δ[i][T]}; S[T]:=argmax{Δ[i][T]}; For s from T downto 2 do: S[s−1]=Psi[S[s]][s]; Return Sequence S and Probability P* end function ``` 伪代码描述了标准版本下的维特比解码流程,适用于离散型观察值情况下的最优路径追踪需求。 --- ####
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值