大数相关算法

在华为的机试的时候,碰到了大数的问题,所有的大数问题,无非就是相加相减相乘相除这四种,里面的区别就是大数的表示方式,一般我们采用字符数组的方式去保存大数,也有采用链表的。这两种方式中,字符数组的方式比较好理解一些,本文通过代码,来说明大数运算的集中情况,力争将这个问题讲清楚。

大数相乘

题目描述:输入两个不超过100位的大整数的乘积。

输入:1234567 123

输出:151851741

方法一

下面说明一下代码的思路,当两数相乘的时候,一般我们都是从被乘数的个位开始的,于是就有了以下代码,首先是按照顺序用被乘数的个位乘以乘数,这个过程中,遇到进位的时候需要保存下来,也就是carry;而得到的结果我们会存放在我们的tempRes中。

for(i = num1Len - 1; i >= 0;i--)
 {
      res = Int(num1[i])*Int(num2[j]) + carry;
      tempRes[tempResLen--] = Char(res % 10);
      carry = res / 10; 
}

由于上面最后一次循环没有加上进位,所以在后面还是需要加上进位位的,并且需要将tempResLen这个值和carry初始化。

tempRes[tempResLen] = Char(carry);
tempResLen = num1Len;
carry = 0;

然后我们需要将最终结果保存在result中,并且由于每次被乘数没有移位,所以我们在保存结果的时候,每次循环需要移位,所以每次offset都会加上1。其实这个处理方式和上面的一样,只是这个是保存在最终结果中。

for(k = resultLen - offset; k > (resultLen - offset - num1Len); k--)
{
       res = Int(result[k]) + Int(tempRes[tempResLen--]) + carry;
       result[k] = Char(res % 10);
       carry = res / 10;
}
result[k] += Int(tempRes[tempResLen] + carry);
carry = 0;
tempResLen = num1Len;
offset++;

完整代码如下。

#include
#include
#include

#define Int(X) (X - '0')  //将字符转化为整型
#define Char(X) (X + '0')  //将整型转化位字符

char *multiBigInteger(const char *,const char *);
int checkNum(const char *);

int main(void)
{
    char num1[100] = {'\0'},num2[100] = {'\0'};
    while(scanf("%s %s",num1,num2) != EOF)
    {
        char *result = "0";
        if(strlen(num1) > 100 || strlen(num2) > 100)
        {
            printf("ERROR!\n");
            return 1;
        }
        if(checkNum(num1) || checkNum(num2))
        {
            printf("ERROR:input must be an Integer!\n");
            return 1;
        }
        printf("num1 = %s\nnum2 = %s\n",num1,num2);
        result = multiBigInteger(num1,num2);
        if(result[0] == '0')
        {
            int i;
            printf("result:\t");
            for(i = 1; (size_t)i < strlen(result); i++)
            {
                printf("%c",result[i]);
            }
            printf("\n");
        }
        else
        {
            printf("result:\t%s\n",result);
        }
        printf("\n");
    }
    return 0;
} 

int checkNum(const char *num)
{
    int i;
    for(i = 0; (size_t)i < strlen(num); i++)
    {
        if(num[i] < '0' || num[i] > '9')
        {
            return 1;
        }
    }
    return 0;
}

char *multiBigInteger(const char *num1,const char *num2)
{
    char *tempRes = NULL;   //用来保存每次相乘的结果
    char *result = NULL;    //用来保存最终结果
    int tempResLen;         //每次相乘结果的最大长度
    int num1Len = strlen(num1);
    int num2Len = strlen(num2);
    int i,j,k;
    int res;         //每次一位相乘/相加的结果
    int carry = 0;   //进位
    int offset = 0; //加法的偏移位
    int resultLen = num1Len + num2Len - 1; //结果的最大长度
    tempResLen = num1Len;

    result = (char *)malloc((resultLen + 2)*sizeof(char));   //初始化result为0
    memset(result,'0',(resultLen+1)*sizeof(char));
    result[resultLen + 1] = 0;

    tempRes = (char *)malloc((tempResLen+2)*sizeof(char));
    for(j = num2Len - 1; j >= 0; j--)
    {
        //初始化tempRes每位为0
        memset(tempRes, '0',(tempResLen + 1)*sizeof(char));
        for(i = num1Len - 1; i >= 0;i--)
        {
            //计算num1与num2各位相乘的结果,结果保存到res
            res = Int(num1[i])*Int(num2[j]) + carry;
            //将res的个位保存到tempRes中
            tempRes[tempResLen--] = Char(res % 10);
            //将进位位保存在carry中
            carry = res / 10; 
        }
        //tempRes第一位为进位,最后将进位加上
        tempRes[tempResLen] = Char(carry);
        tempResLen = num1Len;
        carry = 0;
        //由result的末尾开始计算和,算完一次,向左偏移一位
        for(k = resultLen - offset; k > (resultLen - offset - num1Len); k--)
        {
            res = Int(result[k]) + Int(tempRes[tempResLen--]) + carry;
            result[k] = Char(res % 10);
            carry = res / 10;
        }
        result[k] += Int(tempRes[tempResLen] + carry);
        carry = 0;
        tempResLen = num1Len;
        offset++;
    }
    printf("num1len:%d\nnum2len:%d\n",num1Len,num2Len);
    return result;
}

方法二

#include
#include
#include

#define Int(X) (X - '0')

int *multiBigInteger(const char *,const char *);
int checkNum(const char *);
char *checkZero(char *);

int main(void)
{
    char num1[100] = {'\0'},num2[100] = {'\0'};
    printf("please input two number(less than 100 digits):\n");
    while(scanf("%s %s",num1,num2) != EOF)
    {
        int *result = NULL;
        int i,change = 0;
     
        if(strlen(num1) > 100 || strlen(num2) > 100)
        {
             printf("per number must less than 100 digits\n");
             return 1;
        }

        if(checkNum(num1) || checkNum(num2))
        {
            printf("Error:input must be an Integer\n");
            return 1;
        }

        printf("num1:\t%s\nnum2:\t%s\n",num1,num2);

        checkZero(num1);
        checkZero(num2);

        result = multiBigInteger(num1,num2);

	//输出结果result,result[0]保存着result的长度,所以从下标1开始
        printf("result:\t");
        for(i = 1; i <= result[0]; i++)
        {
            //用于去除前面的0,第一位如果是0,就跳过不输出
	    if(result[i] != 0)
		change = 1;
	    if(!change)
	    {
		if(i > 1)  //用于判断结果是否为0
                {
		    printf("0");  //如果第二位还是0,就判断为0
                    break;
		}
	        continue;
	    }
	    printf("%d",result[i]);
        }
	printf("\n");
	printf("\nplease input two number(less than 100 digits):\n");
    }
    return 0;
}

//用于去除输入数中前面的0
char *checkZero(char *a)
{
     int i,j=0;
     for(i = 0;a[i] != '\0';)
     {
	if(Int(a[i]) == 0)
        {
	    i++;
	    continue;
        }
	else if(Int(a[i]) != 0)
	    break;
     }
     while(a[i] != '\0')
	a[j++] = a[i++];
     a[j] = '\0';
     return a;
}

//用于检测输入的是否为数字,如果是就返回0,不是就返回1
int checkNum(const char *num)
{
    int i;
    for(i = 0; (size_t)i < strlen(num); i++)
    {
	if(num[i] < '0' || num[i] > '9')
	    return 1;
    }
    return 0;
}

int *multiBigInteger(const char *num1,const char *num2)
{
    int *result = NULL;
    int num1Len = strlen(num1);
    int num2Len = strlen(num2);
    int resultLen;
    int i,j;
    resultLen = num1Len + num2Len;
    
    result = (int *)malloc((resultLen+1)*sizeof(int));
    memset(result,0,(resultLen+1)*sizeof(int));

    result[0] = resultLen;
    //result的第一位用于保存result的长度

    /*
     *num1和num2相乘,这个算法不采用先进位的方式,所以算法
     *是从左到右按顺序来乘,然后将结果保存在result的每一位
     *中,循环一次result就从下一位开始求和。
     */
    for(j = 0; j < num2Len; j++)
    {
	for(i = 0; i < num1Len; i++)
        {
	    /*
	     *result第一位是用来保存result长度的,而第二位是保存
	     *结果最后的进位位的。如果没有进位,则为0,所以相乘之
	     *和是从第三位开始的。
	     */
	    result[i+j+2] += Int(num1[i]) * Int(num2[j]);
	}
    }
    
    /*
     *这个循环用来处理进位的,所以要从result的最后一位一直处理到
     *首位。要注意result的总长度是resultLen+1,有一位是保存result
     *的长度,而C语言的下标是从0 开始的,所以result的最后一位下标
     *是resultLen,而第一位是1
     */
    for(i = resultLen; i > 1; i--)
    {
	result[i - 1] += result[i]/10;
	result[i] = result[i] % 10;
    }
    printf("num1Len:%d\nnum2Len:%d\n",num1Len,num2Len);
    return result;
}

大数相加

题目描述:输入n个不超过100位的大整数的乘积。

输入:

3

12345

67890

456789

输出:537024

方法一

这个结果方式比较简单,按位相加,注意进位即可!

#include
#include

#define MAX 100

char *BigDataAdd(char *s1,char *s2)
{
    if(s1 == NULL || s2 == NULL)
        return NULL;

    int len1 = strlen(s1);
    int len2 = strlen(s2);
    int Maxlen = (len1>len2)?len1:len2;

    int i,k;
    for(i = len1-1,k=Maxlen;i >= 0;i--,k--)
        s1[k] = s1[i] - '0';
    if(k >= 0)
        memset(s1,0,(k+1)*sizeof(char));
    
    for(i = len2-1,k=Maxlen; i >= 0; i--,k--)
        s2[k] = s2[i] - '0';
    if(k>=0)
        memset(s2,0,(k+1)*sizeof(char));

    for(i = Maxlen;i >= 1; i--)
    {
        s1[i] += s2[i];
        if(s1[i] >= 10)
        {
            s1[i] -= 10;
            s1[i-1] += 1;
        }
    }

    if(s1[0] == 0)
    {
        for(i = 1;i <= Maxlen; i++)
            s1[i-1] = s1[i] + '0';
        s1[i-1] = '\0';    
    }
    else
    {
        for(i=0; i<=Maxlen; i++)
            s1[i] = s1[i] + '0';
        s1[i] = '\0';
    }
    return s1;
}

char *input(int n,char *temp)
{
    char s1[MAX];
    if(n == 0)
    {
        printf("%s\n",temp);
        return temp;
    }
    else
    {
        scanf("%s",s1);
        BigDataAdd(temp,s1);
        input(n-1,temp);
    }
}

int main()
{
    int n;
    char temp[MAX + 5] = {'\0'};
    scanf("%d",&n);
    input(n,temp);
    return 0;
}

方法二

将输入字符翻转相加,然后结果再次翻转,注意,该算法是利用Java实现的。

private static String add(String str1,String str2)
{
	//任何一个字符串为空,都不需要继续运算了。
	if(str1 == null || "".equals(str1))
		return str2;
	if(str2 == null || "".equals(str2))
		return str1;
		
	int maxlen = Math.max(str1.length(), str2.length());
	//定义一个存储结果的字符串
	StringBuffer result = new StringBuffer(maxlen + 1);
	//对输入的两个字符串进行翻转
	str1 = new StringBuffer(str1).reverse().toString();
	str2 = new StringBuffer(str2).reverse().toString();
		
	int minlen = Math.min(str1.length(), str2.length());
	int carry = 0;
	int currentNum = 0;
	int i = 0;
		
	for(;i < minlen; i++)
	{
		//分别获取两个字符对应的字面数值,然后相加,再加上进位
		currentNum = str1.charAt(i) + str2.charAt(i) - 2*'0' + carry;
		//获取进位值
		carry = currentNum / 10;
		//处理当前位的最终值
		currentNum %= 10;
		//保存当前位的值到最终的字符缓冲区中
		result.append(String.valueOf(currentNum));
	}
	//找到长度大的那个数
	if(str1.length() < str2.length())
		str1 = str2;
	//对长度大的数剩余的位进行处理,处理方式参考上面的过程
	for(;i < str1.length(); i++)
	{
		currentNum = str1.charAt(i) - '0' + carry;
		carry = currentNum / 10;
		currentNum %= 10;
		result.append(String.valueOf(currentNum));
	}
		
	//处理最后一个进位
	if(carry > 0)
		result.append(String.valueOf(carry));
	//最后翻转恢复字符串,返回结果
	return result.reverse().toString();
}

参考文章:

https://www.cnblogs.com/king-ding/p/bigIntegerMul.html

你可能感兴趣的:(数据结构与算法)