`
frank-liu
  • 浏览: 1682612 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

大整数求余数的问题分析

 
阅读更多

问题描述

        最近在学习一些资料的时候正好看到一些和大整数求余数相关的问题,这些问题粗粗看来似乎有点麻烦。但是当结合一些有关数学的特性来分析时,会觉得很有意思。

问题1: 求一个整数X的N次方除以某个整数P的余数。用数学公式表示则如下:

 

其中N >= 0, P > 0.  

        这个问题需要考虑的就是如果N比较大的时候,很可能就超出我们所用一般数据类型所能表示的范围。如果直接去求X的N次方,就算有数据能保存的下来,肯定也会消耗大量的时间和空间。

 

问题2: 给定一个很大的数,求它除以某个整数P的余数。这个数因为足够大到没办法用普通的数据类型来表示,所以需要用一个整数类型的数组或字符串来保存。结果也是要求X mod P

 

问题1分析

最初分析

        我们先来看第一个问题。这个问题假定X和P并不是太大,可以用一个计算机的常用数据类型来表示。一种最简单直白的方法就是我们直接将所有N个X相乘,然后再对被除数P相除,求余数。当然,这是基于一个前提,我们有能够保存足够大的数据类型。如果我们对这种思路的时间和空间复杂度做一个粗略的估计的话,会发现,假设X是int类型的整数,占4个字节,而最坏的情况就是每次相乘的结果就占用的结果增加4个字节,这样N次乘积就需要占用4N字节的空间。而如果算上每次相乘的中间结果,占用的空间就达到N*N的量级。再看时间复杂度,假定两个int类型的整数乘积的运算时间单位为1的话,在没有任何优化假定的前提下,一个32位整数和64位整数乘积的时间则为原来的两倍。如果以这个标准来分析的话,后面的时间复杂度也到了N*N量级。

第一步改进

        可见,虽然前面这种办法虽然理论上可行,但是实际上时间和空间复杂度太大,不太合适。现在我们再来看看另外一种思路。因为问题的关键就是指数N比较大,如果能将指数能够降下来,将其转换成对等的表达式,则问题就好解决了。我们看前面求乘积的过程,假定是最简单的情况,N =2,则相当于求(X * X) mod D. 如果利用整数求余数的性质,我们发现他们满足下面的性质:

     这个等式的证明可以参照相关的数学材料或者文章后面的补充证明部分。通过这个性质,至少我们可以发现,对于两个数的乘积求余数,我们可以先求一个数的余数,然后再将这个余数乘以另外一个数再求余数。这样就可以求出来两个数乘积的余数。那么,如果对于3个,4个甚至更多的数的乘积求余数呢?我们可以将这个等式扩展一下,对于3个数的乘积,我们可以先求出前面两个数乘积的余数,再和第三个数相乘求。依次类推,重复N次就可以求出N次方的结果。于是,基于这种思路,我们可以写出如下的代码:

 

public static long power(long x, long n, long p)
{
    if(n == 0)
        return 1;

    long tmp = x % p;
    for(long i = 0; i < n - 1 ; i++)
    {
         tmp = (x * tmp) % p;
    }
    return tmp;
}

        这种方法和前面的思路比起来,有一个进步的地方,就是每次运算的时候都对中间结果求模运算,使得结果都在普通数据类型可以保存的范围内,这样不会需要额外的存储空间。而时间的复杂度主要取决于运算的指数,所以时间复杂度为o(N)。这样我们就找到了一个还不错的解决方法了。

再进一步考虑

    前面的办法虽然是已经在一个o(N)的范围了,可是如果N很大的话,我们还是要做一个很大的循环运算。还有没有可能使得我们的方法更加有效率呢?我们求X的N次方,可以根据N的性质做如下的分析:

当N为偶数的情况下:

那么,就有如下的等式成立:

后面这一部分的等式成立是基于模运算的这么一个特性:

当N为奇数的情况下,则有:

 那么,结合前面讨论的公式,对奇数情况下求模,则结果为如下等式:

综合前面的两种情况,我们可以发现,当N为偶数时,我们可以求X的平方再取模,如果是奇数的话则要再乘以X,然后取模。这样,一次运算下来,我们就将指数N折半了。按照这个过程,整个过程的时间复杂度可以降低到对数的级别上来。

 

根据讨论的递归关系,我们可以得出如下递归方式的代码:

public static long power(long x, long n, long p)
{
	if(n == 0)
		return 1;

	long tmp = power((x * x) % p, n / 2, p);

	if(n % 2 != 0)
		tmp = (tmp * x) % p;

	return tmp;
}
 

将递归版本转换成循环实现的方式的代码如下:

 

public static long loopPower(long x, long n, long p)
{
	x %= p;
	long tmp = 0;

	while(n > 0)
	{
		tmp = (x * x) % p;
		if(n % 2 != 0)
			tmp = (tmp * x) % p;
		n /= 2;
	}

	return tmp;
}

 

问题2分析

        结合前面的情况,因为问题2中本身需要求模运算的数字比较特殊,不是用一个普通的数据类型来保存,而是用的整型数组或者字符串数组。在这种情况下,我们需要考虑的是利用一些模运算的特性,使得整个运算的过程拆分成可运算的各个小的步骤。在这里,我们先假设是10进制的数字,比如说一个int数组[1, 2, 3, 4, 5, 6],那么他们实际对应的这个数字应该是如下:

这就相当于转换成了一个多项式求和的问题。对于一个整型的数组表示的长数据,我们按照多项式方式求和的典型代码如下:

 

 

public static long sum(int[] array)
{
    long sum = 0;
    for(int i = 1; i < array.length; i++)
    {
         sum = sum * 10 + array[i];
    }
    return sum;
}

    如果再结合一些模运算的性质来考虑,比如,对多个数字的相加再求模和先对中间部分结果求模再相加之后求模的结果是一样的。那么,我们可以得出一个通用的求模运算的方法:

 

public static long sum(int[] array, int base, int p)
{
    long sum = 0;
    for(int i = 1; i < array.length; i++)
    {
        sum = sum * base + array[i];
        sum %= p;
    }
    return sum;
}

      这里,base表示数的进制,可以是10以外的其他进制。

总结:

        前面针对大整数的两种情况进行了讨论,一种是给定一个整数,然后有一个比较大的指数,这种情况下需要考虑将指数变小给降下来。这里要利用到模运算里底数可以结合的特性。而针对一个很长的整数,我们可以将他转换成多项式求和的形式。这里就利用了多个数字求和取模和部分结果先取模再求和取模的结果一致这个特性。问题本身不是很复杂,主要是要把这几种特性想清楚,给用好了。总的来看,确实有点绕。

 

补充证明:

我们先来证明如下的等式成立:

因为X, Y都要对P求模,而实际上X, Y 都可以表示成 X = A*P + B 的形式,其中B就是X mod P的结果。那么前面的A*P这部分则是对于P可整除的。(X * Y) = (A * P + B)* Y = A * P * Y + B * Y

如果对这个等式的右边求模的话,显然A*P*Y这一部分是P的倍数,求模后的结果为0, 则结果为(B * Y) mod P, 前面我们知道 B = X mod P。这样我们就证明了(X * Y) mod P = [X(Y mod P)] mod P。后面这部分的证明可以类似推导出来。

 

我们再证明下面的等式成立:

按照前面一部分的讨论,我们可以假设X = A * P + B, 那么原来的等式则转化为:

    在右边的等式中,如果我们按照二项式定理将它展开,那么他们将成为很多项乘积的和。但是所有和A*P相乘的项都可以被P整除,那么这些项的结果最终都是0,只有不含有A*P的项对最后的结果有效。展开后唯一有效的部分就是B^N。而B本身就是X mod P。那么我们也就证明了上面等式的成立。

参考材料:

data structures and problem solving using java

  • 大小: 1.4 KB
  • 大小: 6.4 KB
  • 大小: 1.8 KB
  • 大小: 2.9 KB
  • 大小: 5.4 KB
  • 大小: 3 KB
  • 大小: 5.8 KB
  • 大小: 5.4 KB
  • 大小: 3 KB
分享到:
评论

相关推荐

    关于求余数问题的一个简单方法.doc

    ### 关于求余数问题的一个简单方法 #### 一、引言 在计算机科学与数学领域,求余数是一个常见的操作。特别是在算法设计、数据结构处理以及编程竞赛中,求余数的操作尤为重要。本文将详细介绍一种求余数的简单方法...

    C++大整数运算代码

    在C++编程语言中,处理大整数运算是一项挑战,因为标准库提供的`int`、`long`或`long long`等数据类型都有其存储和计算的限制。...通过阅读和分析代码,我们可以更好地掌握大整数运算的实现细节,并提高编程技巧。

    有余数的除法学情分析(作业).pdf

    不过,从标题可以推测,文档可能分析了在数学教学中,学生在学习带有余数的除法时所遇到的问题以及相应的教学策略。下面我将详细阐述有余数的除法相关知识点,以及在教学中可能遇到的挑战。 首先,有余数的除法是...

    C++实现的大整数四则运算

    在编程领域,大整数运算是一项重要的技术,特别是在处理金融计算、加密算法或者科学计算时。C++作为一门强类型且高效的语言,虽然标准库中并没有内置大整数支持,但可以通过自定义数据结构和算法来实现。这个项目...

    输入两个整数求最大公约数三种算法C语言

    欧几里得算法是计算最大公约数的经典方法,基于以下原理:对于任意两个正整数a和b(a&gt;b),它们的最大公约数等于a除以b的余数c与b之间的最大公约数。反复应用这个原理,直到余数为0,此时b即为最大公约数。 ```c ...

    求两个正整数m、n的最大公约数 Java语言实现

    欧几里得算法是一种高效求解最大公约数的方法,它基于以下原理:两个正整数a和b(假设a&gt;b),它们的最大公约数等于b和a对b取模后的余数的最大公约数。这个过程可以不断重复,直到其中一个数为0,此时另一个非零数即...

    大整数类利用c++实现完成了各项运算

    这个"大整数类利用C++实现完成了各项运算"的项目,显然为解决这一问题提供了自定义解决方案,特别适用于魔兽服务器的数据加密和通信研究,这两个领域通常需要处理大量的数据和安全操作。 大整数类的设计通常基于...

    自己实现超大整数加法运算

    通过阅读和分析这些源码,我们可以学习到如何在实际编程中处理超大整数问题,这对于在大数据处理、加密算法实现等领域工作的人来说是非常有价值的。 总的来说,掌握超大整数的加法运算不仅是理论知识的积累,也是...

    用递归算法实现两个整数最大公约数的计算

    这种设计遵循了欧几里得算法的逻辑,通过不断将问题规模缩小,最终达到基线条件,从而求出最大公约数。 ### 总结 通过上述分析,我们可以看到递归算法在解决数学问题,特别是最大公约数计算上的应用。递归不仅简化...

    字符串整数的余数leetcode-LeetCode-Activity:整数到罗马数字代码和描述

    字符串可能的余数LeetCode-活动 整数到罗马数字代码和描述 将整数转换为罗马数字 我目前正在教 AP 计算机科学 A(我的第一年),我选择这个作业是因为我认为我的学生也可以编码。 我们刚刚了解了用户定义的类和 ...

    最方便好用的余数计算器

    对于大整数的余数计算,可能需要用到高效的大数运算库,如GMP(GNU Multiple Precision Arithmetic Library)。 在实际应用中,余数计算器可能包含一些额外的功能,如历史记录查看、连续计算(连续对同一个除数进行...

    求给定正整数的位数以及各位

    在编程领域,处理正整数并获取其位数以及逆序打印各位数字是常见的基础操作。这个题目主要涉及两个知识点:计算位数和数组或字符串的逆序操作。以下是这两个知识点的详细说明: **1. 计算位数** 计算一个正整数的...

    大整数类 C++实现

    在计算机科学中,大整数类(通常称为大数或大整数)是用于处理超出标准整数类型(如int、long...在`Bignum.cpp`和`bignum.h`中,你可以找到具体的实现细节,通过阅读和分析这些代码,可以进一步加深对大整数类的理解。

    java代码-分解数求余数

    在Java编程语言中,"分解数求余数"通常指的是将一个整数分解为其因子,并对每个因子求余数的过程。这个过程在许多数学和计算机科学问题中都有应用,例如在模运算、加密算法或者算法分析中。让我们深入探讨这个主题。...

    课设报告样例(大整数运算).rar

    这篇课设报告样例以“大整数运算”为主题,主要针对C语言编程环境下的大整数处理进行了深入探讨和实践。大整数运算在计算机科学中是一个重要的领域,尤其是在密码学、数值计算以及分布式计算中有着广泛的应用。C语言...

    运算 大整数 乘法

    在计算机科学领域,大整数乘法是指处理超过常规整型数据类型所能表示的最大数值范围内的数字相乘问题。传统的整型数据类型(如C语言中的`int`)往往有固定的大小限制,如32位系统中的`int`类型通常最大只能表示到\(2...

    精选_基于C++实现的大整数计算_源码打包

    在实际应用中,如密码学的RSA加密、区块链技术的椭圆曲线加密,或者大数据分析中的精确计数等问题,都需要大整数计算的支持。因此,掌握这种技术不仅可以提高编程能力,还能为解决实际问题提供强大的工具。这个...

    数据结构课程设计长整数加减乘除

    **求余运算**在长整数中也相当重要,它可以通过减法来实现。先计算长整数除以另一个长整数的商,然后用原来的长整数减去商乘以除数的结果,剩下的就是余数。 在实际项目中,`数据结构课程设计长整数加减乘除现.txt`...

    整数与多项式1

    在编程竞赛中,模运算常用于处理大数据量的问题,比如计算大整数除以某个固定数的结果,或者实现快速幂运算。 接下来,我们转向信息学竞赛中的多项式。多项式是由变量、常数以及这些项的加法和乘法构成的表达式,如...

    GCD大整数.rar_K8YT_gcd_hurtuwe_stein

    GCD)计算的压缩包文件,其中可能包含了用Java和Python两种编程语言实现的Stein's Algorithm,这是一种优化版的GCD算法,特别适用于大整数的处理,以克服传统欧几里得算法在处理大数时效率较低的问题。 欧几里得...

Global site tag (gtag.js) - Google Analytics