【算法笔记】第五章:数学问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sd4567855/article/details/88037921

【算法笔记】第五章:数学问题

标签(空格分隔):【算法笔记】


第五章:入门篇(3)——数学问题

5.1 简单数学

5.2 最大公约数与最小公倍数

5.2.1 最大公约数

欧几里得算法(辗转相除法):设a、b为两个整数(假设a>b),则gcd(a,b) = gcd(a, a%b);

5.2.2 最小公倍数

最小公倍数的求解是建立在 最大公约数 上的。
当得到 a 和 b 的最大公约数 d 之后,可以得到a与b的最小公倍数为 ab/d.

5.3.1 分数的表示和化简

分数的表示:对于一个分数来说,最简洁的写法就是写成 假分数 的形式,即无论分子和分母的大小如何,均保留其原数。
可以用结构体来存储这种只有分子和分母的分数。

struct Fraction{
    int up;
    int down;
}

这样表示,可以制定三项规则:1. 分母down不可以为负数。若分数为负数,则分子up为负数。2. 如果分数为0, 则规定分子up 为 0, 分母down 为1. 3. 分子和分母没有除了1之外的公约数。

分数的化简:主要看约分:求出分子绝对值与分母绝对值的最大公约数d,然后令二者同时除以d.

5.3.2 分数的四则运算

分数的加减法:对于给定的两个分数 f1 和 f2,其加减法公式为:
r e s = f 1. u p f 2. d o w n ± f 2. u p f 1. d o w n f 1. d o w n f 2. d o w n res = \frac{ f1.up * f2.down \pm f2.up * f1.down}{f1.down * f2.down}

分数的乘法:对于给定的两个分数 f1 和 f2,其乘法公式为:
r e s = f 1. u p f 2. u p f 1. d o w n f 2. d o w n res = \frac{ f1.up * f2.up}{ f1.down * f2.down}

分数的除法:对于给定的两个分数 f1 和 f2,其除法公式为:
r e s = f 1. u p f 2. d o w n f 1. d o w n f 2. u p res = \frac{ f1.up * f2.down}{ f1.down * f2.up}

以上代码实现略。

5.3.3 分数的输出

5.4 素数

素数(质数):除了 1 和其本身外,不能被其他数整除的一类数。

特殊:1既不是素数,也不是合数。

5.4.1 素数的判断

最直接的方法:一个整数n要被判断为素数,首先判断n是否能被2,3,…,n-1中的一个整除。只有2,3,…,n-1都不能整除n,n才能判定为素数,否则n为合数。该方法时间复杂度为O(n),对于实际问题而言,是比较耗时的。

改进方法:如果 2 ~ n-1中存在n的约数,不妨设为k,即 n%k == 0, 那么由 k *(n/k) == n ,可知 n/k 也是n的一个约数。所以,我们只需要判定n是否能被 2,3,… n \lfloor \sqrt{n} \rfloor 中的一个整除,即可判定n是否为素数。该方法运算时间为O(sqrt(n)).
代码如下:

bool isPrime( int n){
    if( n <= 1) 
        return false;
    int sqr = (int)sqrt(1.0*n);
    for( int i = 2; i <= sqr; i++)//注意是小于等于
        if( n%i == 0)
            return false;
            
    return true;
}

筛选法:其中最容易理解的一种筛选法为 埃氏晒法(eratoshenes筛法),其时间复杂度为O(nloglogn).
算法从小到大枚举所有的数字,对于每一个素数,晒去它的所有倍数,剩下的就是素数了。
例如:求 1 ~ 15 中所有的素数。
2 3 4 5 6 7 8 9 10 11 12 13 14 15 2,3,4,5,6,7,8,9,10,11,12,13,14,15
首先 2 是素数,所以晒去2的倍数。
2 3 5 7 9 11 13 15 2,3,5,7,9,11,13,15
第二个数字是3,由于它没有被晒去,所以3是素数。
2 3 5 7 11 13 2,3,5,7,11,13
依次类推即可。

代码:

const int maxsize =101;
int prime[maxsize], pNum = 0;//素数表长至少比n大1.
bool p[maxsize] = {0};
void find_Prime(){
    for( int i = 2; i < maxsize; i++){//不能写成<=
        if( p[i] == flase;){
            prime[pNum++] = i;
            
            for( int j = i + i; j < maxSize; j+=i)
                p[j] = true;
        }
    }
}

5.5 质因子分解

质因子分解:指的是将一个正整数n写成一个或者多个质数的乘积的形式,例如 6 = 2 * 3, 8 = 2 * 2 * 2…注意,由于1本身不是素数,所以它没有质因子。
由于每一个质因子可能出现不止一次,所以可以定义结构体factor,用来存放质因子以及其个数。

struct factor{
    int x;
    int count;
}fac[ num];

5.6 大整数的运算

5.6.1 大整数的存储

A+B这样的运算永远是最简单的,但是如果A和B是1000位的数字呢?
计算机是无法存储这么大的数字的,因此需要进行特殊处理。
最简单的处理办法是使用 数组,可以让整数的每一位分别存储到对应数组的位置上。整数的高位存储在数组的高位,整数的低位存储在数组的低位。(有时候需要反转一下)

5.6.2 大整数的四则运算

高精度加法、高精度减法、高精度乘法、高精度除法。
以上代码实现较为简单,知道思想即可。

5.7 组合数

5.7.1 一个关于 n! 的问题

关于 n! 的问题:求 n! 中有多少个质因子p。
方法一: 最直观的想法是计算 1~n 中每个数的总共有多少质因子p,然后结果相加。该算法运算时间为O(nlogn).
显然,对于足够大的数字,以上放上是不够实用的。
方法二:公式:n! 中有 $ \frac{n}{p} + \frac{n}{p^2} + \frac{n}{p^3} +…$ 个质因子p,其中除法均为向下取整。该算法的运算时间为O(logn).
代码如下:

int cal( int n, int p){
    int ans = 0;
    while( n){
        ans += n/p;
        n /= p;
    }
    return ans;

}

扩展:计算n!的末尾有多少个零?
由于末尾0的个数等于n!中因子10的个数,而这又等于n!中质因子5的个数,因此只需要代入cal(n,5)即可。

5.7.2 组合数的计算

组合数$ \binom{n}{m} n m ( m &lt; = n ) . 指从n个不同元素中挑出m个元素的方案数( m &lt;= n). 也可以写成 C_n^m $, 其定义为 C n m = n ! m ! ( n m ) ! C_n^m = \frac{n!}{m!(n-m)!}
性质:1. 组合数满足$ C_n^m = C_n^{n-m} $. 2. C n 0 = C n n = 1 C_n^0 = C_n^n = 1 .

计算组合数的方法:

方法一: 通过定义直接计算。
C n m = n ! m ! ( n m ) ! C_n^m = \frac{n!}{m!(n-m)!} ,可先计算 n!, 再计算m!(n-m)!.
但是,由于阶乘数十分庞大,很容易发生溢出。因此不推荐这种方式。

方法二: 通过递推式:
由组合数的性质可知,$ C_n^m $代表从n个数字中选择m个数。可以分解为两部分,分别是 一,选最后一个数字,从前n-1个数字中选择m-1个数字;二,不选最后一个数字,从前n-1个数字中选择m个数字。
即 $ C_n^m = C_{n-1}^m + C_{n-1}^{m-1}$
注意,该方法写代码时不建议使用递归(因为会造成重复计算),可以使用二位矩阵代替。

方法三: 通过定义式的变形来计算
$C_n^m = \frac{n!}{m!(n-m)!} = \frac{ (n - m +1)(n- m + 2)…(n-m+m)}{123*…*m} $
可以观察到,上式的分子和分母的项数均为m项,可以按照以下方式计算: C n m = n ! m ! ( n m ) ! = ( n m + 1 ) ( n m + 2 ) . . . ( n m + m ) 1 2 3 . . . m = n m + 1 1 ( n m + 2 ) 2 . . . . . . ( n m + m ) m C_n^m = \frac{n!}{m!(n-m)!} \\= \frac{ (n - m +1)(n- m + 2)...(n-m+m)}{1*2*3*...*m} \\ = \frac{\frac{\frac{ \frac{n-m+1}{1} * (n-m+2)}{2}*...}{...}*(n-m+m)}{m}
上式唯一需要证明的每次除法都是整数。这个很简单,因为上式就是把$ C_{n-m+i}^{i} 展开写的结果,而 C_{n-m+i}^{i}$必然是个整数。
代码:

long long C( long long n, long long m){
    long long ans = 1;
    for( int i = 1; i <=m; i++)
        ans = ans * (n - m + i) /i; // 先乘再除
}

一般而言,组合数的求解会发生溢出。在很多问题中,一般让运算结果对一个正整数p进行取模。

我的微信公众号

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/sd4567855/article/details/88037921
今日推荐