我是靠谱客的博主 可靠小海豚,最近开发中收集的这篇文章主要介绍算法笔记,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

算法笔记

欧几里得算法求最大公约数

~又称辗转相除法,求两数的最大公约数

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)的线性复杂度

aokpif.jpg](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 =a_n(n-1)! +a_{n-1}(n-2)!+dots+a_1*0!+1 $, 其中 X 代表排列中的排名, a i a_i ai代表该排列中的第 i 位数字在第 i 位其后的数字中按升序排在第几个(顺序0 为最小)。

例如 1 ~ 5排列中,求 34152的排名

第一位 3:小于3的有1,2两个 --> 2 ∗ ( 5 − 1 ) ! = 2 ∗ 4 ! 2*(5-1)!=2*4! 2(51)!=24!

第二位4:小于4的有1,2,3(不能动),只能比较后面四位 2 ∗ ( 5 − 2 ) ! = 2 ∗ 3 ! 2*(5-2)!=2*3! 2(52)!=23!

第三位1:在1,5,2中排0位

第四位数字是5:在52中排第0个

第五位固定为0 a n = 0 a_n=0 an=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("%dn",x);
}

逆康托展开

在1~5的排列中,求解第62位的具体排列

61 / 4 ! = 2 … 13 61/4! = 2dots 13 61/4!=213 首位排第2(以0位最小)

13 / 3 ! = 2 … 1 13/3! = 2 dots 1 13/3!=21 第二位数字在剩余数字排第 2

1 / 2 ! = 0 … 1 1/2! = 0 dots 1 1/2!=01

$1/1! = 1dots 0 $

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排列
}
}

例如: 34152 = = 2 ∗ 4 ! + 2 ∗ 3 ! + 0 ∗ 2 ! + 1 ∗ 1 ! 34152 == 2*4! + 2*3! + 0*2!+1*1! 34152==24!+23!+02!+11! ,除以 4 ! 4! 4!后,表示后面数字比首数字大的数有2个,也就是3,把3选出来后要记得在操作数组v中把3剔除,不然会影响数字比较大小的操作。

同余定理

给定一个正整数 m ,如果两个整数 a 和 b 满足 a - b 能够被 m 整除,即 (a-b)/m 得到一个整数,那么就称整数 a 与 b 对模 m 同余,记作 a ≡ b ( m o d m ) aequiv b(mod m) ab(modm) 。对模 m 同余是整数的一个等价关系。

数学上,两个整数除以同一个整数,若得相同余数,则二整数同余。

其数学意义是a%m = b%m ,若 a,b作差,则相同得余数一定会被抵消掉,即(a-b)%m=0

假设 (a-b)=m*k(k是整数) —> a=b+m*k

同余的性质

  • a ≡ a ( m o d m ) aequiv a(mod m) aa(modm)
  • 如果 a ≡ b ( m o d m ) aequiv b(mod m) ab(modm),则 b ≡ a ( m o d m ) bequiv a(mod m) ba(modm)
  • 如果 a ≡ b ( m o d m ) aequiv b(modm) ab(modm) b ≡ c ( m o d m ) bequiv c(modm) bc(modm),则 a ≡ c ( m o d m ) aequiv c(modm) ac(modm)
  • 如果a与b除以m的余数相同,那么an与bn除以m的余数也相同,但不一定等于原余数

如果 a ≡ b ( m o d    m ) aequiv b(mod m) ab(modm) c ≡ d ( m o d    m ) cequiv d(mod m) cd(modm),则

1、 ( a + c ) ≡ ( b + d ) ( m o d    m ) (a+c)equiv (b+d) ( mod m) (a+c)(b+d)(modm)

2、 ( a − c ) ≡ ( b − d ) ( m o d    m ) (a-c) equiv (b-d) (mod m) (ac)(bd)(modm)

3、 ( a ∗ c ) ≡ ( b ∗ d ) ( m o d    m ) (a*c) equiv (b*d)(mod m) (ac)(bd)(modm)

4、 ( a + b ) ≡ c ( m o d m ) − − > a ≡ ( c − b ) ( m o d m ) (a+b) equiv c (mod m )--> a equiv(c-b)(mod m) (a+b)c(modm)>a(cb)(modm)

  • 如果 a c ≡ b c ( m o d    m ) acequiv bc(mod m) acbc(modm),那么

a ≡ b ( m o d m g c d ( c , m ) ) a equiv b(mod frac{m}{gcd(c,m)}) ab(modgcd(c,m)m)

aomgzt.md.jpg

  • 若有 a ≡ b ( m o d    m ) 且 aequiv b(mod m)且 ab(modm) n ∣ m n|m nm,则 a ≡ b ( m o d    m ) aequiv b( mod m) ab(modm)

次方取模算法

目标 : 求 a b m o d    c a^{b}mod c abmodc (b是一个大数)

原理

( a ∗ b ) m o d    c = [ ( a m o d    c ) ∗ ( b m o d    c ) ] m o d    c (a*b)mod c=[(a mod c)*(b mod c)]mod c (ab)modc=[(amodc)(bmodc)]modc

考虑到b是一个大数,直接算 ab 会很慢,所以首先把 b 转换成二进制形式(这个转换不用再写一个程序,计算机里面就是二进制保持的)

b = ( b n b n − 1 … b 3 b 2 b 1 b 0 ) 2 b=(b_nb_{n-1}dots b_3b_2b_1b_0 )_2 b=(bnbn1b3b2b1b0)2

b = b 0 + b 1 ∗ 2 1 + b 2 ∗ 2 2 + b 4 ∗ 2 4 + ⋯ + b n − 1 ∗ 2 n − 1 + b n ∗ 2 n b=b_0+b_1*2^1+b_2*2^2+b_4*2^4+dots+b_{n-1}*2^{n-1}+b_n*2^n b=b0+b121+b222+b424++bn12n1+bn2n

a b = a b 0 + b 1 ∗ 2 1 + b 2 ∗ 2 2 + b 4 ∗ 2 4 + ⋯ + b n − 1 ∗ 2 n − 1 + b n ∗ 2 n a^b=a^{b_0+b_1*2^1+b_2*2^2+b_4*2^4+dots+b_{n-1}*2^{n-1}+b_n*2^n} ab=ab0+b121+b222+b424++bn12n1+bn2n

= a b 0 ∗ a b 1 ∗ 2 ∗ a b 2 ∗ 2 2 ∗ a b 3 ∗ 2 3 ∗ a b 4 ∗ 2 4 + ⋯ + a b n − 1 ∗ 2 n − 1 + a b n ∗ 2 n =a^{b_0}*a^{b_1*2}*a^{b_2*2^2}*a^{b_3*2^3}*a^{b_4*2^4}+dots +a^{b_{n-1}*2^{n-1}}+a^{b_n*2^n} =ab0ab12ab222ab323ab424++abn12n1+abn2n

a b % c = ( a b 0 ∗ a b 1 ∗ 2 ∗ a b 2 ∗ 2 2 ∗ a b 3 ∗ 2 3 ∗ a b 4 ∗ 2 4 + ⋯ + a b n − 1 ∗ 2 n − 1 + a b n ∗ 2 n ) % c a^b%c=(a^{b_0}*a^{b_1*2}*a^{b_2*2^2}*a^{b_3*2^3}*a^{b_4*2^4}+dots +a^{b_{n-1}*2^{n-1}}+a^{b_n*2^n})%c ab%c=(ab0ab12ab222ab323ab424++abn12n1+abn2n)%c

A n = ( a b 0 ∗ a b 1 ∗ 2 ∗ a b 2 ∗ 2 2 ∗ a b 3 ∗ 2 3 ∗ a b 4 ∗ 2 4 ∗ ⋯ ∗ a b n − 1 ∗ 2 n − 1 a b n ∗ 2 n ) % c A_n=(a^{b_0}*a^{b_1*2}*a^{b_2*2^2}*a^{b_3*2^3}*a^{b_4*2^4}*dots *a^{b_{n-1}*2^{n-1}}a^{b_n*2^n})%c An=(ab0ab12ab222ab323ab424abn12n1abn2n)%c

A n = [ ( a b 0 ∗ a b 1 ∗ 2 ∗ a b 2 ∗ 2 2 ∗ a b 3 ∗ 2 3 ∗ a b 4 ∗ 2 4 ∗ ⋯ ∗ a b n − 1 ∗ 2 n − 1 ) % c ∗ ( a b n ∗ 2 n ) % c ] % c A_n=[(a^{b_0}*a^{b_1*2}*a^{b_2*2^2}*a^{b_3*2^3}*a^{b_4*2^4}*dots *a^{b_{n-1}*2^{n-1}})%c * (a^{b_n*2^n})%c]%c An=[(ab0ab12ab222ab323ab424abn12n1)%c(abn2n)%c]%c

= [ A n − 1 ∗ ( a b n ∗ 2 n ) % c ] % c =[A_{n-1}*(a^{b_n*2^n})%c]%c =[An1(abn2n)%c]%c

为了方便这里设 K n = ( a b n ∗ 2 n ) % c K_n=(a^{b_n*2^n})%c Kn=(abn2n)%c

A n = ( A n − 1 ∗ K n ) % c A_n=(A_{n-1}*K_n)%c An=(An1Kn)%c

A 0 = a b 0 % c A_0=a^{b_0}%c A0=ab0%c

screenshot 20200809 170228 com.huawei.browser

由上可知当 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;
}

三角形面积

求法:

  • 已知三顶点的坐标,求面积

( x 1 , y 1 ) , ( x 2 , y 2 ) , ( x 3 , y 3 ) (x_1,y_1),(x_2,y_2),(x_3,y_3) (x1,y1),(x2,y2),(x3,y3)

S = 1 2 ∣ ( x 2 − x 1 ) ( y 3 − y 1 ) − ( x 3 − x 1 ) ( y 2 − y 1 ) ∣ S=frac{1}{2}|(x_2-x_1)(y_3-y_1)-(x_3-x_1)(y_2-y_1)| S=21(x2x1)(y3y1)(x3x1)(y2y1)

  • 已知三条边长

S = p ( p − a ) ( p − b ) ( p − c ) S=sqrt{p(p-a)(p-b)(p-c)} S=p(pa)(pb)(pc)

p = a + b + c 2 p=frac{a+b+c}{2} p=2a+b+c

三点顺序

给出三个点,让你判断 是顺时针还是逆时针给出的

叉乘计算方法

∣ a ∗ b ∣ = ∣ a ∣ ∣ b ∣ s i n θ |a*b|=|a||b|sin{theta} ab=absinθ

坐标表示 ( x 1 ∗ y 2 − x 2 ∗ y 1 ) (x_1*y_2-x_2*y_1) (x1y2x2y1)

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;
}

归并排序

//思想:反复将数值元素分成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 %dn",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;
}
}

最后

以上就是可靠小海豚为你收集整理的算法笔记的全部内容,希望文章能够帮你解决算法笔记所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(45)

评论列表共有 0 条评论

立即
投稿
返回
顶部