题目来源于ACM竞赛。
要求,输入任意一个正整数,计算得到2^N%N,需要考虑溢出,并且有时间限制。
按照正常的循环求指数的运算,基本上在N比较大的时候一定会超时,因此为了实现这个要求只能寻求时间复杂度小于O(N)的算法。
相应的算法及测试代码如下:
1
class
Program
2
{
3 static void Main(string[] args)
4 {
5 Console.WriteLine(“请输入一个正整数:“);
6 bool flag = false;
7 int test = 0;
8 while (!flag)
9 {
10 string s = Console.ReadLine();
11 flag = int.TryParse(s, out test);
12 flag = flag && (test > 0);
13 if (!flag)
14 Console.WriteLine(“格式不正确,请重新输入:“);
15 else
16 Console.WriteLine(“您输入的正整数为:{0}“, test);
17 }
18
19 Stopwatch sw = new Stopwatch();
20 sw.Start();
21 int fast = computeFast(test);
22 sw.Stop();
23 Console.WriteLine(“O(logN)计算结果为{0}“, fast);
24 Console.WriteLine(“O(logN)算法耗时{0}ms“, sw.ElapsedMilliseconds);
25 sw.Reset();
26 sw.Start();
27 int slow = computeSlow(test);
28 sw.Stop();
29 Console.WriteLine(“O(N)计算结果为{0}“, slow);
30 Console.WriteLine(“O(N)算法耗时{0}ms“, sw.ElapsedMilliseconds);
31 Console.Read();
32 }
33
34 static int computeFast(int n)
35 {
36 if (n <= 0)
37 throw new ArgumentException();
38 long result = 1;
39 long helper = 2;
40 int m = n;
41 while (m > 0)
42 {
43 if ((m & 1) == 1)
44 result = (result * helper) % n;
45 helper = ((helper % n) * (helper % n)) % n;
46 m = m >> 1;
47 }
48 return (int)result;
49 }
50
51
52 static int computeSlow(int n)
53 {
54 if (n <= 0)
55 throw new ArgumentException();
56 long result = 1;
57 for (int i = 0; i < n; i++)
58 {
59 result = (result << 1) % n;
60 }
61 return (int)result;
62 }
63 } 对于两个算法中用long去申请局部变量的原因是为了防止溢出,尽管N是一个int变量,但是由于int集合在乘法运算中并不满足闭包性质,所以只有利用long才能解决问题。至于结果由于是已经%N,因此它一定小于N,仍然是一个int变量。
主要来说,这里面运用的数学定理不多,主要就是(a*b)%n=(a%n)*(b%n)%n。如果没有这个公式,可以说由于指数运算,long也必然无法满足要求而导致溢出。
至于第一种算法,主要就是将指数N分解成为二进制的形式,利用一个helper变量来维护当前需要乘的值。利用的是公式a^(m+n)=a^m*a^n。
经过测试,当N较大时,差距将比较明显——时间复杂度的区别还是非常值得注意的。