0对任何数取余_大整数求余数的问题分析

问题描述

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

问题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。那么我们也就证明了上面等式的成立。

参考材料:

大小: 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

分享到:

2012-11-26 18:26

浏览 12841

评论

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
c面试题 4. static有什么用途?(请至少说明两种) 1.限制变量的作用域 2.设置变量的存储域 7. 引用与指针有什么区别? 1) 引用必须被初始化,指针不必。 2) 引用初始化以后不能被改变,指针可以改变所指的对象。 2) 不存在指向空值的引用,但是存在指向空值的指针。 8. 描述实时系统的基本特性 在特定时间内完成特定的任务,实时性与可靠性 9. 全局变量和局部变量在内存中是否有区别?如果有,是什么区别? 全局变量储存在静态据库,局部变量在堆栈 10. 什么是平衡二叉树? 左右子树都是平衡二叉树且左右子树的深度差值的绝对值不大于1 11. 堆栈溢出一般是由什么原因导致的? 没有回收垃圾资源 12. 什么函不能声明为虚函? constructor 13. 冒泡排序算法的时间复杂度是什么? O(n^2) 14. 写出float x 与“零值”比较的if语句。 if(x>0.000001&&x<-0.000001) 16. Internet采用哪种网络协议?该协议的主要层次结构? tcp/ip 应用层/传输层/网络层/据链路层/物理层 17. Internet物理地址和IP地址转换采用什么协议? ARP (Address Resolution Protocol)(地址解析協議) 18.IP地址的编码分为哪俩部分? IP地址由两部分组成,网络号和主机号。不过是要和“子网掩码”按位与上之后才能区分哪些是网络位哪些是主机位。 2.用户输入M,N值,从1至N开始顺序循环,每到M输出该值,直至全部输出。写出C程序。 循环链表,用取余操作做 3.不能做switch()的参类型是: switch的参不能为实型。 華為 1、局部变量能否和全局变量重名? 答:能,局部会屏蔽全局。要用全局变量,需要使用"::" 局部变量可以与全局变量同名,在函内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内 2、如何引用一个已经定义过的全局变量? 答:extern 可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错 3、全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么? 答:可以,在不同的C文件中以static形式来声明同名全局变量。 可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时连接不会出错 4、语句for( ;1 ;)有什么问题?它是什么意思? 答:和while(1)相同。 5、do……while和while……do有什么区别? 答:前一个循环一遍再判断,后一个判断以后再循环 6、请写出下列代码的输出内容 #include<stdio.h> main() { int a,b,c,d; a=10; b=a++; c=++a; d=10*a++; printf("b,c,d:%d,%d,%d",b,c,d); return 0; } 答:10,12,120 1、static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函与普通函有什么区别? 全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函公用,因此可以避免在其它源文件中引起错误。 从以上分析可以看出,把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。 static函与普通函作用域不同。仅在本文件。只在当前源文件中使用的函应该说明为内部函(static),内部函应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函,应该在一个头文件中说明,要使用这些函的源文件要包含这个头文件 static全局变量与普通的全局变量有什么区别:static全局变量只初使化一次,防止在其他文件单元中被引用; static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值; static函与普通函有什么区别:static函在内存中只有一份,普通函在每个被调用中维持一份拷贝 2、程序的局部变量存在于(堆栈)中,全局变量存在于(静态区)中,动态申请据存在于(堆)中。 3、设有以下说明和定义: typedef union {long i; int k[5]; char c;} DATE; struct data { int cat; DATE cow; double dog;} too; DATE max; 则语句 printf("%d",sizeof(struct date)+sizeof(max));的执行结果是:___52____ 答:DATE是一个union, 变量公用空间. 里面最大的变量类型是int[5], 占用20个字节. 所以它的大小是20 data是一个struct, 每个变量分开占用空间. 依次为int4 + DATE20 + double8 = 32. 所以结果是 20 + 32 = 52. 当然...在某些16位编辑器下, int可能是2字节,那么结果是 int2 + DATE10 + double8 = 20 4、队列和栈有什么区别? 队列先进先出,栈后进先出 5、写出下列代码的输出内容 #include<stdio.h> int inc(int a) { return(++a); } int multi(int*a,int*b,int*c) { return(*c=*a**b); } typedef int(FUNC1)(int in); typedef int(FUNC2) (int*,int*,int*); void show(FUNC2 fun,int arg1, int*arg2) { INCp=&inc; int temp =p(arg1); fun(&temp,&arg1, arg2); printf("%d"n",*arg2); } main() { int a; show(multi,10,&a); return 0; } 答:110 7、请找出下面代码中的所以错误 说明:以下代码是把一个字符串倒序,如“abcd”倒序后变为“dcba” 1、#include"string.h" 2、main() 3、{ 4、 char*src="hello,world"; 5、 char* dest=NULL; 6、 int len=strlen(src); 7、 dest=(char*)malloc(len); 8、 char* d=dest; 9、 char* s=src[len]; 10、 while(len--!=0) 11、 d++=s--; 12、 printf("%s",dest); 13、 return 0; 14、} 答: 方法1: int main(){ char* src = "hello,world"; int len = strlen(src); char* dest = (char*)malloc(len+1);//要为"0分配一个空间 char* d = dest; char* s = &src[len-1];//指向最后一个字符 while( len-- != 0 ) *d++=*s--; *d = 0;//尾部要加"0 printf("%s"n",dest); free(dest);// 使用完,应当释放空间,以免造成内存汇泄露 return 0; } 方法2: #include <stdio.h> #include <string.h> main() { char str[]="hello,world"; int len=strlen(str); char t; for(int i=0; i<len/2; i++) { t=str[i]; str[i]=str[len-i-1]; str[len-i-1]=t; } printf("%s",str); return 0; } 1.-1,2,7,28,,126请问28和126中间那个是什么?为什么? 第一题的答案应该是4^3-1=63 规律是n^3-1(当n为偶0,2,4) n^3+1(当n为奇1,3,5) 答案:63 2.用两个栈实现一个队列的功能?要求给出算法和思路! 设2个栈为A,B, 一开始均为空. 入队: 将新元素push入栈A; 出队: (1)判断栈B是否为空; (2)如果不为空,则将栈A中所有元素依次pop出并push到栈B; (3)将栈B的栈顶元素pop出; 这样实现的队列入队和出队的平摊复杂度都还是O(1), 比上面的几种方法要好。3.在c语言库函中将一个字符转换成整型的函是atool()吗,这个函的原型是什么? 函名: atol 功能: 把字符串转换成长整型 用法: long atol(const char *nptr); 程序例: #include <stdlib.h> #include <stdio.h> int main(void) { long l; char *str = "98765432"; l = atol(lstr); printf("string = %s integer = %ld"n", str, l); return(0); } 2.对于一个频繁使用的短小函,在C语言中应用什么实现,在C++中应用什么实现? c用宏定义,c++用inline 3.直接链接两个信令点的一组链路称作什么? PPP点到点连接 4.接入网用的是什么接口? 5.voip都用了那些协议? 6.软件测试都有那些种类? 黑盒:针对系统功能的测试白合:测试函功能,各函接口 7.确定模块的功能和模块的接口是在软件设计的那个队段完成的? 概要设计阶段 8.enum string { x1, x2, x3=10, x4, x5, }x; 问x= 0x801005,0x8010f4 ; 9.unsigned char *p1; unsigned long *p2; p1=(unsigned char *)0x801000; p2=(unsigned long *)0x810000; 请问p1+5= ; p2+5= ; 三.选择题: 1.Ethternet链接到Internet用到以下那个协议? A.HDLC;B.ARP;C.UDP;D.TCP;E.ID 2.属于网络层协议的是: A.TCP;B.IP;C.ICMP;D.X.25 3.Windows消息调度机制是: A.指令队列;B.指令堆栈;C.消息队列;D.消息堆栈; 4.unsigned short hash(unsigned short key) { return (key>>)%256 } 请问hash(16),hash(256)的值分别是: A.1.16;B.8.32;C.4.16;D.1.32 四.找错题: 1.请问下面程序有什么错误? int a[60][250][1000],i,j,k; for(k=0;k<=1000;k++) for(j=0;j<250;j++) for(i=0;i<60;i++) a[i][j][k]=0; 把循环语句内外换一下 2.#define Max_CB 500 void LmiQueryCSmd(Struct MSgCB * pmsg) { unsigned char ucCmdNum; ...... for(ucCmdNum=0;ucCmdNum<Max_CB;ucCmdNum++) { ......; } 死循环 3.以下是求一个的平方的程序,请找出错误: #define SQUARE(a)((a)*(a)) int a=5; int b; b=SQUARE(a++); 4.typedef unsigned char BYTE int examply_fun(BYTE gt_len; BYTE *gt_code) { BYTE *gt_buf; gt_buf=(BYTE *)MALLOC(Max_GT_Length); ...... if(gt_len>Max_GT_Length) { return GT_Length_ERROR; } ....... } 五.问答题: 1.IP Phone的原理是什么? IPV6 2.TCP/IP通信建立的过程怎样,端口有什么作用? 三次握手,确定是哪个应用程序使用该协议 3.1号信令和7号信令有什么区别,我国某前广泛使用的是那一种? 4.列举5种以上的电话新业务?
目 录 第一章 概述 1 第二章 总体设计 2 2.1 时钟显示的结构 2 2.2设计总思路 2 第三章 软件设计 3 3.1获取时间的各整型据 3 3.2提取字的各位 4 3.3 七段布尔显示控件编码 4 3.4 译码、布尔显示字 5 3.5 显示时间的区段 5 3.6 闪烁 6 第四章 程序调试 7 总 结 8 附 录 9 程序框图前面板 9 程序面板程序框图 10 第一章 概述 虚拟仪器技术就是利用高性能的模块化硬件,结合高效灵活的软件来完成各种测试、 测量和自动化的应用。灵活高效的软件能帮助您创建完全自定义的用户界面,模块化的 硬件能方便地提供全方位的系统集成,标准的软硬件平台能满足对同步和定时应用的需 求。这也正是NI近30年来始终引领测试测量行业发展趋势的原因所在。只有同时拥有高 效的软件、模块化I/O硬件和用于集成的软硬件平台这三大组成部分,才能充分发挥虚拟 仪器技术性能高、扩展性强、开发时间少,以及出色的集成这四大优势。LabVIEW(Lab oratory Virtual Instrument Engineering Workbench,实验室虚拟仪器集成环境)是一种图形化的编程语言(又称G语言),它是 由美国NI公司推出的虚拟仪器开发平台,也是目前应用最广、发展最快、功能最强的图 形化软件集成开发环境。使用这种语言编程时,基本上不用写程序代码,取而代之的是 程序框图。LabVIEW的特点如下: 编程简单; 开发周期短; 高效性; 开放性; 自定义性; 性价比高,能一机多用。 第二章 总体设计 2.1 时钟显示的结构 本课题要求设计一个字时钟。通过获取电脑的系统时间,并分离出给字,在通过 布尔显示显示。字的显示主要是7个长条的布尔显示组成,原理与7段码管相似。7段 码管显示不同的字主要通过其7个布尔不同的真假值控制,将0- 9对应的7段布尔显示值依次存入一个布尔组里,只需提取此组的不同段即可让其显 示不同的值,如显示"0"提取组的0-6位分别赋值给7段布尔显示。 2.2设计总思路 获取时间的控件可以获取的信息有:年、月、日、星期、天、时、分、秒、秒小 等,取得的值为整型据,利用除取余即可分离个十位。为了获得更好的显示效果将 背景改为黑色,布尔显示的颜色为绿色。 第三章 软件设计 3.1获取时间的各整型据 软件中获取日期/时间(秒)控件从计算机系统中自动提取时间,并转换成日期、时 间等,控件图如下: 图3-1 在获取时间后,将据解除捆绑即分别取得年、月、日、时间等,如下图: 图 3-2 3.2提取字的各位 通过除10取余便可得到个位,商为十位,以此方法也可求出年的各个字,如下图: 图3-3 3.3 七段布尔显示控件编码 取出各位字后,并不可直接显示,要通过统一编码规范显示的格式,这样才能显示 各位,编码各段如下: 图3-4 A、B、C、D、E、F、G分别对应着七段布尔显示控件,要显示"0"时A~G的赋值分别为:1 1 1 1 1 1 0(1代表真,0为假),显示其他字也是按此做法的。最后将此70个值串接起来存入一 个布尔组里,如图3-4右。 3.4 译码、布尔显示字 要显示字时,只需将字乘以7,再将乘积的值作为索引在那存放真值的组里寻找对 应显示的七个布尔显示控件的值。如显示'2',则从组的第2*7=14位开始,依次取出7 个(分别代表着七个布尔的真值),其他也同理。最后捆绑成簇,因为七段显示布尔已 做成簇。Labview表达如下: 图 3-5 3.5 显示时间的区段 显示的时间被分成7段,即凌晨、早上、上午、中午、下午、傍晚、晚上,只需通过表达 式节点计算出此时属于哪一段(1- 7),再通过七个布尔文本分别显示即可,布尔显示控件叠加在一起,程序及显示效果如 下图: 图3-6 3.6 闪烁 闪烁时只要判断其是否秒大于0.5S,大于则显示(为真),小于则熄灭(为假),图 3-7为程序框图,3-8为实际效果: 图 3-7 图 3-8 第四章 程序调试 程序很容易上手,没有C语言那么复杂的语法等,图形界面让人更直观的对各部分进 行操作。此课程设计重点及难点在于布尔显示字,开始时感觉其据量非常大,由于 簇的特点使得程序的复杂性大大降低,而且程序更加清晰。显示的主要借助于平时常用 的码管,它的显示原理就如本课程设计的要求是一样的,主要有统一的编码,显示时 通过解码即可显示出字。 开始做好7段布尔显示后,不管怎么看都很生硬,完全不是字时钟的模样,研究了 半天,原来是背景的缘故,当我换背景成黑色,且布尔显示位绿色后,显示效果大不一 样,让人感觉到它就是一个电子手表一样。不过它还是有一点不足:显示的字还是那么 生硬,不生动,字体台统一、死板了。不过由于是布尔

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值