初级算法——模拟与高精度(C语言)

在进行算法题训练时,其中包含的算法类型数不胜数,非常让人头痛的就是数据结构一类的知识,对于我这个普通人来说,不仅概念难,应用于算法也难,简直寸步难行,为了以后的考试以及面试成功率,还有今后要想在计算机行业站稳脚跟,我决定从头开始进行逐步搭建思想以及算法能力,就从模拟与高精度开始:

一.高精度:

按我的理解来说,高精度主要是将int 型,float型,double型等表示不出来的过大的值存储在数组中,通过十进制的方法将数字一个个拆分出来,然后进行相对应的层层叠加,最后得到想要的结果,其中包含数字运算,还包含对于字符串的整体应用以及各种操作。例如洛谷上最常见也是最基础的A+B高精度问题以及A*B高精度问题。

首先是A+B问题,首先先定义两个z字符数组a[1000], b[1000] ,将两个数字分别存储到相对应的数组中,在定义两个另外三个整型数组a1[1000],b1[1000], c[1000],先用strlen函数进行对a,b两个字符数组的长度获取从而实现对两个数字的输入,然后将a[1000]和b[1000]的字符转化为数字存储到a1[1000], b1[1000]中,去两者中最大的一个长度作为c[1000]的数组总长度,利用for循环将c[i] =  a1[i] + b1[i]; c[i + 1]  = c[ i ] /  10; c[i]  = c[i] % 10;从而等到对应的数,但此番得到的数组与所求刚好是反的,所以应该在最后进行逆序处理,但在逆序之前还要将c的长度进行去除前导零的操作以防万一,代码如下:

#include<stdio.h>
#include<string.h>
#define MAX 1000 //其实一个n 位数和 一个m位数相加后的数,最大位数不过也只是n和m中最的的数+1;

char a[MAX];//a和b字符串数组用来暂存输入的大整数
char b[MAX];
int a1[MAX];//a1和b1用来存将字符串中的字符数字转换成整型后的数字。
int b1[MAX];
int c[MAX];//c数组用来存二者相加之和
int main(){
    scanf("%s\n%s", a, b);
    int alen = strlen(a);
    int blen = strlen(b);
    int k = alen > blen ? alen : blen;//k获得二者之间最大位数
    for (int i = 0; i < alen; i++) {
        a1[alen - 1 - i] = a[i] - 48;//倒着存入整型数组中,使得整型数组中的第一位是个位。因为相加都是从个位数开始
    }
    for (int i = 0; i < blen; i++) {
        b1[blen - 1 - i] = b[i] - 48;
    }
    k += 1;
    for (int i = 0; i < k; i++) {
        c[i]+= a1[i] + b1[i];//之所以是+=,是因为,上一步的进位数已经存到c[i]中;
        c[i + 1] = c[i] / 10;//大于等于10的进位到下一位
        c[i] %= 10;//每一位应该都是小于10的数字
    }
    while (c[k] == 0 &&k >= 1)k--;//去除前导0

    for (int i = k; i >= 0; i--)printf("%d", c[i]);//倒着输出,因为当初相加的时候是从个位开始
    
    return 0;
}

接下来就是关于A*B的问题,整体思路差不多,但是它不能像上面那样直接将字符转化为数字,这样的结果大至一定值时会出现很严重的偏差,最好使用一个函数进行反转的操作。还有c[1000]中的代码除了基础架构不变,中间的总体还要进一步的更改,代码如下:

#include <stdio.h>
#include <string.h>

//翻转函数
void reverse (int d[1000], int len) {
    int temp, mid = len/2;
    for (int i = 0, j = len - 1; i < mid, j >= mid; i++, j--) {
        temp = d[i];
        d[i] = d[j];
        d[j] = temp;
    }
}

int main () {
    char s1[1000], s2[1000];
    int a[1000], b[1000], c[1000] = {0};
    scanf("%s", s1);
    scanf("%s", s2);
    int lena = strlen(s1);
    int lenb = strlen(s2);
    int lenc = lena + lenb;
    
    //将char的1转为int的1
    for (int i = 0; i < lena; i++) {
        a[i] = s1[i] - '0';
    }
    for (int i = 0; i < lenb; i++) {
        b[i] = s2[i] - '0';
    }
    
    reverse(a, lena);
    reverse(b, lenb);
    
    //核心代码
    for (int i = 0; i < lenb; i++) {
        for (int j = 0; j < lena; j++) {
            c[i+j] += b[i] * a[j];
            c[i+j+1] += c[i+j] / 10;
            c[i+j] = c[i+j] % 10;
        }
    }
    
    //处理前导0
    lenc = lenc - 1;
    while (c[lenc] == 0 && lenc >= 1) {
        lenc--;
    }
    
    //输出
    for (int i = lenc; i >= 0; i--) {
        printf("%d", c[i]);
    }
    
    return 0;
}
 

另外关于高精度算法的进一步应用中洛谷上麦森数是一个很好的例子,他首先给的 2^P−1以及P的取值范围就非常的大,最后要求的位数以及最后500位数字也是看起来非常令我震惊,带究其根本的确是关于高精度算法的 。首先,在数学公式上,有近似2^p = 10^n + a  (a<10),n = log10(2^p-a)=log10(2^p)=p*log(2)输出的位数由math.h中的log10函数计算p = a(0)*2^0 + a(1)*2^1 +....+ 2^n (其中a(n)是p的各二进制位)2^p = 2^(a(0)*2^0) * ... * 2^(2^n)为了避免超时,大整数以10000进制存储(这是在网上找的,本人哪有这个脑子能想到这个公式),这其中还引用到了关于A*B问题中关于c[1000]中的核心代码,麦森数代码如下:

#include <stdio.h>
#include <math.h>
#include <string.h>
void mul(int* a, int* b)//高精度  A*B Problem
{
    int i, j, res[125];
    memset(res, 0, sizeof(res));//初始化数组  res暂存a*b结果
    for (i = 0; i < 125; i++)
        for (j = 0; j < 125 - i; j++)
        {
            res[i + j] += a[i] * b[j];
            res[i + j + 1] += res[i + j] / 10000;
            res[i + j] %= 10000;
        }
    memcpy(a, res, sizeof(res));//把结果从res赋值到a数组中
}
int p, result[125], base[125]/**用来存放2^(2^n)**/;  //result是存结果的,p是指数,base是底数
int main()
{
    int i;
    scanf("%d", &p);
    printf("%d\n", (int)(p * log10(2.0)) + 1);//+1是因为n = log10(2^p-a)=log10(2^p)=p*log(2) 时省去了a,如3*10^4 4+1位数
    result[0] = 1;//存结果
    base[0] = 2;//存底数,起初是2
    while (p)//高精度快速幂 最优版
    {
        if (p & 1)//相当于p%2==1  如果是奇数次幂,如2^5 
            mul(result, base); //高精度result = result * base ;  单独运算  result*底数^1 即resule*2^1                
        p >>= 1;//指数右移实现指数折半,p/=2;
        mul(base, base); //base = (base * base) ; 底数变大成原来的平方 mul是高精度乘法函数
    }
    result[0]--;
    //result一共125位,每一位都是四位数,125*4=500
    for (i = 124; i >= 0; i--)
        if (i % 25 == 12)//
            printf("%02d\n%02d", result[i] / 100, result[i] % 100);
        else
        {
            printf("%04d", result[i]);
            if (i % 25 == 0)printf("\n");//要求输出500位,每行五十位
        }
    return 0;
}

虽然以我现在的示例这种层级的算法题只有查看答案的份,但我想只要答题思路会了,总有灵光乍现的一天(奢望啊)。

二.模拟算法

这个算法按我的理解就是包含在题目给的较长甚至超级长的的那一种,就我做的几道题来看,此种算法与if…else语句以及三目运算符有着密切联系,不管是简单的条件还是复杂条件都或多或少有着他俩的影子。怎么说呢,这题型简单是可以了上天,难时有可能来题目的条件都读不懂(只针对我这种笨蛋)!

先来看简单的,是洛谷上的扫雷游戏,不做过多阐述,代码如下:

#include<stdio.h>
char a[101][101];

int main(){
    
    int n,m;
    //scanf("%d %d\n",&n, &m);
    scanf("%d%d", &n,&m);
    getchar();
    for(int i=1;i<=n;i++){
        //TODO
        for(int j=1;j<=m;j++){
            scanf("%c",&a[i][j]);
        }
        getchar();//吞掉换行
        
    }
    for (int i = 1; i <= n; i++) {//下面循环扫一遍即可
        for (int j = 1; j <= m; j++) {

            if (a[i][j] == '*') {//如果是雷,相应的位置b[i][j]赋值为'*'
                printf("*");
            }else{            //如果不是雷,判断其八个方位即可
                int count = 0;
                if (a[i][j - 1] == '*')count++;
                if (a[i][j + 1] == '*')count++;
                if (a[i + 1][j - 1] == '*')count++;
                if (a[i - 1][j + 1] == '*')count++;
                if (a[i + 1][j] == '*')count++;
                if (a[i - 1][j] == '*')count++;
                if (a[i + 1][j + 1] == '*')count++;
                if (a[i - 1][j - 1] == '*')count++;
                printf("%c",count+'0');
            }

            
        }
        //遍历完一行要换行
        printf("\n");
    }//边扫边输出即可
    
}
 

再来看难的,是洛谷上的玩具谜题,思路如下(异或思想):

         对于指令顺序:将0看作向左,将1看作向右

         对于玩偶朝向:将0看作朝内,左边顺时针转,右边逆时针转;将1看作朝外,左边逆时针转,右边顺时针转。

        代码如下:

#include<stdio.h>
struct toy{
    char name[10];
    int  j;
};//每个玩偶的名字和朝向 
int main()
{
    int n,m;
    scanf("%d %d",&n,&m);
    int b[m+1][3];//二维数组 
    struct toy a[n+1];//结构体数组 
    int i,j;
    for(i=1;i<n+1;i++)
    {
        scanf("%d %s",&a[i].j,&a[i].name);//0为朝内,1为朝外 
    }
    for(i=1;i<=m;i++)
    {
        scanf("%d %d",&b[i][1],&b[i][2]);/*b[i][1]为朝左朝右的判断,1为右,0为左:b[i][2]储存步数 */
    }
    int l;
    i=1;
        for(l=1;l<=m;l++)
        {
            if(a[i].j!=b[l][1])//用到了异或的思想 
            {
                i=i+b[l][2];//逆时针转 
            }else
            {
                i=i-b[l][2];//顺时针转 
            }
            if(i<1)//使为i为合适值 
            {
                i=n+i;
            }
            if(i>n)
            {
                i=i-n;
            }
        }
    printf("%s",a[i].name);//输出最终玩偶名字 
    return 0;
}

这种题一般代码多还算简单,但思路的整理确实有一定困难,懂思路了就做,不懂就是在很难。还有一个是关于洛谷上的字符串展开问题,题目有些长,但是思路很清晰,不太容易的地方就是代码逻辑的分配以及编写时容易出现的错误,总的来说还是有很多坑的,代码如下:

#include<stdio.h>
int main() {
    int p1, p2, p3, i = 0, j, k;
    char ch[300] = { 0 }, be, af, f, p;//p用于输出; 
    scanf("%d%d%d%s", &p1, &p2, &p3, ch);//输入;
    while (ch[i]) {//当ch[i]有值时;
        be = ch[i - 1]; af = ch[i + 1]; f = ch[i];//f存储ch[i],便于判断; 
        if (f == '-' && af > be && (be >= '0' && af <= '9' || be >= 'a' && af <= 'z')) {//意思是ch[i]若为'-',就判断其前后是否满足条件,满足进入循环; 
            for (p3 == 1 ? j = be + 1:( j = af - 1); p3 == 1 ? j < af : (j > be); p3 == 1 ? j++ : j--) {
                //此处三目运算符中表达式三需要括起来,否则会报错。
                p = j;//j是整形变量,p是字符型变量,这样是将p赋值为ASCII码为j的字符; 
                if (p1 == 2)//是否大写; 
                    p = (p >= 'a') ? p - 32 : p;//如果是字母就转成大写 
                else if (p1 == 3) p = '*';//是否输出'*' 
                for (k = 0; k < p2; k++)//输出p2个p
                    printf("%c", p);
            }
        }
        else
            printf("%c", f);//如果ch[i]是非'-'或者其前后不满足条件,就原样输出;
        i++;//一定要放在后面,不然会出错。
    }
    return 0;
}

说来惭愧,从刚开始进入大学接触计算机到现在我已经大一下了,C语言已经系统学完了,但是关于C语言的算法部分一直都只是偏下的水平,先不谈后面的一系列复杂的数据结构,光这些基础算法说实话理论都还可以,上算法题就不行了,说我刷题少吧,之前大一上的时候可谓是一天至少两道,可也是刚刚好在C语言算法考试上刚好及格罢了,放寒假一直在忙着做实验室下发的关于Arduino蓝牙避障小车的学习以及制作,还有今年要学习的Java语言学习,以及本人想在放假期间好好放松的心,让我在假期里对于算法方面的能力不进反而还退了一些,说真的,我很烦算法题,但是又不得不在这上面花功夫甚至要做到中等水平才能在如今竞争激烈的环境中生存下去。

加油,无论如何,今天的算法精进了一小步!

  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值