舉例
11 Mod 2,值為1
上述模運算多用於程式編寫,舉一例來說明模運算的原理:
Turbo Pascal對mod的解釋是這樣的:
A Mod B=A-(A
divB) * B (div含義為整除)
概念及性質
本文以c++語言為載體,對基本的模運算套用進行了分析和程式設計,以理論和實際相結合的方法向大家介紹模運算的基本套用。
基本概念
給定一個
正整數 ,任意一個整數
,一定存在等式
;
其中
、
是整數,且
,稱
為
除以
的商,
為
除以
的
餘數。
取模運算:a % p(或a mod p),表示a除以p的
餘數。
模p
加法:(a + b) % p ,其結果是a+b算術和除以p的餘數,也就是說,(a+b) = kp +r,則(a + b) % p = r。
模p減法:(a-b) % p ,其結果是a-b算術差除以p的餘數。
模p
乘法:(a * b) % p,其結果是 a * b算術乘法除以p的餘數。
說明:
1.
同餘式:正整數a,b對p取模,它們的餘數相同,記做 a ≡ b % p或者a ≡ b (mod p)。
2. n % p得到結果的正負由
被除數n決定,與p無關。例如:7%4 = 3, -7%4 = -3, 7%-4 = 3, -7%-4 = -3(在java、C/C++中%是取余,在python是模運算,此處%按取余處理)。
基本性質
(1)若p|(a-b),則a≡b (% p)。例如 11 ≡ 4 (% 7), 18 ≡ 4(% 7)
(2)(a % p)=(b % p)意味a≡b (% p)
(3)
對稱性:a≡b (% p)等價於b≡a (% p)
(4)傳遞性:若a≡b (% p)且b≡c (% p) ,則a≡c (% p)
運算規則
模運算與基本四則運算有些相似,但是除法例外。其規則如下:
(a + b) % p = (a % p + b % p) % p (1)
(a - b) % p = (a % p - b % p + p) % p (2)
(a * b) % p = (a % p * b % p) % p (3)
(a^b) % p = ((a % p)^b) % p (4)
結合律:
((a+b) % p + c) % p = (a + (b+c) % p) % p (5)
((a*b) % p * c)% p = (a * b*c) % p (6)// (a%p*b)%p=(a*b)%p
交換律:
(a + b) % p = (b+a) % p (7)
(a * b) % p = (b * a) % p (8)
分配律:
((a +b)% p * c) % p = ((a * c) % p + (b * c) % p) % p (9)
重要定理:
若a≡b (% p),則對於任意的c,都有(a + c) ≡ (b + c) (%p);(10)
若a≡b (% p),則對於任意的c,都有(a * c) ≡ (b * c) (%p);(11)
若a≡b (% p),c≡d (% p),則 (a + c) ≡ (b + d) (%p),(a - c) ≡ (b - d) (%p),
(a * c) ≡ (b * d) (%p); (12)
基本套用
判別奇偶數
奇
偶數的判別是模運算最基本的套用,也非常簡單。已知一個整數n對2取模,如果
餘數為0,則表示n為偶數,否則n為奇數。
C++實現功能函式:
/*函式名:IsEven函式功能:判別整數n的奇偶性。能被2整除為偶數,否則為奇數輸入值:int n,整數n返回值:bool,若整數n是偶數,返回true,否則返回false*/bool IsEven(int n){ return !(n%2);}
判別素數
一個數,如果只有1和它本身兩個因數,這樣的數叫做
質數(或素數)。例如 2,3,5,7 是質數,而 4,6,8,9 則不是,後者稱為合成數或
合數。
判斷某個自然數是否是素數最常用的方法就是
試除法:用比該自然數的平方根小的
正整數去除這個自然數,若該自然數能被整除,則說明其非素數。
C++實現功能函式:
/*函式名:IsPrime
函式功能:判別自然數n是否為素數。
輸入值:int n,自然數n
返回值:bool,若自然數n是素數,返回true,否則返回false*/
#include<math.h>
bool IsPrime(unsigned n)
{
unsigned maxFactor = sqrt(n); //n的最大因子
for (unsigned i = 2 ; i <= maxFactor ; i++)
{
if (!(n % i)) //n能被i整除,則說明n非素數
return false;
}
return true;
}
最大公約數
證明:a可以表示成a = kb + r,則r = a mod b
假設d是a,b的一個
公約數,則有d|a, d|b,而r = a - kb,因此d|r
因此d是(b,a mod b)的公約數
假設d 是(b,a mod b)的公約數,則d | b , d |r ,但是a = kb +r
因此d也是(a,b)的公約數
因此(a,b)和(b,a mod b)的
公約數是一樣的,其
最大公約數也必然相等,得證。
C++實現功能函式:
/*函式功能:利用歐幾里德算法,採用遞歸方式,求兩個自然數的最大公約數
函式名:Gcd
輸入值:
unsigned int a,自然數a
unsigned int b,自然數b
返回值:unsigned int,兩個自然數的最大公約數*/
unsigned Gcd(unsigned a , unsigned b)
{
if (b)
return Gcd(b , a % b);
return a;
}
/*函式功能:利用歐幾里德算法,採用疊代方式,求兩個自然數的最大公約數
函式名:Gcd
輸入值:
unsigned int a,自然數a
unsigned int b,自然數b
返回值:
unsigned int,兩個自然數的最大公約數*/
unsigned Gcd(unsigned a , unsigned b)
{
unsigned temp;
while (b)
{
temp = a % b;
a = b;
b = temp;
}
return a;
}
模冪運算
利用模運算的運算規則,我們可以使某些計算得到簡化。例如,我們想知道3333^5555的末位是什麼。很明顯不可能直接把3333^5555的結果計算出來,那樣太大了。但我們想要確定的是3333^5555(%10),所以問題就簡化了。
根據運算規則(4)a^b% p = ((a % p)^b) % p ,我們知道3333^5555(%10)= 3^5555(%10)。由於3^4 = 81,所以3^4(%10)= 1。
根據運算規則(3) (a * b) % p = (a % p * b % p) % p ,由於5555 = 4 * 1388 + 3,我們得到3^5555(%10)=(3^(4*1388) * 3^3)(%10)=((3^(4*1388)(%10)* 3^3(%10))(%10)
=(1 * 7)(%10)= 7。
計算完畢。
利用這些規則我們可以有效地計算X^N(% P)。簡單的算法是將result初始化為1,然後重複將result乘以X,每次
乘法之後套用%運算符(這樣使得result的值變小,以免溢出),執行N次相乘後,result就是我們要找的答案。
這樣對於較小的N值來說,實現是合理的,但是當N的值很大時,需要計算很長時間,是不切實際的。下面的結論可以得到一種更好的算法。
如果N是
偶數,那么X^N =(X*X)^[N/2];
如果N是奇數,那么X^N = X*X^(N-1) = X *(X*X)^[N/2];
其中[N]是指小於或等於N的最大整數。
C++實現功能函式:
/*函式功能:利用模運算規則,採用遞歸方式,計算X^N(% P)
函式名:PowerMod
輸入值:
unsigned int x,底數x
unsigned int n,指數n
unsigned int p,模p
返回值:unsigned int,X^N(% P)的結果*/
unsigned PowerMod(unsigned x , unsigned n , unsigned p)
{
if (!n)
return 1;
unsigned temp = PowerMod((x * x) % p , n >> 1 , p); //遞歸計算(X*X)^[N/2]
if (n & 1) //判斷n的奇偶性
temp = (temp * x) % p;
return temp;
}
孫子問題(中國剩餘定理)
“今有物不知其數,三三數之剩二,五五數之剩三,七七數之剩二,問物幾何?”意思是,“一個數除以3餘2,除以5餘3,除以7餘2.求適合這個條件的最小數。”
這個問題稱為“孫子問題”。關於孫子問題的一般解法,國際上稱為“中國剩餘定理”。
我國古代學者早就研究過這個問題。例如我國明朝數學家
程大位在他著的《算法統宗》(1593年)中就用四句很通俗的口訣暗示了此題的解法:
三人同行七十稀,五樹梅花廿一支,七子團圓正半月,除百零五便得知。
"正半月"暗指15。"除百零五"的原意是,當所得的數比105大時,就105、105地往下減,使之小於105;這相當於用105去除,求出
餘數。
這四句口訣暗示的意思是:當
除數分別是3、5、7時,用70乘以用3除的餘數,用21乘以用5除的餘數,用15乘以用7除的餘數,然後把這三個乘積相加。加得的結果如果比105大,就除以105,所得的餘數就是滿足題目要求的最小正整數解。
根據
剩餘定理,可以把此種解法推廣到有n(n為自然數)個除數對應n個餘數,求最小被除數的情況。輸入n個除數(除數不能互相整除)和對應的餘數,計算機將輸出最小被除數。
C++實現功能函式:
/*函式名:ResidueTheorem
函式功能:運用剩餘定理,解決推廣了的孫子問題。通過給定n個除數(除數不能互相整除)和對應的餘數,返回最小被除數
輸入值:
unsigned int devisor[],存儲了n個除數的數組
unsigned int remainder[],存儲了n個餘數的數組
int length,數組的長度
返回值:
unsigned int, 最小被除數*/
unsigned ResidueTheorem(const unsigned devisor[] , const unsigned remainder[] , int length)
{
unsigned product = 1; //所有除數之乘積
for (int i=0 ; i<length ; i++)//計算所有除數之乘積
product *= devisor[i];//公倍數數組,表示除該元素(除數)之外其他除數的公倍數
unsigned int *commonMultiple = new unsigned int(length);
for (int i=0 ; i<length ; i++)//計算除該元素(除數)之外其他除數的公倍數
commonMultiple[i] = product / devisor[i];
unsigned dividend = 0; //被除數,就是函式要返回的值
for (int i=0 ; i<length ; i++)//計算被除數,但此時得到的不是最小被除數
{
unsigned tempMul = commonMultiple[i];//按照剩餘理論計算合適的公倍數,使得tempMul % devisor[i] == 1
while (tempMul % devisor[i] != 1)
tempMul += commonMultiple[i];
dividend += tempMul * remainder[i]; //用本除數得到的餘數乘以其他除數的公倍數
}
delete []commonMultiple;
return (dividend % product); //返回最小被除數}凱撒密碼
}
凱撒密碼(caeser)是羅馬擴張時期朱利斯o凱撒(Julius Caesar)創造的,用於加密通過信使傳遞的作戰命令。
它將字母表中的字母移動一定位置而實現加密。注意26個字母循環使用,z的後面可以看成是a。
例如,當密匙為k = 3,即向後移動3位時,若明文為”How are you!”,則密文為”Krz duh btx!”。
在這裡,可以做一約定:明文記為m,密文記為c,加密變換記為E(key1,m)(其中key1為
密鑰),
解密變換記為D(key2,m)(key2為解密密鑰)(在這裡key1=key2,不妨記為key)。
凱撒密碼的加密過程可記為如下一個變換:c≡m+key (mod n) (其中n為基本字元個數)
同樣,解密過程可表示為:m≡c+key (mod n) (其中n為基本字元個數)
C++實現功能函式:
/*函式功能:使用凱撒密碼原理,對明文進行加密,返回密文
函式名:Encrypt
輸入值:
const char proclaimedInWriting[],存儲了明文的字元串
char cryptograph[],用來存儲密文的字元串
int keyey,加密密匙,正數表示後移,負數表示前移
返回值:無返回值,但是要將新的密文字元串返回*/
#include<string.h>
void Encrypt(const char proclaimedInWriting[] , char cryptograph[] , int key)
{
const int NUM = 26; //字母個數
int len = strlen(proclaimedInWriting);
for (int i=0 ; i<len ; i++)
{
if (proclaimedInWriting[i] >= 'a' && proclaimedInWriting[i] <= 'z')//明碼是大寫字母,則密碼也為大寫字母
cryptograph[i] = (proclaimedInWriting[i] - 'a' + key) % NUM + 'a';
else if (proclaimedInWriting[i] >= 'A' && proclaimedInWriting[i] <= 'Z')//明碼是小寫字母,則密碼也為小寫字母
cryptograph[i] = (proclaimedInWriting[i] - 'A' + key) % NUM + 'A';
else//明碼不是字母,則密碼與明碼相同
cryptograph[i] = proclaimedInWriting[i];
}
cryptograph[len] = '\0';
}
/*函式功能:使用凱撒密碼原理,對密文進行解密,返回明文
函式名:Decode
輸入值:
char proclaimedInWriting[],用來存儲明文的字元串
const char cryptograph[],存儲了密文的字元串
int keyey,解密密匙,正數表示前移,負數表示後移(與加密相反)
返回值:無返回值,但是要將新的明文字元串返回*/
#include<string.h>
void Decode(const char cryptograph[] , char proclaimedInWriting[] , int key)
{
const int NUM = 26; //字母個數
int len = strlen(cryptograph);
for (int i=0 ; i<len ; i++)
{
if (cryptograph[i] >= 'a' && cryptograph[i] <= 'z')//密碼是大寫字母,則明碼也為大寫字母,為防止出現負數,轉換時要加個NUM
proclaimedInWriting[i] = (cryptograph[i] - 'a' - key + NUM) % NUM + 'a';
else if (cryptograph[i] >= 'A' && cryptograph[i] <= 'Z')//密碼是小寫字母,則明碼也為小寫字母
proclaimedInWriting[i] = (cryptograph[i] - 'A' - key + NUM) % NUM + 'A';
else//密碼不是字母,則明碼與密碼相同
proclaimedInWriting[i] = cryptograph[i];
}
proclaimedInWriting[len] = '\0';
}
總結
模運算及其簡單套用差不多就這么多了,其實模運算在數學及計算機領域的套用非常廣泛,這些只是一些最最基本的情形,希望能夠起到一個拋磚引玉的作用,讓更多的人關注模運算,並及其套用到更廣闊的領域中。