你好,我是黃申。今天我們來聊聊「餘數」。提起來餘數,我想你肯定不陌生,因為我們生活中就有很多很多與餘數相關的例子。比如說,今天是星期三,你想知道 50 天之後是星期幾,那你可以這樣算,拿 50 除以 7(因為乙個星期有 7 天),然後餘 1,最後在今天的基礎上加一天,這樣你就能知道 50 天之後是星期四了。再比如,我們做 web 程式設計的時候,經常要用到分頁的概念。如果你要展示 1123 條資料,每頁 10 條,那該怎麼計算總共的頁數呢?我想你肯定是拿 1123 除以 10,最後得到商是 112,餘數是 3,所以你的總頁數就是 112+1=113,而最後的餘數就是多出來,湊不夠一頁的資料。看完這幾個例子,不知道你有沒有發現,餘數總是在乙個固定的範圍內。比如你拿任何乙個整數除以 7,那得到的餘數肯定是在 0~6 之間的某乙個數。所以當我們知道 1900 年的 1 月 1 日是星期一,那便可以知道這一天之後的第 1 萬天、10 萬天是星期幾,是不是很神奇?你知道,整數是沒有邊界的,它可能是正無窮,也可能是負無窮。但是餘數卻可以通過某一種關係,讓整數處於乙個確定的邊界內。我想這也是人類發明星期或者禮拜的初衷吧,任你時光變遷,我都是以 7 天為乙個週期,「周」而復始地過著確定的生活。因為從星期的角度看,不管你是哪一天,都會落到星期一到星期日的某一天裡。我們再拿上面星期的例子來看。假如今天是星期一,從今天開始的 100 天裡,都有多少個星期呢?你拿 100 除以 7,得到商 14 餘 2,也就是說這 100 天裡有 14 週多 2 天。換個角度看,我們可以說,這 100 天裡,你的第 1 天、第 8 天、第 15 天等等,在餘數的世界裡都被認為是同一天,因為它們的餘數都是 1,都是星期一,你要上班的日子。同理,第 2 天、第 9 天、第 16 天餘數都是 2,它們都是星期二。
這些數的餘數都是一樣的,所以被歸類到了一起,有意思吧?是的,我們的前人早已注意到了這一規律或者特點,所以他們把這一結論稱為同餘定理。簡單來說,就是兩個整數 a 和 b,如果它們除以正整數 m 得到的餘數相等,我們就可以說 a 和 b 對於模 m 同餘。也就是說,上面我們說的 100 天裡,所有星期一的這些天都是同餘的,所有星期二的這些天就是同餘的,同理,星期
三、星期四等等這些天也都是同餘的。還有,我們經常提到的奇數和偶數,其實也是同餘定理的乙個應用。當然,這個應用裡,它的模就是 2 了,2 除以 2 餘 0,所以它是偶數;3 除以 2 餘 1,所以它是奇數。2 和 4 除以 2 的餘數都是 0,所以它們都是一類,都是偶數。3 和 5 除以 2 的餘數都是 1,所以它們都是一類,都是奇數。你肯定會說,同餘定理就這麼簡單嗎,這個定理到底有什麼實際的用途啊?其實,我上面已經告訴你答案了,你不妨先自己思考下,同餘定理的意義到底是什麼。簡單來說,同餘定理其實就是用來分類的。你知道,我們有無窮多個整數,那怎麼能夠全面、多維度地管理這些整數?同餘定理就提供了乙個思路。
因為不管你的模是幾,最終得到的餘數肯定都在乙個範圍內。比如我們上面除以 7,就得到了星期幾;我們除以 2,就得到了奇偶數。所以按照這種方式, 我們就可以把無窮多個整數分成有限多個類。這一點,在我們的計算機中,可是有大用途。雜湊(hash)你應該不陌生,在每個程式語言中,都會有對應的雜湊函式。雜湊有的時候也會被翻譯為雜湊,簡單來說,它就是將任意長度的輸入,通過雜湊演算法,壓縮為某一固定長度的輸出。這話聽著是不是有點耳熟?我們上面的求餘過程不就是在做這事兒嗎?
舉個例子,假如你想要快速讀寫 100 萬條資料記錄,要達到高速地訪問,最理想的情況當然是開闢乙個連續的空間存放這些資料,這樣就可以減少定址的時間。但是由於條件的限制,我們並沒有能夠容納 100 萬條記錄的連續位址空間,這個時候該怎麼辦呢?我們可以考察一下,看看系統是否可以提供若干個較小的連續空間,而每個空間又能存放一定數量的記錄。比如我們找到了 100 個較小的連續空間,也就是說,這些空間彼此之間是被分隔開來的,但是內部是連續的,並足以容納 1 萬條記錄連續存放,那麼我們就可以使用餘數和同餘定理來設計乙個雜湊函式,並實現雜湊表的結構。
那這個函式應該怎麼設計呢?你可以先停下來思考思考,提醒你下,你可以再想想星期幾的那個例子,因為這裡面用的就是餘數的思想。
下面是我想到的一種方法:
在這個公式中,x 表示等待被轉換的數值,而 size 表示有限儲存空間的大小,mod 表示取餘操作。通過餘數,你就能將任何數值,轉換為有限範圍內的乙個數值,然後根據這個新的數值,來確定將資料存放在何處。
具體來說,我們可以通過記錄標號模 100 的餘數,指定某條記錄存放在哪個空間。這個時候,我們的公式就變成了這樣:
假設有兩條記錄,它們的記錄標號分別是 1 和 101。我們把這些模 100 之後餘數都是 1 的,存放到第 1 個可用空間裡。以此類推,將餘數為 2 的 2、102、202 等,存放到第 2 個可用空間,將 100、200、300 等存放到第 100 個可用空間裡。這樣,我們就可以根據求餘的快速數字變化,對資料進行分組,並把它們存放到不同的位址空間裡。而求餘操作本身非常簡單,因此幾乎不會增加定址時間。
除此之外,為了增加資料雜湊的隨機程度,我們還可以在公式中加入乙個較大的隨機數 max,於是,上面的公式就可以寫成這樣:
我們假設隨機數 max 是 590199,那麼我們針對標號為 1 的記錄進行重新計算,最後的計算結果就是 0,而針對標號 101 的記錄,如果隨機數 max 取 627901,對應的結果應該是 2。這樣先前被分配到空間 1 的兩條記錄,在新的計算公式作用下,就會被分配到不同的可用空間中。
你可以嘗試記錄 2 和 102,或者記錄 100 和 200,最後應該也是同樣的情況。你會發現,使用了 max 這個隨機數之後,被分配到同乙個空間中的記錄就更加「隨機」,更適合需要將資料重新洗牌的應用場景,比如加密演算法、mapreduce 中的資料分發、記錄的高速查詢和定位等等。
讓我以加密演算法為例,在這裡面引入 max 隨機數就可以增強加密演算法的保密程度,是不是很厲害?舉個例子,比如說我們要加密一組三位數,那我們設定乙個這樣的加密規則:
先對每個三位數的個、十和百位數,都加上乙個較大的隨機數。
然後將每位上的數都除以 7,用所得的餘數代替原有的個、十、百位數;
最後將第一位和第三位交換。
這就是乙個基本的加密變換過程。
假如說,我們要加密數字 625,根據剛才的規則,我們來試試。假設隨機數我選擇 590127。那百、十和個位分別加上這個隨機數,就變成了 590133,590129,590132。然後,三位分別除以 7 求餘後得到 5,1,4。最終,我們可以得到加密後的數字就是 415。因為加密的人知道加密的規則、求餘所用的除數 7、除法的商、以及所引入的隨機數 590127,所以當拿到 415 的時候,加密者就可以算出原始的資料是 625。是不是很有意思?
小結
到這裡,餘數的所有知識點我們都講完了。我想在此之前,你肯定是知道餘數,也明白怎麼求餘。但對於餘數的應用不知道你之前是否有思考過呢?我們經常說,數學是計算機的基礎,在餘數這個小知識點裡,我們就能找到很多的應用場景,比如我前面介紹的雜湊函式、加密演算法,當然,也還有我們沒有介紹到的,比如迴圈冗餘校驗等等。餘數只是數學知識中的滄海一粟。你在中學或者大學的時候,肯定接觸過很多的數學知識和定理,程式設計的時候也會經常和數字、公式以及資料打交道,但是真正學懂數學的人卻沒幾個。希望我們可以從餘數這個小概念開始,讓你認識到數學思想其實非常實用,用好這些知識,對你的程式設計,甚至生活都有意想不到的作用。
程式設計師的數學
封面 內容簡介 如果數學不好,是否可以成為一名程式設計師呢?答案是肯定的。本書最適合 數學糟糕但又想學習程式設計的你。沒有晦澀的公式,只有好玩的數學題。幫你掌握程式設計所需的 數學思維 日文版已重印14次!程式設計的基礎是電腦科學,而電腦科學的基礎是數學。因此,學習數學有助於鞏固程式設計的基礎,寫出...
程式設計師的數學
0 明確表現可 無即是有 換言之,就是不對 無 進行特別處理。引入 0 以後,更容易簡化規則。如果找出具有一致性的簡單的規則,則便於機械式處理,讓計算機來解決問題。邏輯基本上被分為 true 和 false 兩個世界。解決問題時,並不是眉毛鬍子一起抓,而應該根據某條件分為 條件成立 和 條件不成立 ...
《程式設計師的成長課》
已經2019年了,新年新氣象。在新的一年裡,我想做件有意義的事兒。具體是什麼事呢?這裡先留個懸念。我是乙個愛思考的人,我也工作好幾年了,這段時間內心突然有個想法 程式設計師的個人成長有規律可循嗎?有人不會表達自己。我幫很多人review過簡歷,但是很多人的簡歷寫的很糟糕,在他們的簡歷上看不到亮點和細...