素数问题,分解质因数,大数四则运算,欧几里得算法,分式计算

平常遇到的一些数学中的问题,整理一下。参考资料《算法笔记》


一、素数问题  

如果用一般的方法去解决素数问题当数很大的时候就会超时,《算法笔记》上提供了一种解决办法,就是取平方根。文中说到“如果在2~n-1中间存在约数,不妨把这个数设置为k,则有n%k==0,可以知道n/k也是一个n的约数,并且k与n/k中有一个一定满足小于sqrt(n),一个大于sqrt(n)”所以判断一个数是否是素数就很简单了,用2到sqrt(n)来判断。  但是这个方法说的关于sqrt(n)的方法没看懂,于是用了第二种方法:打表
打表是一个很不错的方法,特别是在用空间换时间的时候;
#include <bits/stdc++.h>
using namespace std;
///打表求素数
const int maxn = 100;                        ///打印1到100之间的素数
int used[maxn] = {0};                   
int prim[maxn] = {0};
int prNum = 0;
void IsPrime()
{
    for(int i=2; i<maxn; i++)
    { 
        if(used[i] == 0)  
        {
            prim[prNum++] = i;                    ///此数组的下标是素数的个数,对应的值是该数
            for(int j=i; j<maxn; j+=i)
            {
                used[j] = 1;
            }
        }
    }
}
void print()
{
    for(int i=0; i<prNum; i++)
        cout<<prim[i]<<" ";
}
int main()
{
    IsPrime();
    print();
    return 0;
}

二、质因子分解

很强大的理论:要获得质因数前,必须先要求出素数,然后对这些素数进行判断,如果要判断的数是素数的话,那么他不存在质因数(这里说的质因数都是不算1和本身);如果不是素数,就可以分解成素数相乘的形式。有些题目不仅要输出数,也要输出次方,为此可以写一个结构体来解决这个事情。

struct factor
{
    int x,cnt;            ///cnt表示因数x出现的次数
}fac[10];

fac[10]的大小取10就够了,因为前10个素数:2*3*5*11*13*17*19*23*29已经超出了int的表示范围

具体实现代码如下

int num = 0;
void init()
{
    int srt = (int)sqrt(n*1.0);                    ///n是在主函数中输入的要被分解的数
    for(int i=0; i<prNum && prim[i]<=srt; i++)
    {
        if(n%prim[i] == 0)                ///如果第i个素数是n的一个因数的话
        {
            fac[num].x = prim[i]; 
            fac[num].cnt = 0;             ///将cnt置为0
            while(n%prim[i] == 0)                               ///我们开始计算有几个第i个素数
            { 
                fac[num].cnt++;
                n =n / prim[i];
            }
            num++;                           ///fac数组往后扩展一位
        }
    }
    if(n!=1)       ///如果最后处理结果得到的n不为1时
    {
        fac[num].x = n;
        fac[num].cnt = 1;
    }
    num++;
}

三,大整数运算

3.1 大数的存储

    最常用的解法是用数组即可。例如:定义int型数组d[1000],例如22354994,有:d[0]=4, d[1]=9, d[2]=9,d[3]=4, d[4]=5, d[5]=3, d[6]=2, d[7]=2.  是倒着进行存储,其实我们进行读数的时候从高位到低位读,这样存储符合思维习惯,而且进行加减运算也很方便。 但是用string读入时我们需要将str[0] = '2', str[1]=2......反过来进行存储。为了方便进行大数的比较,我们可以使用结构体来进行存储。

struct bign
{
    int d[1000];
    int len;
    bign()                                                    ///这里用构造函数将其初始化
    {
        memset(d,0,sizeof(d));
        len = 0;
    }
};

这样我们在读入时用倒着存的思想:

bign input()
{
    bign a ;
    string str; cin>>str;
    a.len = str.length(); 
    int k = a.len-1;
    for(int i=0; i<a.len; i++)
    {
        a.d[k] = str[i]-'0';                ///注意这里是倒着存储
        k--;
    }
    return a;
}

3.2 大数的四则运算

有四种:1:高精度加法  2:高精度减法   3:低精度与高精度乘法   4:低精度与高精度除法

先看高精度加法:

用小学生的列竖式方法进行相加,进位可以用carry表示。因为每次加的时候个位数相加,超过10就对10取模,当做进位的数。

代码实现如下:

bign add(bign a,bign b)
{
    bign c;
    int carry = 0;
    for(int i=0; i<a.len || i<b.len; i++)
    {
        int temp = a.d[i] + b.d[i] + carry;        ///加的时候把上次的进位也加上
        c.d[i] = temp%10;            ///个位数
        carry = temp/10;                        ///进位的数
        c.len++;
    }
    if(carry != 0)                        ///表示最后进位不为零,即最高位相加时产生了进位
    {
        c.d[c.len++] = carry;
    }
    return c;
}

2:高精度减法。从低位开始相减,这里假设被减数>减数注意当高位为零时,我们需要把高位的零去掉,但是为了防止结果全是零的情况,我们要保留一个零。

bign sub(bign a, bign b)
{
    bign c;
    for(int i=0; i<a.len|| i< b.len; i++)
    {
        if(a.d[i] < b.d[i])
        {
            a.d[i+1] --;
            a.d[i] = 10+a.d[i];
        }
        c.d[c.len++] = a.d[i] - b.d[i];
    }
    while(c.len > 1 && c.d[c.len-1] == 0)                ///对高位是零的情况进行单独处理 
        c.len--;
    return c;
}

3.高精度和低精度相乘:与平时的列竖式的乘法不一样,用高精度的每一位和低精度相乘,进位什么的就和加法一样

bign mul(bign a,int x)
{
    bign c;
    int carry = 0;
    for(int i=0; i<a.len; i++)
    {
        int temp = a.d[i]*x+carry;
        c.d[c.len++] = temp % 10;
        temp = temp / 10;
    }
    while(carry != 0)
    {
        c.d[c.len++] = carry% 10;
        carry = carry / 10;
    }
    return c;
}

4.高精度和低精度相除:从高位开始除,当除数<被除数时,将高精度的数往后移一位并将原除数*10+现在高精度的数。除不尽有余数时当做进位来处理

bign div(bign a,int x)
{
    bign c;
    int temp = 0;
    for(int i=a.len-1; i>=0; i--)
    {
        if(temp != 0)
        {
            temp = temp*10+a.d[i];
        }else{
            temp = a.d[i];
        }
        while(temp < x)
        {
            i--;
            temp = temp*10+a.d[i];
        }

        c.d[c.len++] = temp/x;
        temp = temp % x;
    }
    return c;
}

四、最大公因数:就是两个数的所有公因数中最大的那个数,最小公倍数就是两个数的所有公倍数中最小的那个数

最大公因数我们一般用gcd()表示,最小公倍数我们一般用lim()表示

关于它们具体实现的代码如下:

int gcd(int a,int b)
{
    if( b == 0)
        return a;
    else return gcd(b,a%b);
}

int lim(int a,int b)                    ///最小公倍数在最大公因数的基础之上说找到的
{
    int c = gcd(a,b);
    return a*b/c;                            ///关于这个理论我不是很明白这个是为什么
}

五、分数的运算: (这里说明的分数是用结构体表示出来的)

分数的运算是:化简,加,减,乘,除

《算法笔记》里面规定了分数的约法三章:

1.分数如果是负数那么就是分母为正,分子为负

2.如果是零,那么分子是零,分母为1

3.所有的分数都已最简形式输出

这个思路实现的算法如下:

struct Fraction
{
    int up,down;                //up表示分子,down表示分母
};
Fraction reduction(Fraction res)
{
    if(res.down<0)
    {
        res.down = -res.down;
        res.up = -res.up;
    }
    if(res.up == 0)
        res.down = 1;
    else
    {
        int d = gcd(res.up,res.down);
        res.up = res.up/d;
        res.down = res.down/d;
    }
    return res;
}
对于所有的分子式的运算,最后都按照这三条约束进行化简
///add
Fraction add(Fraction f1,Fraction f2)
{
    Fraction res;
    res.up = f1.up*f2.down+f2.up*f1.down;
    res.down = f1.down * f2.down;
    res = reduction(res);
    return res;
}
Fraction sub(Fraction f1,Fraction f2)
{
    Fraction res;
    res.up = f1.up*f2.down-f2.up*f1.down;
    res.down = f1.down * f2.down;
    res = reduction(res);
    return res;
}
Fraction mul(Fraction f1,Fraction f2)
{
    Fraction res;
    res.up = f1.up*f2.up;
    res.down = f1.down * f2.down;
    res = reduction(res);
    return res;
}
Fraction div(Fraction f1,Fraction f2)
{
    Fraction res;
    res.up = f1.up*f2.down;
    res.down = f1.down * f2.up;
    res = reduction(res);
    return res;
}









猜你喜欢

转载自blog.csdn.net/qq_38851184/article/details/80329882