简单的算法总结
算法笔记
欧几里得算法求最大公约数
~又称辗转相除法,求两数的最大公约数
gcd(a,b) = gcd(b,a%b)
一般代码递归形式
int gcd(int a,int b) { return b? gcd(b,a%b) :a ; }
迭代形式
int gcd(int a,int b) { while(1) { if(b == 0) return a; int temp = a%b; a = b; b = temp; } }
性质及证明:
-
设 x 为两整数a,b的最大公约数,那么
x|a,x|b
-
整数除法具有传递性(x 能整除 a,b的任意组合)则
x|a-b
;(重点) -
设x 不是 b 的因子,则 x 不是 b 和 a-b 的公因子;设x 不是 a的因子, 则 x 不是 b和 a-b 的公因子;所以
gcd(a,b) = gcd(b,a-b)
; -
由
a>=b
可知,a可表示为a=b*q+r
;则 a 减去 q 个 b 剩下的数字即为r
,所以gcd(a,b) = gcd(b,a%b)
;
性质
- 若
gcd(a,b)=1
那么 a,b 两数互质; gcd(a,2a)=a
;gcd(a,0)=a
;gcd(a,b)=gcd(-a,b)=gcd(a,-b)=gcd(-a,-b)
;LCM(a,b)gcd(a,b)=ab(LCM为最小公倍数)
;gcd(n,n+1)=1
;
筛法求素数
题目:判断 1-n 的范围内有多少素数这是一般代码
bool sushu(int n) { if(n<=1) return 0; for(int i=2;i<=sqrt(n);i++) { if(n%2 == 0) return 0; } return 1; }
当 n 取很大时,每判断一个数 i 是否为素数,都要枚举 sqrt(i)次,时间复杂度接近O(n*2)
方法:提前建立一张素数表,再通过查表的方式来判断素数,需要用到筛法,把表中不是素数的筛去
埃氏筛
主要内容:素数的倍数都不是素数,如2的倍数(偶数)都不是素数
用一个 prime[n] bool 数组来打表 ,prime[i]=0
表示 i 是素数,prime[i]=1
表示 i 不是素数
一开始都初始化为 0 ,认为都是素数,然后把不是素数的筛去(prime[i]标为 1)
//时间复杂度 O(nloglogn) #define ll long long long long n; bool prime[1000005] void ai_shai() { prime[1]=1;//1不是质数 for(ll i =2;i<=n;i++) if(prime[i] == 0) //如果是质数,防止产生多余步骤,比如4,6的倍数已经被2筛掉了 { for(ll j=i*i; j<=n;j+=i) prime[j]=1; //质数的倍数都不是质数 } }
上述的从i*i
开始筛选可以理解为,轮到3的时候,可以从3*3
筛选,因为3*2
已经被2*3
筛去了
欧式筛(线性)
对上述算法进一步优化,有些数会被不同的素数筛多次,比如30 = 3* 10 =5* 6(3,5,6各筛一次),所以欧式筛就是在基础上,让每个合数只被最小的质因数筛一次,这可以使得时间复杂度达到O(n)的线性复杂度
](https://imgchr.com/i/aIY8u4)
bool prime[1000005]; int zyz[1000005]; int cnt=0 void ol_shai { prime[1]=1; for(int i=2;i<=n;i++) { if(prime[i] == 0)//如果是质数 zyz[++cnt]=i;//记录质数的个数和数据 for(int j=1;j<=cnt&&i*zyz[j]<=n;++j) { prime[i*zyz[j]]=1; //让 循环的 i 乘以已经记录的cnt个质数 if(i%zyz[j] == 0) break;//如果zyz[i]还是i的最小质因数 //例如 i=4的时候(cnt = 2,zyz[2]=3,zyz[1] = 2)得prime[8]=1 //而 4%2==0,-->break || 不再进行4*3=12 ,后面i = 6的时候会进行,即 //**让每个合数只被最小的质因数筛一次** } } }
康托展开
给定一个1~n的排列,求该排列在 1 ~ n 的全排列中,按字典序排名多少位
例如{1,2,3}全排列为123,132,213,231,312,321,依次变大......
算法内容
公式, 其中 X 代表排列中的排名, 代表该排列中的第 i 位数字在第 i 位其后的数字中按升序排在第几个(顺序0 为最小)。
例如 1 ~ 5排列中,求 34152的排名
第一位 3:小于3的有1,2两个 -->
第二位4:小于4的有1,2,3(不能动),只能比较后面四位
第三位1:在1,5,2中排0位
第四位数字是5:在52中排第0个
第五位固定为0
代码实现
内容:求比指定排列小的排列个数
int num[100]; int factorial(int a) { if(a==1||a==0) return 1; return a*factorial(a-1); } void cantor() { int n; scanf("%d",&n); for(int i=1;i<=n;i+=) { scanf("%d",&num[i]); } int x=0; for(int i=1;i<n;i++) { int a_i = 0; for(int j=i+1;j<=n;j++) //a_n始终是 0 { if(num[i]>num[j]) a_i++; } x+=a_i * factorial(n-i); } x+=1;//最后加一 printf("%d\n",x); }
逆康托展开
在1~5的排列中,求解第62位的具体排列
首位排第2(以0位最小)
第二位数字在剩余数字排第 2
34152
int factorial(int a) { if(a==1||a==0) return 1; return a*factorial(a-1); } void decantor(int x,int n) { vector<int> v;//存放操作组合 vector<int> a;//所求排列组合,即排列结果 for(int i=1;i<=n;i++) v.push_back(i); for(int i=n;i>=1;i--) { int r=x%factorial[i-1]; int t=x/factorial[i-1]; x=r; sort(v.begin(),v.end()); a.push_back(v[t]);//第t+1个数 v.erase(v.begin()+t);//去除便于sort排列 } }
例如: ,除以后,表示后面数字比首数字大的数有2个,也就是3,把3选出来后要记得在操作数组v中把3剔除,不然会影响数字比较大小的操作。
同余定理
给定一个正整数 m ,如果两个整数 a 和 b 满足 a - b 能够被 m 整除,即 (a-b)/m 得到一个整数,那么就称整数 a 与 b 对模 m 同余,记作 。对模 m 同余是整数的一个等价关系。
数学上,两个整数除以同一个整数,若得相同余数,则二整数同余。
其数学意义是a%m = b%m
,若 a,b作差,则相同得余数一定会被抵消掉,即(a-b)%m=0
假设 (a-b)=m*k(k是整数)
---> a=b+m*k
同余的性质
- 如果 ,则
- 如果和,则
- 如果a与b除以m的余数相同,那么an与bn除以m的余数也相同,但不一定等于原余数
如果和,则
1、
2、
3、
4、
- 如果,那么
- 若有 且,则
次方取模算法
目标 : 求 (b是一个大数)
原理
考虑到b是一个大数,直接算 ab 会很慢,所以首先把 b 转换成二进制形式(这个转换不用再写一个程序,计算机里面就是二进制保持的)
设
为了方便这里设

由上可知当 bn=0时,Kn=1; 当 bn=1,Kn=Tn;
代码
int quick(int a,int b, int c) { int A=1; int T=a%c; while(b!=0) { if(b&1)//判断最右边bn是不是1,如果是1,K_n=Tn递推, { A=(A*T)%c; } b>>=1;//二进制位移,从右向左读取 T=(T*T)%c; } return A; }
三角形面积
求法:
- 已知三顶点的坐标,求面积
- 已知三条边长
三点顺序
给出三个点,让你判断 是顺时针还是逆时针给出的
叉乘计算方法
坐标表示
ans =(x2-x1)*(y3-y1) - (y2-y1)*(x3-x1) ans<0 顺时针 ans>0 逆时针
二分查找
#include<iostream> using namespace std; Template <class T> int midsort(T a[],int n,T target) { int low = 0,high = n-1,mid; while(low <= high) { mid = low+(high - low)/2; if(a[mid] = target) return mid; else if(a[mid]>target) high = mid - 1; else low = mid + 1; } return -1; }
二分递归写法
int binary_searchs(int *arr, int target, int l, int r) { if (l > r) return -1; int mid = (l + r) >> 1; if (arr[mid] == target) return mid; else if (arr[mid] > target) binary_search(arr, target, l, mid - 1); else binary_search(arr, target, mid + 1, r); }
归并排序
//思想:反复将数值元素分成2个(n/2)组数据,两个排完后把两组合并,然后变成4个一组,再把两组(每组包含四个元素)合并,完成排序 const int maxn = 100; void merge(int A[],int L1,int L2,int R1,int R2) //L1,R1 L2,R2分别表示两组数据的开头和结尾的索引 { int i = L1,j = L2; int temp[maxn],index = 0; while(i<=R1 && j<=R2) { if (A[i] <= A[j]){ temp[index++] = A[i++]; } else{ temp[index++] = A[j++]; } } while(i<=R1) temp[index++] = A[i++]; while(j<=R2) temp[index++] = A[j++]; for(i = 0;i<index;i++) A[L1+i] = temp[i]; } void mergeSort(int A[],int left,int right) { if(left<right){//如果left和right已经相邻了,那么计算mid后left==right不会再进入循环 int mid = (right+left)/2; mergeSort(A,left,mid); mergeSort(A,mid+1,right); merge(A,left,mid,mid+1,right); } }
快速排序
int Pos(int A[],int left,int right) { int temp = A[left]; while(left<right) { while(left<right && A[right]>temp) right -- ; A[left] = A[right]; // A[left]最后处理,先把小的数移到前面 while(left<right && A[left]<temp) left++; A[right] = A[left]; // 把大的数移到后面 } A[left] = temp; // 最后把基准数移到中间 return left; } // 也可以这样写 { while(...) r--; swap(a[i],a[j]); while(...) l++; swap(a[i],a[j]); // 最后不用交换 } void quickSort(int A[],int left,int right) { if(left<right){//当区间长度不超过1 int pos = Pos(A[],left,right); quickSort(A[],left,pos-1); quickSort(A[],pos+1,right);//一个减1,一个加1 } }
在递增数组中快速找到A+B=的数
void (int A[],left,right){ i = left; j = right;//i往右,j往左 while(i<j) { if(a[i]+a[j] == m) { printf("%d %d\n",i,j); i++; j--;//找到了一种方案 } else if(a[i]+a[j]<m) i++; else j--; } }
合并数组两个递增数组合并成一个递增数组
int merge(int A[],int B[],int C[],int m,int n) // m 和 n 分别是A和B的长度 { int i=0,j=0; int index = 0; while(i<m && i<n){ if(A[i]<=A[j]) C[index++] = A[i++]; else C[index++] = A[j++]; } while(i<m) C[index++] = A[i++]; while(j<n) C[index++] = A[j++]; return indedx;//序列的长度 }
质因子分解
#include<cstdio> #include<math.h> const int maxn = 1000010; bool is_prime(int n){//判断是否是素数 if (n==1) return false; for (int i =2;i<sqrt(n);i++) { if(n%i == 0) return false; } return true; } int pNum = 0,prime[maxn]; void Find_prime(){//求素数表 for (int i=2;i<maxn;i++){ if (is_prime(i)) prime[pNum++] = i; } } struct factor{ int x,cnt; }fac[10]; int main(){ Find_prime(); int n,num=0; cin>>n; if(n==1) cout<<"1==1"; else{ int sqr = (int) sqrt(1.0*n) // n的根号 for(int i=0;i<pNum && prime[i]<sqr;i++){ if(n%prime[i] ==0){ fac[num].x=prime[i];//先记录第一个数字 fac[num].cnt = 0;//进入循环后再加个数,次数 while(n%prime[i] == 0){ fac[num].cnt++; n /= prime[i]; } num ++;//下一个质因子 } if(n==1) break;//及时退出循环,节省时间 } if(n!=1){//如果无法被根号n以内的数除尽,比如108 = 59 * 2,59不满足if语句,永远进不了if判断,需要单独记录 fac[num].x = n; fac[num].cnt = 1; } for(int i=0;i<num;i++) { if(i>0) cout<<"*"; cout<<fac[i].x; if(fac[i].cnt>1) cout<<"^"<<"fac[i].cnt"; } } //以上都是else里面的内容 return 0; }
组合数计算
//递归写法 long long C(int n,int m){ if(m==1||m==n){ return 1; } return C(n-1,m)+C(n-1,m-1); } int res[60][60] = {0}; long long C(int n,int m){ if (m==1 || m==n) return 1; if(res[n][m]!=0) return res[n][m];//已经计算过的 return res[n][m] = C(n-1,m)+C(n-1,m-1);//没有计算过的 } //递归直接打表,把所有组合数即在表里 const int n = 60; for(int i=0;i<=n;i++){ res[i][0] = res[i][i] = 1;//初始化边界 for (int i=2;i<=n;i++){ for(int j =0;j<=i/2;j++){ res[i][j] = res[i-1][j]+res[i-1][j-1]; res[i][i-j] = res[i][j];//组合数的性质 } } } //化简计算 long long C(int n,int m){ ans = 1; for (int i=1;i<=m;i++){ ans = ans*(n-m+i)/i; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)