zoukankan      html  css  js  c++  java
  • 整数的故事(1)

      程序设计课程总是充满趣味,在学习了判断和循环后就可以编写一些有意思的代码。记得我在初学编程时,老师曾出过一个题目:找出两个整数的最大公约数。当时我在黑板上写下了自己的实现方式:

     1 def gcd(a, b):
     2     if a == b:
     3         return a
     4 
     5     if a < b:
     6         a,b = b,a
     7 
     8     result = [i for i in range(1, b + 1) if a % i == 0 and b % i == 0]
     9 
    10     return  result.pop()

       运行结果是正确的,回到座位上,我为此高兴了两分钟。

      后来老师写出了另一个实现:

    1 def gcd(a, b):
    2     if b == 0:
    3         return a
    4     else:
    5         return gcd(b, a % b)

       我的第一反应是:“嗯?”

      遗憾的是,我并没有对这段代码深究,只是简单的记住了这种方法,反正都交给计算机计算,何必在乎快慢呢?

      后来学了数据结构,知道了用大O评估算法效率,我这才开始重新审视那段寻找最大公约数的代码——它实际上使用了传说中的“辗转相除”,要真正弄清楚来龙去脉,还要从整数说起……

    整除和余数

      我们都曾经用笨拙的声音从1数到10,这大概是人生中第一次接触数学。稍大一点后,懂得了零的概念,再后来知道了有负数存在……这些美好的记忆都有整数伴随在左右。随着年龄的增长和知识的提升,我们知道了更多关于整数的知识,这其中就包括整除和余数。

    欧几里德算式

      数学中是以数轴分段的方式定义整除的,如果n是一个正整数,那么可以用n的倍数将数轴分成很多段:

     

      如果将一个整数m放在数轴上,那么m将正好位于qn和(q+1)n之间,其中q也是一个整数:

     

      如果m正好是n的整数倍,那么m=qn,否则可以写成m=qn+r形式,qn是在m左侧最近的n的整数倍,r是qn到m的距离。如果把两种情况合并,那么m总是可以写成下面的形式:

     

      对于特定的n来说,m的表达式唯一的,这种表达式叫做欧几里德算式,也叫做除法算式。

      看着挺唬人,其实欧几里德算式有更常见的描述:如果m,n都是整数,并且n≠0,那么总是存在q和r,0≤r<|n|,使得m有唯一的表达式:

      其中q是商,r是余数,如果r=0,则称m 能够被n整除,或n能整除m,记作n|m。看来欧几里德算式只不过是从代数上解释了什么是整除,什么是余数。

       示例   找出q和r

       

      1和2比较简单:

     

      3可能会出点差错:

     

      计算机运行的结果:

      看来计算机认为是另一种答案:

     

      定义终于显现出作用了,在余数的定义中0≤r<|n|,r=-1不满足这个条件,所以正确答案是q=-3,r=4。

    整除的性质

      整除有一些被人们熟知的性质,如果a,b,c都是整数,则:

      1. 如果a|b且a|c,则a|(b+c)

      2. 如果a|b,则a|cb

      3. 如果a|b且b|c,则a|c

      由于0不能作为除数,所以a|b包含的默认条件是a≠0。

      此外还有一个推论,如果a,b,c都是整数,当a|b且a|c时,对于任意整数m和n,都有a|(mb+nc)。

      除法是乘法的逆运算,这些性质和推论其实都是根据乘法的分配律和结合律推导而来的。

    素数

      整数的故事中少不了素数,它的另一个名称是质数(prime number),是一种大于1整数。素数是这样定义的:设p是大于1的正整数,如果能整除p的正整数只有p和1,那么p就是一个素数。其中1比较特殊,它不是素数,是单位数。2,3,5,7,11是素数,4,6,8,10,12不是素数。

    寻找素数

      看起来判断一个整数是否是素数很简单,但这句话仅适用于较小的整数,稍大一点的整数就没那么容易判断了,1234567是否是素数?给你10秒钟时间。

      这种复杂的问题还是交给计算机去处理:

     1 # 判断a是否是素数
     2 def is_prime(a):
     3     if a < 2:
     4         return False,a
     5     elif a == 2:
     6         return True,a
     7 
     8     # 用从 2 到 a-1 之间的每个数除以a,看看那个能被整除
     9     divisors = [x for x in range(2, a) if a % x == 0]
    10 
    11     return len(divisors) == 0,divisors 
    12 
    13 print(is_prime(1234567))

      is_prim返回一个包含两个元素的元组,第一个元素回答了a是否是素数,另一个回答了除了1和自身外,a还有哪些约数。

      结果显示1234567是不一个素数,除了1和自身外,它还有127和9721两个因数。

    寻找素数2.0版

      is_prime方法能够正确运行,但仍然有改进的余地。根据欧几里德算式,m=qn,q和n二者此消彼长,它们的平衡点是根号m,这意味着只要判断2到根号m间是否存在能够被m整除的数就可以;此外,一个大于2偶数一定不是素数,所以只需要判断奇数即可。由此得到了判断素数的改进版:

     1 import math
     2 
     3 # 判断a是否是素数
     4 def is_prime_2(a):
     5     if a < 2:
     6         return False
     7     elif a == 2:
     8         return True
     9     elif a % 2 == 0:
    10         return False
    11 
    12     result = True
    13     # 取 math.sqrt(a) 的整数部分
    14     end = int(math.sqrt(a))
    15     q = 3
    16     while(q <= end):
    17         if a % q == 0:
    18             result = False
    19             break
    20         q += 2
    21 
    22     return result

    整数分解

      我们知道多项式的因式分解,类似地,整数也可以分解,可以将一个正整数写成它的几个约数因子的乘积,这就是整数分解(integer divisorization)。

    素因子表达式

      大于1的整数分解可以更进一步,使每个因子都是素数。对于每一个大于1的正整数m来说,可以唯一地写成:

      其中pi是能整除m的素数因子,p1<p2<…<pt;ki是pi出现的次数。这种表达式被称为整数m的素因子表达式,对于任意m,它的素因子表达式是独一无二的。将m写素因子表达式的过程叫做素因子分解。

      素因子表达也从另一个层面(非素数的层面)定义了素数:如果一个大于1的整数m不是素数,那么m一定能够分解成2个或两个以上素数的乘积,并且这个表达式是唯一的。

     

      示例  写出792030的素因子表达式

      7本身是一个素数,只能整数分解成1×7,但1并不是素数,所以7的素因子表达式就是7本身。

      可以使用下面的代码进行整数的素因子分解:

     1 # 获取a的所有除1和自身外的约数
     2 def get_divisors(a):
     3     return [x for x in range(2, a) if a % x == 0]
     4 
     5 # 素因子分解
     6 def prim_division(a):
     7     if is_prime_2(a):
     8         return [a]
     9 
    10     result = []
    11     divisors = get_divisors(a)
    12     for divisor in divisors :
    13         while True:
    14             if a % divisor != 0:
    15                 break
    16             if is_prime_2(divisor):
    17                 result.append(divisor)
    18                 a /= divisor
    19             else:
    20                 break
    21 
    22     return result

       好了,我们已经知道整数可以素因子分解,但是这有什么用呢?

      数学的发展来源于实践,不能用的东西大概也没人去研究。素因子分解的用处还挺多,它可以用来证明素数有无穷个,根号2是无理数,还可以用于密码学、计算复杂理论,甚至用于量子计算等等。接下来就举几个例子,看看素因子分解究竟是怎么使用的。

    素数有无穷个吗?

      老师:素数有无穷个吗?

      同学:当然了!

      老师:为什么呢?

      同学:没有为什么啊,它当然是无穷的,这还要正明吗?随便给出一个素数,不是很容易的例举出比它更大的素数吗?

      老师:但是素数没什么先验的理由必须有无穷多个啊。如果写出一个相当长的,能够绕地球一圈的素数,你能保证一定有一个更大的素数吗?

      同学:……

      老师:其实你已经回答了,随便给出一个素数,确实能够例举出比它更大的素数,只不过我们需要使用反证法来证明。

      假设素数的个数是有限的,那么这些素数都可以用集合的形式列举出来:

      P就是集合中最大的素数。

      根据整数的素因子分解,一个大于1的正整数可以分解成若干个素数的乘积,那么存在一个整数M,它等于Ω中所有元素的乘积加1:

      M肯定比P更大,P已经被假定是最大的素数,所以M肯定不是素数。素因子表达式告诉我们,M如果不是素数,则一定能够分解成若干个素数的乘积,因为已经假设Ω中包含了所有的素数,所以M也一定能够分解成Ω中若干个元素的乘积。但是现在M除以Ω中的所有元素都会产生余数1,这意味着M只能被M或1整除,所以M也是一个素数,这与“M肯定不是素数”矛盾,因此“素数有无穷个”。

      注:“素数有无穷个”这一命题最早的证明出现在古希腊数学家欧几里得 (Euclid) 的《几何原本》上,这一命题也因此被称为了 “欧几里得定理” (Euclid's theorem) 或 “欧几里得第二定理” (Euclid's second theorem)。

    根号2为什么“无理”

      老师:根号2是一个无理数,它无限不循环,没有尽头。

      同学:为什么呢?也许它在绕地球一圈后循环了。

      老师:它确实是不循环的,能试试自己证明吗?

      同学:还是使用反证法吗?

      老师:是的。证明的时候别忘了素因子分解。

      假设根号2 是一个有理数,根据有理数的定义,有理数是一个整数a和一个正整数b的比,这样一来,就可以得出:

      其中a/b不能通分,也就是说a和b是互素的(a和b的公约数只有1)。

      现在将等式两侧同时平方:

      2b2肯定是偶数,所以a2也是偶数,如果a是奇数,则a2也是奇数,所以a只能是偶数,a一定可以素因子分解成:

      将a2=2b2等式两侧同时除以2:

     

      a2是偶数,a2/2还是偶数。a2/2= b2所以b2也是偶数,b仍然是偶数,b可以素因子分解成:

     

      现在2|a并且2|b,这和a、b 互素矛盾,所以根号2是无理数。

      待续

     


       作者:我是8位的

      出处:http://www.cnblogs.com/bigmonkey

      本文以学习、研究和分享为主,如需转载,请联系本人,标明作者和出处,非商业用途! 

      扫描二维码关注公众号“我是8位的”

  • 相关阅读:
    文摘
    Maximal Square leetcode
    Majority Element II
    Merge k Sorted Lists leetcode
    学习方法-暗时间
    4sum leetcode
    valid parentheses
    两道考研算法设计题- 2010 2013
    regular expression matching DP
    valid sudoku leetcode
  • 原文地址:https://www.cnblogs.com/bigmonkey/p/10237155.html
Copyright © 2011-2022 走看看