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]]++;
posted @ 2020-10-14 09:29  praying_cqf  阅读(321)  评论(0)    收藏  举报