数论算法

数论是一个非常庞大的数学分支,这篇博客我会把学的数论知识,和算法慢慢分享出来(不定期更新),各种代码的正确性我会在代码注释里给出证明

最大公约数:
最大公约数是数论很重要的组成部分(其实是数论模运算的重要应用),再看证明之前各位必须了解一下公约数及最大公约数的重要性质,下面我就简要的提下我证明所用到的性质。
符号说明:d | b表示d能整除b,即 b % d = 0
1.若d | a且 d | b,则d | (a * x + b * y) (x,y为任意整数)
2.gcd(0,0) = 0
3.对于gcd(a,b),a为非负整数,b为整数,则gcd(a,b) = a * x + b * y
(x,y为满足gcd(a,b)为最小正整数的实数对)
注:算法导论中 a * x = b(mod n)的解不是只有d个,我们可以设定正确x’的值可以得到更多,即在execute_gcd算法中设定递归出来的y’值.
下面我们来实现代码及代码正确性的证明:

#include <iostream>
#include <vector>
#include <stdio.h>

using namespace std;
/*
**  证明: gcd(a,b) = gcd(b,a % b)
证:要证gcd(a,b) = gcd(b,a % b)只需证明
        gcd(a,b) | gcd(b,a % b)且
        gcd(b,a % b) | gcd(a,b)
   设:d = gcd(a,b),则 d | a 且 d | b,而我们
   知道 a % b = a - b * q (q = 向下取整(a / b))
   因此,a % b是a和b的线性集合的元素,故 d | a % b
   ,即: d | b 且 d | (a % b)
   进一步可得:   gcd(a,b) | gcd(b,a % b),
   设 d2 = gcd(b,a % b),则 d2 | b,d2 | (a % b),
   ,而 a = (a % b) - b * q (q = 向下取整(a / b))
   则a是(a % b)和b线性集合里的一个元素,故
   d | a,综上,d | a 且 d | b,即 d | gcd(a,b)
   综上,gcd(a,b) = gcd(b,a % b);
*/
int gcd(const int& a,const int& b) {
    if (b == 0) {
        return a;
    }
    return gcd(b, a % b);
}
/*
** 众所周知: gcd(a,b) = a * x + b * y(x,y为使gcd(a,b)是最小正整数的实数对集)
   而程序中是利用了 gcd(a,b) = gcd(b,a % b)的特性,则运行过程为
            gcd(a,b) = a * x + b * y;
            gcd(b,b % a) = b * x1 + (b % a) * y1;
            .......
            gcd(ai,bi) = ai * 1 + 0 * yi;
  根据递归终止条件可知我们bi = 0,xi = 0,
  而除最后一条递归,之前的x,y可能多解,而最后一条是因为条件限制
  xi = 0,yi任意,我们可以通过设定yi的值,得到x,y的可能解,对于
                gcd(x,y) = a * x + b * y;
  我们可以通过调整x或y的值,得到满足条件的(x,y)实数对,然而最后一种情况,
  已经固定   xi = 1,yi任意,所以调整(x和y为含yi的表达式会不失一般性和其任意性)
  而 gcd(a,b) = a * x + b * y;
     gcd(b,a % b) = b * x' + (a % b) * y'
                  = b * x' + (a - b * [a / b]) * y'
  即: a * x + b * y = b * x' + (a - b *[a / b]) * y'
  调整: a * x + b * y = a * y' + (x' - [a / b] * y') * b;
  令 x = x',则 y = x' - [a / b] * y';([a / b]为其向下取整)
  通过随意指定yi,而xi = 1,我们很好的维护了程序的正确性
*/
pair<int, pair<int, int>> execute_gcd(const int& a,const int
&b) {
    if (b == 0) {
        return make_pair(a, make_pair(1, 79));
    }
    pair<int, pair<int, int>> get;
    pair<int, pair<int, int>> ret;
    get = execute_gcd(b, a % b);
    ret.first = get.first;
    ret.second.first = get.second.second;
    ret.second.second = get.second.first - static_cast<int>(a / b) * get.second.second;
    printf("a:  %d\tb:  %d\tx:  %d\ty:  %d\n", a, b, ret.second.first, ret.second.second);
    return ret;
}
/*
**  模线性方程: a * x = b(mod n)
    这里 a,b,n为任意输入值,我们求解x
    不知各位发现没有其实这个方程就像gcd(a,n)的逆运算
    b = gcd(a,n) = a * x + n * y;此时我们输入
    a,n来求解b,与此同时能得到(x,y)的结果集,而
    模线性方程则是让我们输入一个我们a,n和猜想的a,n最大公约数b,
    如果b = gcd(a,n)则就说明有解,否则无解,但是当n = 0,我们就不能把
    a * x = b(mod n)改写成 b = gcd(a,n) = a * x + n * y了
    此时我们设d = gcd(a,n),来观察d 和 b的关系而不失一般性,
    当 n = 0时,   a * x = b(mod 0),即 b = a * x (x为整数)
    此时  d = gcd(a,n) = a * x = a(这里的x必须为1,不懂的去看gcd的性质),
    观察d = a,b = a * x,如果x有解,则必有 d | b
    所以,综上我们容易知道:对于 a * x = b(mod n),若d = gcd(a,n),d | a,则
    方程有解,否则方程无解.
    数学证明总是容易的(给定了条件(起点)和结果(终点),而我们给出过程(连上起点和终点)总是很容易的,但是真正
    困难的是我们总要弄清楚终点的重要意义及终点的存在(否则变成了矢量,哈哈),如果我们不弄懂终点存在的意义(我们知道终点的存在及其到达的方向,然而我们其更为重大的意义是我们把它当作起点,继续寻找终点及方向),
    而只是在起点和终点寻找各种各样的路径(事实更可怕的是,人们往往认为(数学家或教科书或自己的固有思维)给出的路线是一条"直线"(两点之间直线最短)),让这条"直线"经久不衰的连起来,其实这种想法也没错,人生那么短,我们何必把这种
    让自己感到折磨的事情强压在自己只有一次的人生之上,所以这种事情就交给感兴趣的人去做(他们觉得这样做能让自己的人生有意义,我们要尊重他们),当然,我们选择了与数学有关的事情(当然我们生活也需要数学,如果有人说数学没用,对于我身边出现这种人,我就有危机感了,因为我的水平太低,竟然让身边充斥着
    这种人(当然这句话不是讽刺,而是尊重自己,人是群居动物为什么不能自己想办法选择一个好一点,适合自己的群去居呢),既然如此,我们总是要凭借别人(站在巨人的肩膀上)找到直线和理解终点的存在,一条条错综复杂的直线和曲线构造了数学的长河,所以如果我们不懂得终点存在的意义,那么以这个终点为起点时,您会觉得
    更加抽象吧,人生亦是如此,我觉得人活着就是奔着死亡去的,如果我们让人生过的毫无意义(自己认为的,不是别人骂你的,哈哈),那么我们死亡时是无奈的叹息或是依依不舍(年轻时的英雄ol真好玩,开玩笑,哈哈),不管怎样,我们都要正确对待死亡,只有死亡,才能开启我们的第二人生啊,兄弟!
    那么我们怎么证明d | b时 a * x = b(mod n),这里就是群的应用了,首先介绍下群的概念:
    符号定义:(S,@),(S,@)表示集合S元素运用二元操作符@
    对于(S,@)需要满足下列条件才能称为群:
        1.封闭性:对于任意a属于S,b属于S,而a @ b属于S,则满足封闭性
        2.可交换性:对于任意a属于S,b属于S,c属于S,(a @ b) @ c = a @ (b @ c),则满足
        可交换性
        3.有单位元:对于任意元素a属于S,存在元素e属于S,使得 a @ e = e @ a = a,则表示(S,@)含单位元
        4.有逆元:对于任意元素a属于S,存在元素a'使得 a @ a' = a' @ a = e(单位元)
    满足以上条件的集合(S,@)称为群
    子群:对于群(S,@),其子集S'满足封闭性,则称S'为S的子群
    有种特殊的方法可以很方便产生子群:我们可以选取群(S,@)中的一个元素a
    对a进行k(k >= 1)次 @运算所产生的集合S' = {(a^k): k > 0)}是S的子群
    我们也很容易证明此处就略去了,下面我们来看看它的几个小性质:
        1.(a^k):k >= 1序列具有周期性,周期 T = | S'|
        ....
    对于模加法操作是一个群(S,+),选取其中一个元素a,生成子群
    模加法群是一个可结合群(阿贝尔群),故S' = {a * x mod n: x >= 1},
    而a * x = b(mod n),则表示若x有解,则b必须属于S',那么
    我们证明 d | b,b就属于S',不就证明当 d = gcd(a,n),d | b,
    a * x = b(mod n)有解
    证明:我们知道存在x'使得 d = (a * x')mod n,故d属于S',
    我们接着证明存在x''使得 k * d = (a * x'')mod n,即
    k * n + a * x'' = d,即d是a和n的一个公约数,然而d是a和n的最大公约数,显然满足条件,
    即当b = k * d即d | b有解,a * x mod n值在[0,n-1]之间通过上式我们还知道
    S'a = S'd = {0,d,2 * d,.....(n / d - 1) * d}
    即 |S| = n / d;
    而当b属于a * i mod n(i = 0,1,2,3....n - 1)说明,
    若 b = a * j mod n,根据周期性可知 b = a * (j + n / d)mod n
    ,以此类推我们可知x的解为 x = x0 + (n / d) * i (i = 0,1,2.....d)
    所以我们找到x0便找到了所有解.
    即    a * x0 = b(mod n)
          d = gcd(a,n) = a * x' + n * y'(x',y'为整数)
          d = a * x'(mod n)
    则:    (a * x0) mod n = b
          (a * x') mod n = d
          x0 = x' * (b / d) mod n
          因此
          x = x0 + (n / d) * i (i= 0,1,2,3..d)
            = x' * ( b / d) mod n + (n / d) * i
*/
//  a * x  = b(mod n)
vector<int> solve_line_mod(const int& a, const int& b,const int& n) {
    int x0;
    vector<int> vec(0);
    pair<int, pair<int, int>> p = execute_gcd(a,n);
    if ((static_cast<double>(b) / p.first) == (static_cast<double>(b / p.first))) {
        x0 = (p.second.first * (b / p.first)) % n;
        vec.push_back(x0);
    }else {
        return vec;
    }
    for (int i = 1; i < p.first; ++i) {
        int x = x0 + (n / p.first) * i;
        vec.push_back(x);
    }
    return vec;
}
int main() {
    //execute_gcd(99, 78);
    int a = 4;
    int b = 3;
    int n = 12;
    vector<int> vec;
    vec = solve_line_mod(a, b, 12);
    if (vec.size() == 0) {
        printf("无解\n");
    }else {
        for (auto v : vec) {
            cout << v << endl;
        }
    }
    system("pause");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/rgbmarco/article/details/80465956