数论
文章目录
一.GCD
1.gcd的定义
GCD 即最大公约数Greatest Common Divisor。整数 a 和 b 的最大公约数是指能同时整除 a 和 b的最大整数,记为 gcd(a,b)
2.gcd的性质
- gcd(a,b)=gcd(a,a+b)=gcd(a,k⋅a+b)
- gcd(ka,kb)=k⋅gcd(a,b)。
- 多个整数的最大公约数:gcd(a,b,c)=gcd(gcd(a,b),c)。
- 若 gcd(a,b)=d,则 gcd(a/d,b/d)=1,即 a/d=与 b/d=互素
- gcd(a+cb,b)=gcd(a,b)
3.gcd实现
① 库函数gcd()——python中gcd不会返回负数
import math
#求这两个整数的最大公约数
print (math.gcd(3, 6))
print (math.gcd(6, 12))
print (math.gcd(12, 36))
print (math.gcd(-12, -36))
print (math.gcd(5, 12))
print (math.gcd(10, 0))
print (math.gcd(0, 34))
print (math.gcd(48,96,120)) # 支持传入多个参数
②自写gcd()
辗转相除法:
gcd(a,b)=gcd(b,a mod b)
# 递归
def gcd(a,b):
if b==0:
return a
else:
gcd(b,a % b)
二.LCM
1.lcm的定义
两个或多个整数公有的倍数叫做它们的公倍数,其中除0以外最小的一个公倍数就叫做这几个整数的最小公倍数。整数a,b的最小公倍数记为lcm(a,b)
2.lcm与gcd
3.lcm的实现
① 库函数实现
from math impport *
print(lcm(3,6,8,9)) # 支持多个参数
--->72
②自写lcm
from math impport *
def lcm(x,y):
return x*y//gcd(x,y) # 一定能整除gcd
三.快速幂
1.快速幂原理
传统的幂运算,是对底数进行连乘,时间复杂度为o(n),例如:2^ 13 = 2 2 *2……2,连乘十三次。但是我们可以通过增加底数,减少指数的做法,降低时间复杂度。从而能够实现复杂度为O(logn)的幂运算。还是以2^13为例,13的二进制为1101,因此2的13次方可以分解成以下形式:
-
其分解的幂次都是2的倍数
-
幂次用二进制分解:13=1101 即化为0、4、8次
-
取模的性质:
a^n mod m = (a mod m)^n mod m
2.快速幂的实现
①自写
&:为按位与 是按二进制位与,比较两个二进制位数,如果上下同为0则为0,如果一个为1,一个为0则为0,如果同时为1则为1.
> > >> >>:右移位运算 将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃
- 求a的n次方
def fastpower(a,n):
ans = 1
while n:
if n & 1:
ans *= a
a *= a
n >>= 1
return ans
- 求a的n次方对b取余
def fastpower(a,b,n):
ans = 1
while n:
if n & 1: # 处理n的最后一位
ans = ans*a%b # 如果最后一位是1 则乘
a = a*a%b # 递推:a^2--a^4--a^8...
n >>= 1 # 右移一位 把处理过的最后一位删除
return ans%b
②pow()—python中比快速幂快
语法:pow(x, y[, z])
函数是计算x的y次方,如果z在存在,则再对结果进行取模,其结果等效于pow(x,y) %z
注意:pow() 通过内置的方法直接调用,内置方法会把参数作为整型,而 math 模块则会把参数转换为 float
import math # 导入 math 模块
print ("math.pow(100, 2) : ", math.pow(100, 2))
# 使用内置,查看输出结果区别
print ("pow(100, 2) : ", pow(100, 2))
print ("math.pow(100, -2) : ", math.pow(100, -2))
print ("math.pow(2, 4) : ", math.pow(2, 4))
print ("math.pow(3, 0) : ", math.pow(3, 0))
--->
math.pow(100, 2) : 10000.0
pow(100, 2) : 10000
math.pow(100, -2) : 0.0001
math.pow(2, 4) : 16.0
math.pow(3, 0) : 1.0
四.矩阵乘法
矩阵乘法类型
① 对位乘积:两个矩阵shape相同,各元素对应相乘,结果是一个相同shape的矩阵
② 矩阵乘法:数学上的矩阵乘法,结果是一个矩阵
③ 向量内积:对应元素相乘,再相加,结果是一个数值
1.for循环
一个m行n列的矩阵,用二维数组matrix[][]存储
矩阵乘法:两个矩阵A,B相乘,要求A的列数等于B的行数,设A为mxn,B为nxu,C=AB 为mxn
**# 三层循环**
for i in range(1,m+1): # 表示A第一到m行
for j in range(1,u+1): # 表示B第一到u列
for k in range(1,n+1): # A,B共有的行列数
c[i][j] += a[i][k] * b[k][j]
# 对应元素相乘
2.np.multiply(A,B) 或 A * B(基于numpy)
作对位乘积
import numpy as np
A = np.array([
[1,2],
[3,4]])
B = np.array([
[1,2],
[3,4]])
C3 = A*B
C4 = np.multiply(A,B)
print(C3)
print('---------')
print(C4)
--->
[[ 1 4]
[ 9 16]]
---------
[[ 1 4]
[ 9 16]]
3.np.dot(A,B) 或 A @ B(基于numpy)
作矩阵乘法
import numpy as np
A = np.array([
[1,2],
[3,4]])
B = np.array([
[1,2],
[3,4]])
C1 = A @ B
C2 = np.dot(A,B)
print(C1)
print('---------')
print(C2)
--->
[[ 7 10]
[15 22]]
---------
[[ 7 10]
[15 22]]
五.素数
质数又称素数。指在一个大于1的自然数中,除了1和此整数自身外,不能被其他自然数整除的数。素数在数论中有着很重要的地位。比 1 大但不是素数的数称为合数。1 和 0 既非素数也非合数,2 是素数
1.试除法
这里我们就可以使用试除法,试除法的思路就是:我们给定一个数N范围在一百到两百之间,然后使用N除以2到N-1,如果都不能除尽则N为素数
试除法优化:将[2,n-1]缩小到[2,√n]即可
证明:若n=axb 则a≤√n,b≥√n,如果n有个因子为a,说明n不是素数 不用再判断b
**
from math import sqrt
def isPrimes1(n):
if n <= 1: **# 小于等于1的数不是素数**
return False
for i in range(2, int(sqrt(n) + 1)): **# 范围只到√n**
if n % i == 0:
return False
return True
再优化试除法:将2之外的偶数排除,降低搜索
from math import sqrt
def isPrimes2(n):
if n > 1:
if n == 2: # 2为素数
return True
if n % 2 == 0: # 能整除2 一定不是素数
return False
for x in range(3, int(sqrt(n) + 1), 2): # 步长为2 均为奇数
if n % x == 0:
return False
return True
return False
2.素数筛
①欧式筛
(由再优化试除法,可知是否为素数还与基础素数的倍数有关)
埃拉托斯特尼筛法,简称埃氏筛,是一种由希腊数学家埃拉托斯特尼所提出的一种简单检定素数的算法:
要得到自然数n以内的全部素数,必须把不大于根号n的所有素数的倍数剔除,剩下的就是素数
给出要筛数值的范围n,找出以内的素数。先用2去筛,即把2留下,把2的倍数剔除掉;再用下一个质数,也就是3筛,把3留下,把3的倍数剔除掉;接下去用下一个质数5筛,把5留下,把5的倍数剔除掉…(不断重复下去)
如果 x是质数,那么大于 x 的 n 的倍数 2x,3x,… 一定不是质数
代码实现:
n = 10**8 # 代求的范围中的最大值
k = 0
s = [True for i in range(n)] # 首先默认所有数都是质数
z = []
for i in range(2,n):
if s[i]: **# 判断是否为质数,如果没有被标记过,就是质数**
k+=1
z.append(i) # 添加质数
for j in range(i+i,n,**i(步长)**): **# 将是指数的倍数的数都改为False**
s[j] = False
print(k) # 质数个数
print(z) # 质数列表
②欧拉筛
在埃氏筛法的基础上,让每个合数只被它的最小质因子筛选一次,以达到不重复的目的
如:2的时候已经去掉了2的倍数,4的时候还要去掉4的倍数,这一步是不是很多余。所以欧拉筛法就是在埃式筛法的基础上加一句话 if ls[x]==True:
代码实现:
from math import sqrt
def hanshu(n):
ls,x,y=[True]*(n+1),2,int(sqrt(n))+1 # 基本量
for i in (2,n):
**if ls[i]==True:** **# 欧拉筛优化方式:防止重复判断**
for j in range(x*2,n+1,x):
ls[j]=False # 标记已处理过的数的倍数
ls=[i for i in range(2,n+1) if ls[i]==True]
return ls
print(hanshu(100))
3.求区间素数
当数字过大时,存储空间不足,无法用[0,b]中素数减去[0,a]中素数来求[a,b]中素数个数,因此,需要在埃式筛下对区间[a,b]进行素数判断
from math import *
def seg_sieve(a,b):
for i in range(2,int(sqrt(b))+1):
if vis[i]==True: # 如果是素数
for j in range(i*i,int(sqrt(b)),i):
vis[j]=False # 标记i的倍数不是素数
for k in range(max(2,(a+i-1)//i)*i,b+1,i):
# 不论a是素数还是非素数 都从i的倍数开始
seg_prime[k-a]=False # 标记[a,b]区间内非素数
num=0
for i in range(0,b-a+1):
if seg_prime[i]:
prime[num]=i+a
num+=1
print(num) # 统计区间内素数个数
# print(prime)
N=1000001
vis=[True]*N # 判断2,√b是否为素数
prime=[0]*N # 存2,√b的素数
seg_prime=[True]*N # 标记[a,b]之间是否有素数
a,b=map(int,input().split())
seg_sieve(a,b)
六.高斯消元
1.原理
线性方程组有很多种解法,可以最简单的直接代入消元计算,但是运算量较大,且过程复杂不直观
高斯消元法目的是预处理方程组的系数矩阵,将系数矩阵变换为上三角矩阵,这样整个方程就变得清晰直观很多,即使不借助计算机,也是可以很简单的手算出结果
原方程:
高斯消元后的方程:
而列主元高斯消元法,是为了克服高斯消元法的”除法bug“的改进版本,因为消元过程中用到了除法,对于计算机来说,如果一个很小的值作为分母,则其在迭代中产生的误差,在做除法后会被放大很多倍。如0.0001与0.0002的误差小,但是1/0.0001与1/0.0002的误差巨大。列主元思想,就顺应而生,在消元前,把列中最大的元素所在行放到矩阵的最上方,那么就可以让较大值做分母,从而避免了误差被除法放大的问题
2.代码实现
代码实现:
eps=1e-7 **# 定义一个趋于0的极小数值 若x小于它 则x==0**
n=int(input())
a=[]
for _ in range(n): a.append(list(map(int,input().split()))) # 得到增广矩阵
for i in range(n): # 枚举行
index=i # 记录当前行
**# 选取该列的最大系数**
for j in range(i+1,n+1): # 增广矩阵有n+1列
if a[j][i] > a[index][i]: index=j
**# 将该行的所有系数与首行交换**
for j in range(i,n+1):
a[index][j],a[i][j]=a[i][j],a[index][j] # i为当前行
**# 换为最大后 若对角线上的主元为0 则该主元所在列为全0列 无解**
if abs(a[i][i])<eps:
print(-1)
exit(0) # 退出
**# 将处理后的该行主元变为1--->除以首元素**
for j in range(n,-1,-1):
a[i][j] /= a[i][i]
**# 对1到n行处理 使主元所在列上所有元素除第i行外全部后变为0**
for j in range(n):
if j!=i:
# i虽为行的定义 但随着消元的进行 化为上三角矩阵 i既为行也为列
x=a[j][i]/a[i][i] # x表示其他行为1的几倍
a[j][i]-=a[i][i]*x # 消为0
for i in range(n):
**# 对角线所在行的最后一个元素(常熟项矩阵)即为xi的解**
print('%.2f'%a[i][-1])
七.牛顿迭代法
step:
1. 我们在求解某一个方程的根时,可以先取函数上的一个易求解的特殊点,取其横坐标x0
2. 我们作x0处的切线,与x轴交于点x1,由于导数可求,因此,可以解出x1的值
3. 再在x1处作同样斜率的切线,交于x2,由此迭代,最终逼近最终解
如:若要求99的立方根,x^3-99=0,我们可以找出一个特殊值(如:x0=2)
代码实现:
import math
a = 99
x0 = 2
x1 = 2/3*x0+a/(3*x0**2)
while math.fabs(x1-x0) > 1E-5:
x0 = x1
x1 = 2/3*x0+a/(3*x0**2)
print(x1)
八.最小二乘法
1. 问题背景
在现实生活中,我们经常会遇到要拟合一些数据的情况,而且有时候我们并不能通过数学公式得到一个完美的函数来拟合这些数据,此时,我们可以使用最小二乘法来拟合这些数据。
💡 最小二乘法是一种用于拟合数据的常用方法,可以用于线性和非线性系统,可以用于单变量和多变量系统,可以用于有约束和无约束的系统。在Python 中,我们可以使用
numpy
库中的polyfit
函数来实现最小二乘法
2. 原理
最小二乘法是一种数学优化技术,它通过最小化误差的平方和寻找数据的最佳函数匹配。最小二乘法可以用于线性和非线性系统,可以用于单变量和多变量系统,可以用于有约束和无约束的系统。
在使用最小二乘法时,我们需要先假设一个函数的形式,然后通过最小化误差的平方和来确定函数中的参数。例如,如果我们想要用一个线性函数来拟合一组数据,可以假设函数的形式为 y = a*x + b
,然后通过最小化误差的平方和来确定 a 和 b 的值。
3. 使用方法
使用最小二乘法来拟合一组数据的步骤如下:
- 假设一个函数的形式。
- 定义一个误差函数,该函数为实际值与拟合值之差的平方和。
- 最小化误差函数,以确定函数中的参数。
4. 代码实现
Python 中可以使用 numpy
库中的 polyfit
函数来实现最小二乘法。例如,我们可以使用以下代码来拟合一组数据:
import numpy as np
# 定义数据
x = np.array([1, 2, 3, 4, 5])
y = np.array([1, 3, 5, 7, 9])
# 使用最小二乘法拟合数据
a, b = np.polyfit(x, y, 1)
# 输出结果
print("a = ", a)
print("b = ", b)
在上面的代码中,我们使用 polyfit
函数来拟合一组数据,其中:
x
和y
分别为输入的数据。1
表示我们要拟合的函数的阶数,这里我们假设拟合函数为一次函数。
b 的值。
九.总结
本章主要介绍了数论、埃氏筛法、欧拉筛法、求区间素数、高斯消元、牛顿迭代法和最小二乘法。其中,数论介绍了一些算法和原理,而其他部分则介绍了算法的实现和使用方法,数论往往依靠数学的知识和思维实现,因此代码并不会很复杂,而是考察思维能力,以上就是全部内容啦,如有错误,欢迎指正~~感谢!!
觉得本篇有帮助的话,就赏个三连吧~