算法笔记(一)——前六章
一、快速入门
1、int 范围 10^6,浮点数全部用double
2、define pi 3.14 (不加分号)
3、const int INF =0x3fffff;
4、%md --右对齐输出 %0md --缺位用0补齐 %.mf–保留m位小数
5、常用math函数
fabs(double x) --绝对值
floor(double x)--向下取整 ceil (double x)--向上取整
asin(double x)--反正弦函数
round(double x)--四舍五入取整
6、memset: memset(a,-1,sizeof(a));
需要使用string头文件,只用于赋值0或-1。
7、gets/puts 用来输入输出一行字符串,以\n作为输入结束
==当用scanf输入一个整数后,需要先用getchar()接受整数后的换行符。==
gets(str1);
puts(str2);
==使用字符数组存储字符串时候至少将数组大小设置为比字符串实际大小大1(\0)
8、字符数组函数
strlen(字符数组)返回 \0 之前的长度 strcmp(字符数组1,字符数组2),小于返回负数,等于返回0,大于返回正数
strcpy(字符数组1,字符数组2) 将字符数组2复制给字符数组1 strcat(字符数组1,字符数组2) 将字符数组2接在字符数组1后面
9、sscanf和 sprintf
sscanf是将字符数组str中的内容以某种格式写到n中。
普通的scanf和printf都是以screen为对象。
sscanf 从字符数组中读取(到int中),sprintf 把(int)存到字符型数组中
int n=0;
char str[]="123";
sscanf(str,"%d",&n);
printf("%d\n",n);
//输出结果为: 123
sprintf 是将n以某种形式写入到str数组中
int n=233;
char str[100];
sprintf(str,"%d",n);
printf("%s\n",str);
//输出结果为: 233
其他例子
int n,db;
char str1[100]=“2028:3.14,hello”,str2[100];
sscanf(str1,"%d:%.lf,%s",n,db,str2);
int n=12;
double db=3.1415;
char str1[100],str[2]="good";
sprintf(str1,"%d:%.2f,%s",n,db,str2);
10、一般使用scanf和printf进行输入输出,在string时可以考虑使用cin,cout
11、其他常用表达
浮点数的比较,一般认为a落在[b-eps,b+eps]区间认为是等于。
const double eps =1e-8;
#define Equ(a,b) (fabs((a)-(b))<(eps))
#define More(a,b) ((a)-(b)>(eps))
#define Less(a,b) ((a)-(b)<(eps))
12、时间复杂度:对一般的OJ系统,每秒钟进行的运算次数为107~108,对于O(N2)的复杂度,n一般上限为1000。
二、C++快速入门 略
三、 入门篇–入门模拟 略
四、 入门篇–算法初步
1、散列
核心思想在于 将元素通过一个函数转换为一个整数,这个证书可以尽量唯一的代表这个元素,(可以通过它来进行O(1)的直接访问
2、分治思想:全排列问题
要生成n个数的全排列,考虑确定第1位,然后递归求2-n位的全排列
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
//书写全排列 2021.1.27
const int maxn=11;
//设置P为当前已经生成的序列,hashtable标记当前序列 0-9 十个数字的使用情况
int n,P[maxn],hashTable[maxn]={false};
void generateP(int index)
{
//当前处理的是第index位
//定义递归边界
if(index==n+1)
{
//P序列已经生成完毕,进行输出
for(int i=1;i<=n;i++)
cout<<P[i]<<' ';
cout<<endl;
return ;
}
else{
for(int x=1;x<=n;x++)
{
//从小的开始生成
if(hashTable[x]==false)
{
P[index]=x;
hashTable[x]=true;
generateP(index+1);
hashTable[x]=false;
}
}
}
}
int main()
{
cin>>n;
//从第1位开始生成
generateP(1);
return 0;
}
采用回溯法的n皇后问题:
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
//书写全排列 2021.1.27
const int maxn=11;
//设置P为当前已经生成的序列,hashtable标记当前序列 0-9 十个数字的使用情况
int n,P[maxn],hashTable[maxn]={false};
void generateP(int index)
{
//当前处理的是第index位
//定义递归边界
if(index==n+1)
{
//P序列已经生成完毕,进行输出
for(int i=1;i<=n;i++)
cout<<P[i]<<' ';
cout<<endl;
return ;
}
else{
for(int x=1;x<=n;x++)
{
//从小的开始生成
if(hashTable[x]==false)
{
P[index]=x;
hashTable[x]=true;
generateP(index+1);
hashTable[x]=false;
}
}
}
}
int main()
{
cin>>n;
//从第1位开始生成
generateP(1);
return 0;
}
3、区间贪心
挑选尽可能多的不相交区间:按区间左端点从大到小排列,左端点相同按右端点从小到大排列。
区间选点:N个闭区间,挑选最少的点,使每个区间内至少有一个点:按区间左端点从大到小排列,挑选该区间的左端点。
4、二分法
使用mid=left+(right-left)/2
来避免溢出。
朴素二分: 二分查找 序列中是否存在xxx
while(left<=right)
{
if(A[mid]==x)
return left
else if(A[mid]>x)
right=mid-1
else
left=mid+1
}
return -1;
求有序序列(序列中存在重复元素)中第一个 大于/大于等于 x的元素位置:
//大于等于
while(left<right)
{
mid=(left+right)/2
if(A[mid>=x])
right=mid;
else
left=mid+1;
}
return left;
//大于
while(left<right)
{
mid=(left+right)/2
if(A[mid>x])
right=mid;
else
left=mid+1;
}
return left;
总结:
序列有序,为前半部分不符合条件,后半部分符合条件,二分查找第一个符合条件的:
while(left<right) // 退出循环条件是left==right,永远不可能left>right
如果当前mid符合条件 right=mid
如果不符合 left=mid+1
二分答案:
计算根号二的近似值,精确度1e(-5)
while(right-left > 1e(-5))
{
double mid=(left+right)/2
if(mid*mid>2)
right=mid;
else
left=mid;
}
return mid;
5、快速幂
二分思想,如果是偶数次幂,等于开方想乘,奇数次幂,等于指数等于 偶数次幂+1。
两种写法:递归写法和迭代写法
//递归
LL binaryPow(LL a,LL b,LL m)
{
//a的b次方模m
if(b==0) return 1;
if(b & 1) return a*binaryPow(a,b-1,m)%m; //b&1 用于替换b % 2 == 1
else
{
LL mul=binaryPow(a,b/2,m);
return mul*mul%m;
}
}
//迭代
LL binaryPow(LL a,LL b,LL m)
{
//a的b次方模m
LL ans=1;
while(b>0)
{
if(b & 1)
ans=ans*a;
a=a*a;
b >>= 1;
}
return ans%m;
}
6、双指针
解决 有序序列中是否存在 两数之和为x(收尾两指针向中间逼近);有序序列合并问题。
归并排序:
const int maxn=100;
//归并排序的递归、非递归写法 2021.1.27
//递归
void merge(int A[],int L1,int R1,int L2,int R2)
{
//将数组的[L1,R1],[L2,R2]合并为有序区间
int i=L1,j=L2;
int temp[maxn];
int 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)
{
int mid = (left+right) /2;
mergeSort(A,left,mid);
mergeSort(A,mid+1,right);
merge(A,left,mid,mid+1,right);
}
}
//非递归
void mergeSort(int A[],int n)
{
for(int step=2;step/2<=n; step*=2)
{
//以step个元素为一组,前step/2个元素和后step/2个元素进行合并
for(int i=1;i<=n;i+=step)
{
int mid =i+step/2-1;
if(mid+1<=n)
{
//特判后半段是否还有元素
merge(A,i,mid,mid+1,min(i+step-1,n));
}
。 }
}
}
快速排序:
const int maxn=100;
//快速排序 2021.1.27
int Partition(int A[],int left,int right)
{
//返回A[1]所在的数组位置,即产生一个划分,是该划分左边的元素均小于A[1],右边的元素军大于A[1]
int temp=A[left];
while(left<right)
{
while(left<right && A[right]>temp) right--;
A[left]=A[right];
while(left<right && A[left]<temp) left++;
A[right]=A[left];
}
A[left]=temp;
return left;
}
void quickSort(int A[],int left,int right)
{
if(left < right)
{
int pos = Partition(A,left,pos-1);
quickSort(A,left,pos-1);
quickSort(A,pos+1,right);
}
}
7、随机选择算法
从一个无需数组中求出第k大的数。排序求出–O(nlogn),随机选择法–期望时间可达到O(n)
int randPartition(int A[],int left,int right)
{
int p=(0.5+(1.0 * rand()/RAND_MAX * (right-left)+left));
swap(A[p],A[left]);
int temp=A[left];
while(left<right)
{
while(left<right && A[right]>temp) right--;
A[left]=A[right];
while(left<right && A[left]<temp) left++;
A[right]=A[left];
}
A[left]=temp;
return left;
}
int randSelect(int A[],int left,int right, int k)
{
//在A数组【left,right】中选择第k大的元素
if(left==right) return A[left]; // 递归边界
int p=randPartition(A,left,right);
int M=p-left+1; //A[p]在【left,right】中是第M大的
if(k==M) return A[p];
if(k<M)
return randSelect(A,left,p-1,k);
else
return randSelect(A,p+1,right,k-M);
}
五、入门篇-数学问题
1、最大公约数与最小公倍数
最大公约数 欧几里得算法: gcd(a,b)gcd(b,a%b) 即辗转相除法
最小公倍数:求a与b的最小公倍数,先求a与b的最大公约数c,然后a*b/c
为了避免溢出,经常采用(a/c)*b
2、分数的表示和化简
3、素数筛法
首先确定2是素数,然后对每一个素数,筛掉它所有的倍数。然后往后遍历,如果当前数没有被筛掉,就一定为素数。
4、质因子分解
对于任意正整数n,如果它存在[2,n]范围内的质因子,则只存在两种可能:要么全部质因子都小于sqrt(n),要么只有一个是大于sqrt(n),而其余全部小于等于sqrt(n)。
5、关于组合数
求n!有多少个质因子p
定理,n!中有
int cal(int n,int p)
{
int ans=0;
while(n)
{
ans+=n/p;
n=n/p;
}
return ans;
}
变形:求n!末尾有多少个0,实质上cal( 10,5 )
组合数计算
采用定义式的变形可以达到O(m)的复杂度。
long long C(long long n,long long m)
{
long long ans=1;
for(long long i=1;i<=m;i++)
ans=ans*(n-m+i)/i;
}
六、C++标准模版库 STL 略
1、set
内部自动有序排列,且会自动去重。
需要注意的是,访问set中的的元素,只能通过迭代器进行访问,不能通过下标。且在set的迭代器中,不允许直接与正整数进行直接运算。
函数:
insert(x)//插入x且去重 O(logn)
find(x) //返回值为x的元素的迭代器 O(logn)
erase(it) // it为要删除元素的迭代器 O(1)
erase(value) //删除值为value的元素
erase(furst,last) // 删除迭代器【first,last)区间内的元素
2、string
可以使用 +=拼接
直接用>=<组合来进行 两string的字典许比较
erase: