程序員的數學基礎課

計算機的源頭

  • 日常生活中,我們廣泛采用十進制計數法。



    10是十進制計數法的基數,這也是十進制中“十”的由來。

  • 十進制是使用10作為基數,那么二進制就是使用2作為基數。110101作為二進制:



    按照這個思路,我們還可以推導出八進制,十六進制等等計數法。

  • 計算機為什么使用二進制
    計算機使用二進制和現代計算機的硬件實現有關。組成計算機系統的邏輯電路通常只要有兩個狀態,開關的接通與斷開。
    斷開的狀態我們使用“0”來表示,接通的狀態用“1”來表示。由于每位數據只有斷開和接通兩種狀態,所以即使系統收到了一定程度的干擾時,仍然能夠可靠地分辨出數字時“0”還是“1”。因此,在具體的系統實現中,二進制的數據表達具有抗干擾強,可靠性高的優點。相比之下,如果用十進制設計具有10種電路,情況就會非常復雜,判斷狀態額時候出錯的幾率就會大大提高。另外,二進制也非常適合邏輯運算,邏輯運算中“真”和“假”,正好與二進制的“0”和“1”兩個數字相對應。邏輯運算中的加法、乘法都可以通過 “0”和“1”的加法、乘法和減法來實現。
  • 二進制的位操作
    • 向左移動
      二進制110101當左移動一位,就是在末尾添加一位0,因此110101就變成了1101010,請注意,這里討論的是數字沒有溢出的情況。所謂數字溢出就是二進制的位數超過了系統所指定的位數,目前主流的系統都支持至少32位的整型數字,而1101010遠未超過32位,所以不會溢出。如果進行左移動操作的二進制已經超過了32位,左移后數字就會溢出,需要將溢出的位數去除。

      在這個例子中,如果將1101010換算為十進制,就是106,剛好是53的2倍。所以,我們可以得出一個結論:二進制向左移動一位,其實就是將數字翻倍
    • 向右移動
      二進制向右移動一位就是去除末尾的那一位,因此110101就變成了11010.我們將11010換算為十進制,就是26,正好是53除以2的整數商。所以,二進制右移一位,就是將數字除以2并處以2求整數商的操作

      這段代碼的運行結果是:數字 53 向左移 1 位是 106;數字 53 向右移 1 位是 26。數字 53 向左移 3位是 424,數字 53 向右移 3 位是 6。
      其中,移位一次相當于乘以或者除以2,而移位3次就相當于乘以或者除以8(2的3次方)。
  • 位的“與”“或”“異或”運算


取余操作和哈希函數的關系

  • 提起余數我們都不陌生,我們生活中有太多和余數相關的例子
    比如說,今天星期三,你想知道50天后星期幾,可以拿50除以7, 余1,最后在今天的基礎上加一天,這樣你就知道50天之后是星期四了。
    再比如我們做Web開發,經常要用到分頁的操作。假如要展示1123條數據,每頁展示10條,那該怎么計算總共的頁數呢,1123除以10,最后得到商是112,余數是3,所以總頁數是112+1=113,最后的余數就是多出來,湊不夠一頁的數據。
    那任意一個整數除以7,余數肯定在0~6之間。
    假如今天是星期一,從今天開始的100天里,都有多少個星期呢?第1天,第8、15天除以7余數都是1,都是星期一,同理,第2、9、16天都是星期二。
    這些數的余數都是一樣的,所以都被歸類到了一起,我們的前人很早就注意到了這一點,所以他們把這一結論稱為同余定理。簡單來說就是兩個整數a和b,如果他們除以正整數m得到的余數相等,我們就可以說a和b對于模m同余。
  • 我們經常提到的奇數和偶數其實也是同余定理的一個應用,里面的模是2.
  • 簡單來說,同余定理其實就是用來分類的
  • 在每個編程語言中,都會有對應的哈希函數,哈希有的時候也會被翻譯成散列,簡單來說它就是將任意長度的輸入,通過哈希算法,壓縮為某一固定長度的輸出求余的過程就是在做這事。
  • 舉個例子,假如你想要快速讀寫100萬條數據,要達到快速的存取最理想的情況當然是開辟一個連續的空間存放這些數據,這樣就可以減少尋址的時間,但是由于條件的限制,并不能開辟能夠容納100萬條數據記錄的連續地址空間。
    但是我們可以開辟若干個較小的連續空間,而每個空間又能存放一定的數據記錄,比如找到100個較小的連續空間,也就是說,這些空間彼此之間是被分割開來的,但是內部是連續的,并足以容納1萬記錄連續存放,那么我們就可以使用余數和同余定理來設計一個散列函數,并實現哈希表的結構。

    在這個公式中,x表示等待被轉換的數值,而size表示有限存儲空間的大小,mod表示取余操作,通過余數,能將任何數值,轉換為有限范圍內的一個數值,然后根據這個新的數值,來確定將數值存放到何處
    根據記錄標號模100的余數,制定某條數據存放在哪個空間。這個時候公式就變成了:

    假設有兩條記錄,他們的記錄標號分別是1和101.把這些模100之后余數都是1的,放到第一個可用空間里面。以此類推,將余數為2的2、102、202等存放到第二個可用空間,將100、200、300存放到第100個可用空間里。
    這樣就可以根據求余的快速數字變化,對數組進行分組,并把它們存放到不同的地址空間里,求余操作本身非常簡單,因此幾乎不會增加尋址時間。

    除此之外位了增加數據散列的隨機程度,我們可以在公式中愛如一個較大的隨機數MAX,公式就變成了:
    image.png

    假設隨機數MAX是590199,那么針對標號為1的記錄進行重新計算,最后計算的結果就是0,針對標號為101的記錄,如果隨機數MAX取627901,那么對應結果應該是2.這樣先前分配到空間1的兩條記錄在新的公式作用下就會被分配到兩個不同的空用空間里。
    使用MAX這個隨機數后被分配到同一個空間中的記錄就更加“隨機”了,適合需要將數據重新洗牌的應用場景,比如加密算法等。
    例:
    加密規則:
    • 1.先對每個三位數的個、十、百位數都加上一個較大的隨機數。
    • 2.將每位上的數除以7,用得到的余數代替原先有的個、十、百位數。
    • 3.最后將第一位和第三位交換。
      這就是一個基本加密變換過程。
      假如說,要加密數字625,根據剛才的規則,隨機數選擇590127,那百、十、個位分別加上這個隨機數就變成了590133、590129、590132.然后三位分別處以7求余得到5,1,4.最終,我們可以得到加密后的數字就是415.因為加密的人知道加密的規則,求余所用的除以7,除法的商,以及所引用的隨機數590127,所以當拿到415的時候,機密者就可以計算出原始的數據是625.


      image.png

用二分法計算計算平方根

數學歸納法

遞歸上

image.png

遞歸下

分而治之 歸并排序

  • 歸并排序通過分治的思想,把長度為n的數列。每次簡化為n/2的數列,這更有利于計算機的并行處理,只需要log2n次歸并。


  • 把歸并和分治的思想結合起來,這其實就是歸并排序算法。
    這種算法每次把數列進行二等分,直到唯一的數字,也就是最基本的有序數列。然后從這些最基本的有序數列開始,兩兩合并有序的數列直到所有的數字都參與了歸并排序。


  • 歸并排序使用了分治的思想,而這個過程需要使用歸并來實現。
    歸并排序的算法用分治的思想把數列不斷的簡化,直到每個數列僅剩下一個單獨的數,然后再使用歸并逐步合并有序的數列,從而達到將整個薯類進行排序的目的。而這個歸并排序正好可以使用歸并的方式來實現。


  • 分治的過程可以通過歸并來表達,因此,歸并排序最直觀的方式就是遞歸,所以從遞歸的步驟出發看下歸并排序如何實現。
    假設 n = k = 1的時候,已經對較小的兩組數進行了排序。只要在n= k的時候將這兩組數合并起來,并且保證合并后的數組仍然是有序的就行了。
    所以在遞歸的每次嵌套調用中,代碼都將一組數分解成更小的兩組,然后將這兩個小組的排序交給下一次的嵌套調用。而本次調用只需要關心如何將排序好的兩個小組進行排序。
    在初始狀態,也即是 n = 1 的時候,對于排序的案例而言,只包含了單個數字的分組,由于分組里只有一個數組,所以已經是排好序了,之后就可以開始遞歸調用的返回階段。



1. 我們為什么需要反碼和補碼?

1.什么是符號位?為什么要有符號位

符號位是有符號二進制數中的最高位,需要用它來表示正負數。
把二進制數分為符號位(signed)和無符號位(unsigned)。
如果是二進制無符號數,最高位不是符號位。二進制符號數的最高位則表示正負。

2.什么是溢出

  • 在數學的理論中,數字可以有無窮大,也有無窮小。可是,現實中的計算機系統,總有一個物理上的極限(比如說晶體管的大小和數量)因此不可能表示無窮大或者無窮小的數字。對計算機而言,無論是何種數據類型,都有一個上限和下限。
    一旦某個數字超過了這些限定,就會發生溢出。如果超出上限,就叫上溢出(overflow)。如果超出了下限,就叫下溢出underflow)。
  • 溢出之后會發生什么呢?n 位數字的最大的正值,其符號位為 0,剩下的 n-1 位都為 0。而符號位是 1,后面 n-1 位全是 0,這表示 -2^(n-1)。



    那么就是說,上溢出之后,又從下限開始,最大的數值加 1,就變成了最小的數值,周而復始,這不就是余數和取模的概念嗎?


二進制的原碼、反碼及補碼

  • 原碼
    原碼就是我們看到的二進制的原始表示。對于有符號的二進制來說,原碼的最高位是符號位,而其余的位用來表示該數字絕對值的二進制。所以 +2 的原碼是 000…010,-2 的的原碼是 100.…010。
  • 那么我們是不是可以直接使用負數的原碼來進行減法計算呢?答案是否定的。還是以 3+(-2) 為例。
    2,它的十進制是 000…010。最低的兩位是 10,前面的高位都是 0。如果我們使用 -2 的原碼,也就是 100…010,然后我們把 3 的二進制原碼 000…011 和 -2 的二進制原碼 100…010 相加,會得到 100…0101。具體計算看這幅圖。

    二進制編碼上的加減法和十進制類似,只不過,在加法中,十進制是滿 10 才進一位,二進制加法中只要滿 2 就進位;同樣,在減法中,二進制借位后相當于 2 而不是 10。
    相加后的結果是二進制 100…0101,它的最高位是 1,表示負數,而最低的 3 位是 101,表示 5,所以結果就是 -5 的原碼了,而 3+(-2) 應該等于 1,兩者不符。
    那該怎么辦呢?這個問題的解答還要依賴計算機的溢出機制。
    對計算機里的減法進行變換。假設有 i-j,其中 j 為正數。如果 i-j 加上取模的除數,那么會形成溢出,并正好能夠獲得我們想要的 i-j 的運算結果。如果還是不太好理解,參考下面這張圖。

    把這個過程用表達式寫出來就是 i-j=(i-j)+(2n-1+1)=i+(2n-1-j+1)。
    其中 2^n-1 的二進制碼在不考慮符號位的情況下是 n-1位的 1,那么 2^n-1-2 的結果就是下面這樣的:

    從結果可以觀察出來,所謂 2^n-1-j 相當于對正數 j的二進制原碼,除了符號位之外按位取反(0 變 1,1 變 0)。由于負數 -j 和正數 j 的原碼,除了符號位之外都是相同的,所以,2^n-1-j 也相當于對負數 -j 的二進制源碼,除了符號位之外按位取反。我們把 2^n-1-j 所對應的編碼稱為負數 -j 的反碼。所以,-2 的反碼就是 1111…1101。
    有了反碼的定義,那么就可以得出 i-j=i+(2^n-1-j+1)=i 的原碼 +(-j 的反碼)+1。
    如果我們把 -j 的反碼加上 1 定義為 -j 的補碼,就可以得到 i-j=i 的原碼 +(-j 的補碼)。
    由于正數的加法無需負數的加法這樣的變換,因此正數的原碼、反碼和補碼三者都是一樣的。最終,我們可以得到 i-j=i 的補碼+(-j 的補碼)。
    換句話說,計算機可以通過補碼,正確地運算二進制減法。我們再來用 3+(-2) 來驗證一下。正數 3 的補碼仍然是 0000…0011,-2 的補碼是 1111…1110,兩者相加,最后得到了正確的結果 1 的二進制。

    可見,溢出本來是計算機數據類型的一種局限性,但在負數的加法上,它倒是可以幫我們大忙。

2.位操作應用實例

1.1. 驗證奇偶數

  • 奇偶數其實也是余數的應用。編程中,也可以用位運算來判斷及偶數。
  • 仔細觀察,你會發現偶數的二進制最后一位總是 0,而奇數的二進制最后一位總是 1,因此對于給定的某個數字,我們可以把它的二進制和數字 1 的二進制進行按位“與”的操作,取得這個數字的二進制最后一位,然后再進行判斷。
  • 同樣次數的奇偶判斷,使用位運算的方法耗時明顯更低。

2.交換兩個數字

  • 想在計算機中交換兩個變量的值,通常都需要一個中間變量,來臨時存放被交換的值。不過,利用異或的特性,我們就可以避免這個中間變量。具體的代碼如下:

    x = (x ^ y);
    y = x ^ y;
    x = x ^ y;

-把第一步代入第二步中, 可以得到

y = (x ^ y) ^ y = x ^ (y ^ y) = x ^ 0 = x

  • 把第一步和第二步的結果代入第三步中,可以得到:

x = (x ^ y) ^ x = (x ^ x) ^ y = 0 ^ y = y

  • 這里用到異或的兩個特性,第一個是兩個相等的數的異或為 0,比如 x^x= 0;第二個是任何一個數和 0 異或之后,還是這個數不變,比如 0^y=y。

3. 集合操作

  • 集合和邏輯的概念是緊密相連的,因此集合的操作也可以通過位的邏輯操作來實現。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容