程序员的数学基础课

计算机的源头

  • 日常生活中,我们广泛采用十进制计数法。



    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. 集合操作

  • 集合和逻辑的概念是紧密相连的,因此集合的操作也可以通过位的逻辑操作来实现。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容