ACM学习 进制转换 大数 复杂度分析 数据类型
1.进制转换
首先是短除法,n转k进制,每次除k取余作为最低位,就可以得到n在k进制下的数。
while (n) { a[++m]=n%k; n=n/k; }
倒序输出即可。
还是一种方法是预处理出k的幂,每次选择一个最大的小于等于n的k的幂k^x,让n减去b*k^x,b=n/(k^x),并且在第x+1位赋值为b,最后当n=0,就得到了n在k进制下的表示。
for (int i=x;n;i--) { if (n>=e[i]) { int b=n/e[i]; n=n-b*e[i]; a[i+1]=b; } }
这里x作为理论上存在的最高位,ei表示k^i。
2.大数相关
大数相关的算法有很多,这里主要指大数加法和大数乘法。
首先是大数加法,由于不会java,只能手写高精度。
代码非常简单。
for (int i=max(n1,n2);i>=1;i--) { s[i]=s1[i]+s2[i]+s[i]; int tmp=s[i]/10; s[i-1]=tmp; s[i]=s[i]/10; }
这里s1数组和s2数组存的是两个大数,s存的是结果,稍微改变一下可以适应更多更复杂的情况,比如变为k进制,或者使用压位,可以解决更大位数的大数加法。
其实就是一个模拟竖式计算的过程。
复杂度是O(max(len1,len2))
大数乘法也是大同小异的,模拟竖式计算。
for (int i=n1;i>=1;i--) for (int j=n2;j>=1;j--) { s[i+j-1]=s[i+j-1]+s[i]*s[j]; int tmp=s[i+j-1]/10; s[i+j-1]=s[i+j-1]%10; s[i+j]+=tmp; } }
不过以上两份代码都是不完整的,原因是他们没有继续往上进位,比如999+1=1000,最后在代码结尾加上判断就好。
乘法可以使用压位优化,以便处理答案位数太大的情况,比如求n!,位数一般比较可怕。
复杂度是O(len1*len2)
如果使用压位,复杂度会变成O(len1*len2/n^2),这里的n是你压的位数。
3.数据类型
常用的是int,long long,double,float,char。
int -2^31-1~2^31-1
unsigned int 就是0~2^32
long long 的大小可以理解为两个int相乘。
unsigned long long 同理。
double精度会高于float许多。
4.复杂度分析
这里是针对排序的复杂度分析。
首先是插入排序,你有一些已经排好序的数,现在需要插入一个数,那么该数从已经排好序的数中从头到尾找到自己的位置,插入。
代码如下
void ins(int x) { int i; for (i=head;x>=a[i];i=nex[i]); nd++; pre[nd]=pre[i]; nex[nd]=i; nex[pre[i]]=nd; pre[i]=nd; }
这里pre和nex表示前驱和后继,这里是将数从大到小排序。
用C++自带的链表应该会很方便,但是我不会,所以使用的是数组模拟链表。
这里复杂度是O(n^2)的,最坏情况下每次都需要遍历整个链表。
接下来是冒泡排序
代码如下
for (int i=1;i<n;i++) for (int j=i+1;j<=n;j++) if (a[i]>a[j]) swap(a[i],a[j]);
这里是从小到大排序。
复杂度是显然的,O(n^2)。
然后是快速排序。
C++代码
sort(a+1,a+n+1)
在默认情况下是从小到大排序,当然可以自定义排序顺序。
实际上大概代码如下
void ks(int l,int r) { int i=l,j=r,k=a[l+r>>1]; while (i<j) { while (a[i]<k) i++; while (a[j]>k) j--; if (i<=j) { swap(a[i],a[j]); i++; j--; } } if (j>l) ks(l,j); if (i<r) ks(i,r;) }
快速排序在大多是情况下都是O(nlogn)的。
归并排序。
本质是把两个已经有序的数组排序成一个数组,采用分治的思想,假如我要排序[l,r],我先排出[l,mid]和[mid+1,r],再将他们归并。
mid取(l+r)/2,这样一来总体复杂度就是O(nlogn),而且是稳定的,缺陷就是常数太大。
代码如下
void gb(int l,int r) { if (l==r) return; int mid=l+r>>1; gb(l,mid); gb(mid+1,r); int i=l,j=mid+1,k=l; while (i<=mid||j<=r) { while (i>mid&&j<=r) b[++k]=a[j++]; while (i<=mid&&j>r) b[++k]=a[i++]; if (i<=mid&&j<=r) { if (a[i]<a[j]) b[++k]=a[i++];else b[++k]=a[j++]; } } for (int i=l;i<=r;i++) a[i]=b[i]; }
堆排序
堆的本质是一棵二叉树,这棵二叉树上的节点的儿子节点代表的值都小于自身的值,这里的堆是小根堆,大根堆类似。
堆操作有两种,一种是取出,一种是插入。
每次将节点插入堆的最末尾,依次与根比较进行上升操作。
取出的话, 删除根节点,将末尾节点调到根节点位置,进行下沉即可。
代码如下
void ins(int x) { len++; d[len]=x; int i=len; while (d[i>>1]>d[i]&&i>1) { swap(d[i>>1],d[i]); i=i>>1; } } int get() { int ret=d[1]; d[1]=d[len]; len--; int i=1; while (i*2<=len) { int j; if (i*2+1>len||d[i<<1|1]>d[i<<1]) j=i<<1;else j=i<<1|1; if (d[i]>d[j]) swap(d[i],d[j]); i=j; } return ret; }
位运算一定程度上可以加快速度。
复杂度是O(nlogn)
计数排序(桶排序)
以元素的值作为下标存储在数组中,顺序遍历值域得到排序结果。
复杂度是O(n+A),A为值域大小。
代码如下
for (int i=1;i<=n;i++) t[a[i]]++;

浙公网安备 33010602011771号