初学 快速幂 的理解
博客停了差不多三个月, 虽然这一段时间在学算法, 但从来没有写博客。 今天看了一上午的快速幂,突然想写写博客, 增加一下自己的记忆!这个博文知识简单介绍一下算法中取余的原因
1 至于快速幂的概念不详细记录了。当我们想求a的b次幂对c取余时,我们会直接想到用这个算法:
int ans = 1;
for( i = 1; i <= b; i++)
{
ans = ans * a;
}
ans %= c;
这个算法的时间复杂度体现在for循环中,为O(b).这个算法存在着明显的问题,如果a和b过大,很容易就会溢出。因此需要用到离散数学知识(该知识点我也没学过,度娘教的^_^)
定理1:
2 由上面的公式可以推出:
把a看成 a * 1, 故由定理1可得出上面的公式。由此得到改进版本:
int ans = 1;
a = a % c; //加上这一句
for(int i = 1;i<=b;i++)
{
ans = (ans * a) % c;//这里再取了一次余
}
ans = ans % c;
这个算法在时间复杂度上没有改进,仍为O(b),不过已经好很多的,但是在c过大的条件下,还是很有可能超时,所以,我们推出以下的快速幂算法。
3 快速幂算法依赖于以下明显的公式,我就不证明了,很简单理解。
4 由此得到改进版本:
int ans = 1;
a = a % c;
if(b%2==1)
ans = (ans * a) mod c; //如果是奇数,要多求一步,可以提前算到ans中
k = (a*a) % c; //我们取a2而不是a
for(int i = 1;i<=b/2;i++)
{
ans = (ans * k) % c;
}
ans = ans % c;
5 我们可以看到,我们把时间复杂度变成了O(b/2).当然,这样子治标不治本。但我们可以看到,当我们令k = (a * a) mod c时,
状态已经发生了变化,我们所要求的最终结果即为(k)^b/2 mod c而不是原来的a^b mod c,所以我们发现这个过程是可以迭代下去的。
((迭代就是把这一次算的值作为下一次循环的初始值,所以可以更简化该算法))
当然,对于奇数的情形会多出一项a mod c,所以为了完成迭代,当b是奇数时,我们通过 ans = (ans * a) % c;来弥补多出来的这一项,
此时剩余的部分就可以进行迭代了。 形如上式的迭代下去后,当b=0时,所有的因子都已经相乘,算法结束。于是便可以在O(log b)的时间内完成了。
于是,有了最终的算法:快速幂算法。
((说实话, 我对这个简化有点糊涂,关于在O(log b)的时间内就可以完成, 就假装懂了吧, 以后慢慢理解吧))
6
int ans = 1;
a = a % c;
while(b>0)
{
if(b % 2 == 1)
ans = (ans * a) % c;
b = b/2;
a = (a * a) % c;
}
7 将上述的代码结构化,也就是写成函数:
int PowerMod(int a, int b, int c)
{
int ans = 1;
a = a % c;
while(b>0)
{
if(b % 2 = = 1)
ans = (ans * a) % c;
b = b/2;
a = (a * a) % c;
}
return ans;
}
这就是最后的优化代码了。以上内容是抄袭的, 只不过自己又写了一遍。如果看不懂我写的的可以点下面网址:
有关于快速幂的算法的推导,还可以从另一个角度来想。我就不介绍了。