大整数-乘法(一)


问题描述

本文主要给出大数乘法的一般思路。说明大数乘法的两种思路,并且结合两道题目给出对于大数和常规整数乘法代码优化的必要性。

思路

基本来说,还是大整数的那套思路。
要进行处理的数字,超过了计算机语言所能提供类型的最大范围。只能自己写数组存储每一位数字。由于不是内置类型,所以没有相应操作的支持。只能自己写,人工模拟减法操作。

但是对于大数乘法这一块,自己在看了点网上的资料之后,以为自己明白了大数乘法的思路,可以做题了就没有进一步思考。直到这两天重新看的时候才发现了之前没有想到的事情。

我们平常所说的大数乘法是大数和常规整数的乘法,这一点是我之前没有仔细考虑的。其实也是显而易见的事情。但是他的算法并不是严格按照按位操作去实现的。所以,当我们说大数乘法的时候,应该是两种。一种是大数和普通整数的乘法,一种是大数和大数的乘法。当然前者也可以完全用后者来实现,只不过对于前者而言,有更简单的办法。

大数和普通整数的乘法

算法思路:被乘数的每一位乘以乘数,然后得到相应位的值以及进位。这就要求被乘数是大数,而乘数则不能是大数。也是也人工模拟的办法,按位去进行计算,只不过但是对于乘数而言,它的计算并不是按位去计算的。
但是我们知道,传统的乘法计算,被乘数和乘数都是按位去进行计算的。

先看一道简单的题目: [ jobdu-1067 ]

题目描述:
输入一个整数n,输出n的阶乘
输入:
一个整数n(1<=n<=20)
输出:
n的阶乘
样例输入:
3
样例输出:
6

考虑到n的最大值就是20,所以数组的大小分配1024没有问题。
代码如下:

/*
input:
n-乘数

process:
    1.data初始化为1,小端机存储
    2.循环:2 - n次,每一次循环应该做的是用大数的每一位乘以 乘数,获取当前位的值和进位
        2.1.循环:遍历大数的每一位, c = 0
            2.1.1.用当前位的值 * i
            2.1.2.计算当前位的值
            2.1.3.计算进位
    3.忽略前导0
    4.按位输出
output:
*/
#include <iostream>
#include <cstring>
#include <fstream>
//#define LOCAL

const int maxn = 1024;
int data[ maxn ];

void bign_mul( int n );

int main()
{
#ifdef LOCAL
    std::ifstream cin;
    cin.open("input.dat");
#endif
    int n = 0;
    while( std::cin >> n )
    {
        bign_mul(n);
    }
#ifdef LOCAL
    cin.close();
#endif
    return 0;
}

void bign_mul( int n )
{
    memset( data, 0, sizeof(data) );
    data[0] = 1;

    for( int i = 2; i <= n; ++i )
    {
        int c = 0;
        for( int j = 0; j < maxn; ++j )
        {
            int tmp = data[j] * i + c ;
            data[j] = tmp % 10;
            c = tmp / 10;
        }
    }

    int i = 0;
    for( i = maxn - 1; !data[i]; --i );
    for( int j = i; j >= 0; --j )
        std::cout << data[j];
    std::cout << std::endl;
}

我们下面再看一道题目: [ jobdu-1076 ]

题目描述:
输入一个正整数N,输出N的阶乘。
输入:
正整数N(0<=N<=1000)
输出:
输入可能包括多组数据,对于每一组输入数据,输出N的阶乘
样例输入:
4
5
15
样例输出:
24
120
1307674368000

分析:还是同一个问题,只不过范围变了下。现在阶乘最大值可以取到1000。对于上面的代码。只需修改下maxn即可。
但是上面的代码,无论如何都是过不了的。因为这个题目最小的maxn是2566,但是上面的代码在maxn=2562的时候就已经TLE。说明上面的代码在时间上面需要优化。至于上面的2566和2562这两个值是怎么得到的,就是不断试出来的。

其实这个题做的时候也比较幸运,因为我直接改了优化的版本去做的。前两次一直WA,因为数组空间开辟较小,只获得了部分数据值。将数组空间改大即可。优化的思路也很简单,每次做乘法的时候不要枚举data数组的所有位,因为不是所有位是有效位。所以增加一个控制有效位长度的变量即可。
代码如下:

#include <iostream>
#include <cstring>
#include <fstream>
//#define LOCAL

const int maxn = 10000;

int data[maxn];

void bign_mul( int n );

int main()
{
#ifdef LOCAL
    std::ifstream cin;
    cin.open( "input.dat" );
#endif
    int n = 0;
    while( std::cin >> n )
    {
        bign_mul(n);
    }
#ifdef LOCAL
    cin.close();
#endif
    return 0;
}

void bign_mul( int n )
{
    std::memset( data, 0, sizeof(data) );
    data[0] = 1;
    int len = 1; // 有效位长度

    for( int i = 2; i <= n; ++i )
    {
        int c = 0;
        for( int j = 0; j < maxn && ( j < len || c ) ; ++j )
        {
            int tmp = data[j] * i + c;
            data[j] = tmp % 10;
            c = tmp / 10;

            if( j >= len )
                ++len;
        }
    }

    for( int i = len - 1; i >= 0; --i ) 
        std::cout << data[i];
    std::cout << std::endl;
}

总结

其实在做acm题的时候给我比较深的感觉就是对于程序鲁棒性的要求,也就是你的程序仅仅是”看起来正确”是远远不够的,必须要做到100%的正确才行。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值