FFT字符串匹配

众所周知$FFT$是一个功能多但是不开$O2$常数吓人的算法。

这里介绍一下$FFT$如何搞字符串匹配。

其实我第一次是字符暴力匹配$52$次,结果$T$了一下午。

后来上网找发现有个更好的算法。

 

如果有两个数判相等,我们可以相减,判断是否为$0$;

但是字符串匹配相当于多对数判相等,相减加和肯定不行,搞成绝对值再相加就行了。

关键是绝对值有点恶心。

所以我们不取绝对值取平方

 

比如文本串$a$,模式串$b$,

我们要判断的是$\sum((a[k+i]-b[i])^2)==0$,

把它打开,结果是$\sum((a[k+i])^2)+\sum((b[i])^2)-2*\sum(a[k+i]*b[i])$。

前边两项预处理可以$O(1)$求,后面那个反转$b$后就是卷积。

于是求一次卷积,然后带入判断结果是否为$0$即可。

不开$O2$,$NTT$常数比较小。

时间复杂度$O(nlogn)$。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 1050000;
const int MOD = 998244353;
int _id(char c)
{
    if(c>='a'&&c<='z')return (c-'a'+1);
    return (27+c-'A');
}
int to[4*N],lim=1,l;
ll fastpow(ll x,int y)
{
    ll ret = 1;
    while(y)
    {
        if(y&1)ret=ret*x%MOD;
        x=x*x%MOD;
        y>>=1;
    }
    return ret;
}
ll inv,W[4*N];
void ntt(ll *a,int len,int k)
{
    for(int i=0;i<len;i++)
        if(i<to[i])swap(a[i],a[to[i]]);
    for(int i=1;i<len;i<<=1)
    {
        ll w0 = W[i];
        for(int j=0;j<len;j+=(i<<1))
        {
            ll w = 1;
            for(int o=0;o<i;++o,w=w*w0%MOD)
            {
                ll w1 = a[j+o],w2 = a[j+o+i] * w%MOD;
                a[j+o] = (w1+w2)%MOD;
                a[j+o+i] = (w1-w2+MOD)%MOD;
            }
        }
    }
    if(k==-1)
    {
        for(int i=1;i<(len>>1);i++)swap(a[i],a[len-i]);
        for(int i=0;i<len;++i)a[i]=a[i]*inv%MOD;
    }
}
char s1[N],s2[N];
int l1,l2;
ll ss1[N],ss2,f[N];
ll a[4*N],b[4*N],c[4*N];
int main()
{
    scanf("%s%s",s1,s2);
    l1 = strlen(s1);
    l2 = strlen(s2);
    ss1[0]=_id(s1[0])*_id(s1[0]);
    for(int i=1;i<l1;++i)ss1[i] = ss1[i-1] + _id(s1[i])*_id(s1[i]);
    for(int i=0;i<l2;++i)ss2 = ss2 + _id(s2[i])*_id(s2[i]);
    while(lim<=(l1+l2))lim<<=1,l++;
    for(int i=1;i<lim;++i)to[i] = ((to[i>>1]>>1)|((i&1)<<(l-1)));
    for(int i=0;i<l1;++i)a[i] = _id(s1[i]);
    for(int i=0;i<l2;++i)b[i] = _id(s2[l2-i-1]);
    inv = fastpow(lim,MOD-2);
    for(int i=1;i<lim;i<<=1)W[i] = fastpow(3,(MOD-1)/(i<<1));
    ntt(a,lim,1),ntt(b,lim,1);
    for(int i=0;i<lim;++i)c[i]=a[i]*b[i]%MOD;
    ntt(c,lim,-1);
    for(int i=l2-1;i<l1;++i)f[i]=c[i];
    int ans = 0;
    if(ss1[l2-1]+ss2-2*f[l2-1]==0)++ans;
    for(int i=1;i+l2-1<l1;++i)if(ss1[i+l2-1]-ss1[i-1]+ss2-2*f[i+l2-1]==0)++ans;
    printf("%d\n",ans);
    return 0;
}

 

转载于:https://www.cnblogs.com/LiGuanlin1124/p/10284340.html

<think>好的,我现在需要帮助用户在STM32中实现通过串口输出FFT计算得到的幅度值。用户已经提供了一些相关的引用内容,我需要仔细分析这些引用,并结合我的知识来给出正确的步骤和建议。 首先,用户的问题是关于如何在STM32上使用串口发送FFT计算后的幅度值。根据提供的引用,特别是引用[1]、[2]、[3]和[4],这些引用提到了FFT的实现步骤,包括初始化FFT参数、执行FFT运算、计算幅度值以及如何获取峰值等。另外,引用[4]还提到用户自己用信号发生器验证了代码,并成功测量了幅值、频率和相位差,这可能涉及到串口通信的部分。 首先,我需要明确整个流程。通常,STM32上进行FFT处理并输出结果到串口的步骤大致如下: 1. **配置ADC采样**:需要采集模拟信号,例如通过ADC模块将模拟信号转换为数字信号。用户可能已经完成了这部分,但如果没有,需要指导如何配置ADC。 2. **FFT初始化**:根据引用[3],需要使用`arm_cfft_radix4_init_f32`来初始化FFT参数,设置FFT长度、变换方向等。例如,设置FFT长度为1024点,使用标准库中的函数初始化结构体。 3. **执行FFT运算**:调用`arm_cfft_radix4_f32`函数进行FFT计算,如引用[1]所示。输入缓冲区需要填充ADC采集的数据,这里需要注意数据的排列是否符合复数格式的要求。对于实数信号,可能需要将数据转换为复数形式,或者使用实数FFT函数,但根据引用[1],用户使用的是复数FFT,因此输入缓冲区可能交替存储实部和虚部,而虚部初始化为0。 4. **计算幅度值**:引用[2]中提到使用`arm_cmplx_mag_f32`函数将复数结果转换为幅度值。这一步是必须的,因为FFT输出的复数结果需要计算模值才能得到各频率分量的幅度。 5. **寻找峰值**:引用[1]中的`fft_getpeak`函数用于寻找基波和谐波峰值,这一步可能需要用户自己实现或者使用DSP库中的函数。不过,DSP库中是否有现成的峰值检测函数需要确认,如果没有,可能需要手动遍历幅度数组找到最大值及其对应的频率索引。 6. **配置串口**:用户需要通过串口发送幅度值,因此需要初始化USART模块,配置波特率、数据位、停止位等参数,并编写发送函数。 7. **数据处理与发送**:将计算得到的幅度值(最大值或所有频率点的幅度)通过串口发送出去。可能需要将浮点数转换为字符串,因为串口发送的是字节数据。例如,使用`sprintf`将浮点数值格式化为字符串,然后通过串口发送。 接下来,我需要考虑可能的实现细节和问题: - **数据缓冲区**:FFT函数需要复数输入,因此输入缓冲区的大小应为FFT长度的两倍(每个复数包括实部和虚部)。例如,1024点FFT需要2048个float类型的数组。ADC采集的数据是实数,因此需要将每个采样点的值放入实部,虚部设为0。 - **采样率与频率分辨率**:用户需要根据信号频率设置合适的采样率,以满足奈奎斯特采样定理。例如,如果信号最高频率是1kHz,采样率应至少为2kHz。频率分辨率为采样率除以FFT长度,这决定了能够区分的最小频率间隔。 - **浮点处理**:STM32F4系列支持硬件浮点运算,因此使用浮点运算效率较高。如果使用其他系列如F1,可能需要使用定点数或软件浮点,但会影响性能。 - **串口发送格式**:用户可能希望以可读的格式发送幅度值,如ASCII字符串,包含换行符以便于显示。例如,发送“Amplitude: 3.14 V\r\n”。 - **实时性考虑**:如果系统需要实时处理,需注意FFT计算和串口发送的时间,避免数据丢失。可以使用DMA进行ADC采样和串口发送,以提高效率。 根据引用[4],用户已经成功测试了FFT的准确性,并且需要将结果通过串口输出。因此,重点在于如何将已有的FFT处理结果通过串口发送出去。 可能的步骤: 1. **初始化外设**:包括ADC、定时器(触发ADC采样)、USART等。用户可能已经完成了ADC和FFT部分,需要添加串口初始化代码。 2. **采集数据并存储到FFT输入缓冲区**:ADC通过DMA或中断方式连续采样,填充到输入缓冲区的实部,虚部置零。 3. **执行FFT和幅度计算**:如引用[1]和[2]中的函数调用顺序。 4. **获取幅度峰值**:找到幅度数组中的最大值,或者根据引用[1]中的`fft_getpeak`函数获取基波幅度。 5. **格式化幅度值为字符串**:使用标准库函数如`sprintf`将浮点数转换为字符数组。 6. **通过串口发送字符串**:调用USART发送函数,将字符数组发送出去。 需要注意的问题: - **浮点转换的性能**:在STM32上使用`sprintf`可能会比较耗时,尤其是启用浮点支持时。可以考虑使用更轻量级的库,或者将浮点数转换为整数部分和小数部分分别处理。 - **缓冲区管理**:确保在发送数据时,不会覆盖正在处理的数据。可以使用双缓冲区或队列来管理发送数据。 - **错误处理**:检查ADC采样是否溢出,FFT运算是否正确,串口发送是否完成等。 根据引用[4]中的示例,用户可能需要将幅度值、频率等信息一起发送,因此可能需要计算频率对应的实际值(如频率=峰值索引*采样率/FFT长度)。 此外,用户提供的引用[1]中的`fft_getpeak`函数可能返回基波的幅度,用户需要将这个值通过串口发送。如果该函数是用户自定义的,可能需要确保其正确性,或者参考DSP库中的相关函数。 总结实现步骤: 1. 配置USART,设置波特率,启用发送功能。 2. 在FFT计算完成后,获取幅度值数组,并找到峰值。 3. 将峰值幅度转换为字符串。 4. 使用串口发送函数发送该字符串。 示例代码部分可能需要包括串口初始化的代码,例如使用STM32标准库或HAL库配置USART,以及发送数据的函数。例如,使用HAL库的HAL_UART_Transmit函数发送数据。 需要注意,用户可能使用标准库或HAL库,因此代码示例需要适配对应的库函数。例如,引用[3]中提到的初始化函数属于CMSIS DSP库,而串口部分可能使用标准外设库或HAL。 另外,浮点数的格式化可能需要启用相关支持,例如在STM32的工程设置中,勾选“Use float with printf”选项,或者重新实现printf的串口输出函数。 最后,需要提醒用户注意硬件连接,例如USART的TX引脚是否正确连接到串口转换器(如CH340),以及电平匹配等问题。</think>在STM32中实现串口输出FFT幅度值的步骤如下: ### 一、硬件与软件准备 1. **硬件连接**:确保ADC输入引脚连接待测信号,串口TX引脚连接PC或其他接收设备(如USB转TTL模块)。 2. **开发环境**:使用Keil MDK或STM32CubeIDE,并安装CMSIS-DSP库[^3]。 ### 二、代码实现步骤 #### 1. 初始化外设 - **配置ADC**:以定时器触发ADC采样,设置采样率为信号最高频率的2倍以上。 - **配置FFT参数**: ```c arm_cfft_radix4_instance_f32 scfft; arm_cfft_radix4_init_f32(&scfft, FFT_LENGTH, 0, 1); //FFT初始化 ``` - **配置串口**(以STM32F4为例): ```c UART_HandleTypeDef huart1; huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; HAL_UART_Init(&huart1); //初始化USART1 ``` #### 2. 数据采集与FFT处理 ```c float32_t fft_inputbuf[FFT_LENGTH*2]; //复数缓冲区(实部+虚部) float32_t fft_outputbuf[FFT_LENGTH]; //幅度结果 //ADC采样填充fft_inputbuf(实部为采样值,虚部置0) for(int i=0; i<FFT_LENGTH; i++){ fft_inputbuf[2*i] = adc_value[i]; //实部 fft_inputbuf[2*i+1] = 0; //虚部 } //执行FFT和幅度计算 arm_cfft_radix4_f32(&scfft, fft_inputbuf); //FFT运算[^1] arm_cmplx_mag_f32(fft_inputbuf, fft_outputbuf, FFT_LENGTH); //取模[^2] ``` #### 3. 获取幅度峰值 ```c uint32_t peak_index; float32_t max_amplitude; arm_max_f32(fft_outputbuf, FFT_LENGTH/2, &max_amplitude, &peak_index); //寻找最大幅值 ``` #### 4. 串口输出幅度值 ```c char uart_buf[50]; sprintf(uart_buf, "Amplitude: %.2f V\r\n", max_amplitude); HAL_UART_Transmit(&huart1, (uint8_t*)uart_buf, strlen(uart_buf), 100); //发送数据 ``` ### 三、关键配置说明 1. **FFT长度选择**:推荐1024点,平衡速度与精度[^3]。 2. **幅度校准**:若信号含直流分量,需跳过`fft_outputbuf[0]`(对应0Hz)。 3. **浮点转字符串优化**:使用`dtostrf`替代`sprintf`以减小代码体积: ```c char str_amp[10]; dtostrf(max_amplitude, 5, 2, str_amp); //格式化为5字符宽度、2位小数 ``` ### 四、验证与调试 1. **信号输入**:通过信号发生器输入已知幅值的正弦波(不超过3.3V)[^4]。 2. **结果对比**:使用串口助手查看输出值是否与理论值一致,误差应小于5%。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值