分类 经典算法 下的文章

算法(C++): 自适应辛普森(Simpson)积分和二重积分算法


概述

最近matlab中存在个二重积分的函数,携程c++的就给我整懵了,因为方法比较多,很容易结果对不上,之前的数值积分是用的龙贝格积分法,应该是前十位相同。这回辛普森的精确到9位,大概是没啥大问题。
辛普森积分法利用拉格朗日插值法来近似拟合原函数,然后通过对拟合函数的积分来简化近似。
近似公式如下:

$$ \int_{a}^{b} f(x) \mathrm{d} x \approx \frac{b-a}{6}\left(f(a)+4 f\left(\frac{a+b}{2}\right)+f(b)\right. $$

拉格朗日插值

拉格朗日插值利用二次曲线(抛物线)来拟合。
在求通过点集$(x_{1},y_{1})$,$(x_{2},y_{2})$,$(x_{3},y_{3})$,···,$(x_{n},y_{n})$的曲线上,拉格朗日利用多条二次曲线来拟合。首先二次曲线 $g = f(x)$ 满足

$$ f_{i}(x_{i}) \begin{cases} 1 \quad i = j\\ 0 \quad i \neq j\\ \end{cases} $$

因此$y_{n}f_{n}(x)$ 在$x_{n}$处,取值$y_{n}$,其余点取值为0。

拉格朗日通过对$f_{i}(x)$的构造如下:

$$ f_{i}(x)=\prod_{j=i}^{1 \leq j \leq n} \frac{\left(x-x_{j}\right)}{\left(x_{i}-x_{j}\right)} $$

最终得到了:

$$ f(x)=\sum_{i=1}^{n} y_{i}f_{i}(x) $$

辛普森公式

积分式:

$$ \int_{a}^{b} f(x)dx $$

第一我们需要用拉格朗日插值法对 $f(x)$ 进行近似计算,得出:

$$ f(x) \approx L_{n}(x) $$

$$ L_{n} = \sum_{i=1}^{n} y_{i}p_{i}(x) $$

$$ p_{i}(x)=\prod_{j=/i}^{1 \leq j \leq n} \frac{\left(x-x_{j}\right)}{\left(x_{i}-x_{j}\right)} $$

进而得出:

$$ f(x) \approx L_{n}(x) = \sum_{i=1}^{n} y_{i}p_{i}(x) $$

两边积分从而得出:

$$ \int_{a}^{b} f(x)dx \approx \int_{a}^{b} \sum_{i=1}^{n} y_{i}p_{i}(x) dx = \sum_{i=1}^{n} y_{i} \int_{a}^{b} p_{i}(x) dx $$

公式中的 $y_{i}=f(x_{i})& 。 令

$$ A_{i} = \int_{a}^{b} p_{i}(x) dx $$

那么就有:

$$ \int_{a}^{b} f(x)dx \approx \sum_{i=1}^{n} A_{i} f(x_{i}) $$

此外,如果在区间[a,b]中取n个点来拟合或者近似,则有 $h=\frac{b-a}{n}$,$X_{k}=a+kh$ , 所以:

$$ A_{i} = \int_{a}^{b} p_{i}(x) dx = h \int_{n}^{0} \prod_{j=1}^{n} \frac{(t-j)}{i-j}dt=(b-a)C_{a}^{(n)} $$

进而就有了:

$$ \int_{a}^{b} f(x)dx \approx \sum_{i=1}^{n} y_{i} A_{i} = (b-a)\sum_{i=0}^{n} C_{a}^{(n)} f(x_{i}) $$

该公式为牛顿-科特斯求积公式。
如果取3个点$x_{1}=a,x_{2}=\frac{a+b}{2},x_{3}=b$,则有下面:

$$ C_{0}^{(2)} = \frac{1}{6}, C_{1}^{(2)} = \frac{4}{6}, C_{2}^{(2)} = \frac{1}{6} $$

$$ \int_{a}^{b} f(x)dx \approx \frac{b-a}{6} (f(a)+4f(\frac{a+b}{2})+f(b)) $$

自适应辛普森积分

在辛普森公式中,三点的近似精度可能满足不了工程要求得精度,为此自适应辛普森积分会根据实际情况来自动调整精度。误差大的区域会划分多个区域。具体判断准则为:

$$ |S(a,c)+S(c,b)-S(a,b)| < 15 * eps $$

其中a , b为积分上下限,$c=\frac{a+b}{2}$,eps为精度

相关代码如下:

#include<iostream>
#include<stdlib.h>
using namespace std;
double Fun(double x){
    return x*x+x;//积分函数,自定义函数
}
double simpson(double a, double b){
   double c=(b+a)/2.0;
   return (Fun(a)+4*Fun(c)+Fun(b))*(b-a)/6.0;//求辛普森近似值
}
double adsp(double a,double b, double eps, double S){//自适应辛普森递归过程
    double c=(b+a)/2.0;
    double L=simpson(a,c),R=simpson(c,b);
    if(abs(L+R-S)<=15.0*eps){//判断准则
        return L+R+(L+R-S)/15.0;
    }
    return adsp(a,c,eps/2.0,L)+adsp(c,b,eps/2.0,R);
}
double intergation(double a, double b, double eps){//求积分
    return adsp(a,b,eps,simpson(a,b));
}
int main() {
    cout<<intergation(0, 1, 0.00001)<<endl;
    system("PAUSE");
    return 0;
}

二重积分

二重积分公式:

$$ \int_{a}^{b}\int_{c}^{d}f(x,y)dx dy = \int_{a}^{b}F_{x}(y)dy \approx \frac{b-1}{6}(F_{x}(a)+4F_{x}(\frac{a+b}{2})+F_{x}(b)) $$

$$ F_{x}(y) = \int_{d}^{c}f(x,y)dx \approx \frac{d-c}{6}(f(c,y)+4f(\frac{d+c}{2},y)+f(d,y)) $$

二重积分只要根据辛普森公式固定y的值后,对f ( x , y ) f(x,y)f(x,y)求关于x的积分,之后再对y求积分就ok了。

相关代码如下:
CommonAlgorithm.h

/**
 * @file CommonAlgorithm.h
 * @author ybw (root@bug-maker.com)
 * @brief 公共算法头文件
 * @version 0.1
 * @date 2022-01-20
 * 
 * @copyright Copyright (c) 2022 CIOMP
 * 
 */
#ifndef _COMMON_ALGORITHM_H_
#define _COMMON_ALGORITHM_H_

#include <iostream>
#include <string>

#ifndef M_PI
#define M_PI 3.1415926535897932384626433832795
#endif

typedef double(*cbp)(double);
typedef double(*cbp2)(double,double);

namespace CommonAlgorithm {
    /**
     * @brief 二重积分函数(对matlab)
     * 
     * @param cbp2 回调函数
     * @param xa x上限
     * @param xb x下限
     * @param ya y上线
     * @param yb y下限
     * @param eps 步长
     * @return double 
     */
    double integral2(double (* F)(double x, double y), double xa, double xb,double ya, double yb,double eps = 1.0e-8);
    double simpsonX2(double (* F)(double x, double y), double a, double b,double y);
    double adspX2(double (* F)(double x, double y), double a,double b,double y, double eps, double S);
    double inte2(double (* F)(double x, double y), double a, double b,double y, double eps);
    double simpsonY2(double (* F)(double x, double y), double xa, double xb,double ya, double yb,double eps);
    double adspY2(double (* F)(double x, double y), double xa, double xb,double ya, double yb, double eps,double S);
    double intergation2(double (* F)(double x, double y),double xa, double xb,double ya, double yb,double eps);
}

#endif

CommonAlgorithm.cpp

/**
 * @file CommonAlgorithm.cpp
 * @author ybw (root@bug-maker.com)
 * @brief 公共算法源文件
 * @version 0.1
 * @date 2022-01-20
 * 
 * @copyright Copyright (c) 2022 CIOMP
 * 
 */
#include "CommonAlgorithm.h"

double CommonAlgorithm::simpsonX2(double (* vFunction)(double x, double y), double a, double b,double y){//a,d为积分下、上限,y为被固定的y值
   double c=(b+a)/2.0;
   return (vFunction(a,y)+4*vFunction(c,y)+vFunction(b,y))*(b-a)/6.0;//对x的辛普森近似
}

double CommonAlgorithm::adspX2(double (* vFunction)(double x, double y), double a,double b,double y, double eps, double S){//对x的自适应辛普森递归
    double c = (b + a) / 2.0;
    double L = CommonAlgorithm::simpsonX2(vFunction,a,c,y),R=CommonAlgorithm::simpsonX2(vFunction,c,b,y);
    if(abs(L+R-S)<=15.0*eps){
        return L+R+(L+R-S)/15.0;
    }
    return CommonAlgorithm::adspX2(vFunction,a,c,y,eps/2.0,L)+CommonAlgorithm::adspX2(vFunction,c,b,y,eps/2.0,R);
}
double CommonAlgorithm::inte2(double (* vFunction)(double x, double y), double a, double b,double y, double eps){//固定y后,对x的积分
    return CommonAlgorithm::adspX2(vFunction,a,b,y,eps,CommonAlgorithm::simpsonX2(vFunction,a,b,y));
}
double CommonAlgorithm::simpsonY2(double (* vFunction)(double x, double y), double xa, double xb,double ya, double yb,double eps){
    double yc=(ya+yb)/2.0;
    return (CommonAlgorithm::inte2(vFunction,xa,xb,ya,eps)+4*CommonAlgorithm::inte2(vFunction,xa,xb,yc,eps)+CommonAlgorithm::inte2(vFunction,xa,xb,yb,eps))*(yb-ya)/6.0;//对y的辛普森近似
}
double CommonAlgorithm::adspY2(double (* vFunction)(double x, double y), double xa, double xb,double ya, double yb, double eps,double S){//对y的自适应辛普森递归
    double L=CommonAlgorithm::simpsonY2(vFunction,xa,xb,ya,(ya+yb)/2,eps);
    double R=CommonAlgorithm::simpsonY2(vFunction,xa,xb,(ya+yb)/2,yb,eps);
    if(abs(L+R-S)<=15.0*eps){
        return L+R+(L+R-S)/15.0;
    }
    return CommonAlgorithm::adspY2(vFunction,xa,xb,ya,(ya+yb)/2.0,eps/2.0,L)+CommonAlgorithm::adspY2(vFunction,xa,xb,(ya+yb)/2.0,yb,eps/2.0,R);
}
double CommonAlgorithm::intergation2(double (* vFunction)(double x, double y),double xa, double xb,double ya, double yb,double eps){//求二重积分
    return CommonAlgorithm::adspY2(vFunction,xa,xb,ya,yb,eps,CommonAlgorithm::simpsonY2(vFunction,xa,xb,ya,yb,eps));
}

double CommonAlgorithm::integral2(double (* vFunction)(double x, double y), double xa, double xb,double ya, double yb,double eps){
    return CommonAlgorithm::adspY2(vFunction,xa,xb,ya,yb,eps,CommonAlgorithm::simpsonY2(vFunction,xa,xb,ya,yb,eps));
}

算法(C++): 数值积分(龙贝格积分法)


算法原理

龙贝格(Romberg)求积法是甲酸定积分$T=\int_{a}^{b}f(x)dx$的最常用算法之一。
设$T_{m}(h)$ 步长为h时利用2m-2 阶牛顿-科茨(Newton-Cotes)公式计算得到的结果:$T_{m}(\frac{h}{2})$ 为将步长h减半后用2m-2 阶牛顿-科茨公式计算得到的结果。将他们机型线性组合,便得到步长为h的2m阶牛顿-科茨公式,即:

$$ T_{m+1}(h)=\frac{4^{m}T_{m}(h/2)-T_{m}(h)}{4^{m}-1} $$

其中$T_{1}(h)$为步长为h时的梯形公式计算得到的结果。$T_{1}(h/2)$为步长为h/2时的梯形公式计算得到的结果。
在实际计算时,龙贝格求积法按下表所示的计算格式进行,直到$|T_{m+1}(h)-T_{m}(h)| < \xi$为止。

梯形法则二阶公式四阶公式六阶公式八阶公式···
$T_{1}(h)$
$T_{1}(\frac{h}{2})$$T_{2}(h)$
$T_{1}(\frac{h}{2^{2}})$$T_{2}(\frac{h}{2})$$T_{3}(h)$
$T_{1}(\frac{h}{2^{3}})$$T_{2}(\frac{h}{2^{2}})$$T_{3}(\frac{h}{2})$$T_{4}(h)$
$T_{1}(\frac{h}{2^{4}})$$T_{2}(\frac{h}{2^{3}})$$T_{3}(\frac{h}{2^{2}})$$T_{4}(\frac{h}{2})$$T_{5}(g)$···
::::::

算法实现

double CommonAlgorithm::integral(double (* vFunction)(double x), double a, double b, double eps = 1e-6){
    int m,n,i,k;
    double y[10],h,ep,p,x,s,q;

    // 迭代初值
    h=b-a;
    y[0]=h*(vFunction(a)+vFunction(b))/2.0;
    m=1; 
    n=1; 
    ep=eps+1.0;
    
    // 迭代计算
    while ((ep>=eps)&&(m<=9))
    { 
        p=0.0;
        
        for (i=0;i<=n-1;i++)
        { 
            x=a+(i+0.5)*h;
            p=p+vFunction(x);
        }

        p=(y[0]+h*p)/2.0;
        s=1.0;

        for (k=1;k<=m;k++)
        { 
            s=4.0*s;
            q=(s*p-y[k-1])/(s-1.0);
            y[k-1]=p; p=q;
        }

        ep=fabs(q-y[m-1]);
        m=m+1; 
        y[m-1]=q; 
        n=n+n; 
        h=h/2.0;
    }
    
    return(q);
}

PHP:CRC16数据校验算法


需求

在公司写个智能洗车的东西,大概流程就是车通过扫描进入车库,洗完车在扫描一次出车库。但是写代码的时候发现显示屏通讯协议中的数据需要crc16校验,那就得单独写,php有自带的crc32,就是没有16。

实现代码

function crc16($string, $length = 0) {

    $auchCRCHi = array(0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
        0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
        0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
        0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
        0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
        0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
        0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
        0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
        0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
        0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
        0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
        0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
        0x40
    );
    $auchCRCLo = array(0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
        0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
        0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
        0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
        0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
        0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
        0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
        0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
        0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
        0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
        0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
        0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
        0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
        0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
        0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
        0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
        0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
        0x40
    );
    $length = ($length <= 0 ? strlen($string) : $length);
    $uchCRCHi = 0xFF;
    $uchCRCLo = 0xFF;
    $uIndex = 0;
    for ($i = 0; $i < $length; $i++) {
        $uIndex = $uchCRCLo ^ ord(substr($string, $i, 1));
        $uchCRCLo = $uchCRCHi ^ $auchCRCHi[$uIndex];
        $uchCRCHi = $auchCRCLo[$uIndex];
    }
    return(chr($uchCRCLo) . chr($uchCRCHi));
}

具体使用

$str = "0064FFFFE30901000005000A000000" //15位代加密数据
$s = pack('H*',$str);  //数据处理16进制字符串
$t = $this->crc16($s);  //校验数据结果位A045
$res = unpack("H*", $s.$t);  //由16进制转成字符串
echo $res;  //最终结果0064FFFFE30901000005000A000000A045

算法3:无重复字符的最长子串(C)


给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
注意:答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

解题思路:

  1. 开始时两个指针first,second均指向数组起始元素,将tag数组元素全部置0。
  2. 让second向后遍历,并同时使当前子串长sizej自增。若second遇到之前未出现的元素x,就将tag[x]置为1,表示元素x出现了。
  3. 继续遍历若重新遇到了元素x(可通过tag[x]是否非0判断),就将当前子串长size与最长子串长maxsize进行比较然后更新maxsize。接下来需要跳过不可能在后续子串中出现的部分。
  4. 做一个一重循环找到之前出现的x再使first指向其后继元素。循环过程中每使first自增一次就使size自减一次。
  5. 执行1,2,3直至数组末尾
  6. 最后一次的maxsize可能并未更新,再做一次比较

代码部分

int lengthOfLongestSubstring(char * s){
    int len = strlen(s);
    if(len == 1){
        return len;
    }
    char *first,*second;
    first = second = s;
    char tag[128] = {0};
    int maxsize=0,size=0;
    while(second < s + len){
        if(!tag[*second]){
            tag[*second] = 1;
            size++;
            second++;
        }else{
            if(maxsize < size)
                maxsize = size;
            while(*first != *second){
                tag[*first] = 0;
                first++;
                size--;
            }
            first++;
            second++;
        }
    }
    if(maxsize < size)
        maxsize = size;
    return maxsize;
}

算法2:整数反转(C)


给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。

示例 1:

输入: 123
输出: 321

 示例 2:

输入: -123
输出: -321

示例 3:

输入: 120
输出: 21

注意:

假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。

代码片段:

int reverse(int x){
    int min=-2147483648,max=2147483647;
    long res=0;
    while(x!=0){
        res = res*10+x%10;
        x/=10;
    }
    return res>max || res<min ? 0:res;
}