快速幂取模:求 a^b % N(C++)

在某些情况下,我们需要求模 N 情况下某个数的多次幂,例如:

  1. 求多次幂结果的最后几位数

  2. RSA算法的加解密

如果底数或者指数很大,直接求幂再取模很容易会出现数据溢出的情况,产生错误的结果,同时如果简单的重复乘以某个数求其多次幂,速度很慢,这时候就需要用到快速幂取模了,一般也简称快速幂。

快速幂取模基于下面这条引理:

积的取余等于取余的积的取余

即 (a * b) % N 等价于 ( (a % N) * (b % N) ) % N

例如:(13 * 5) % 3 = ( (13 % 3) * (5 % 3) ) % 3

通过计算可以知道上述等式是成立的,而且也不难理解上述引理的正确性,这里就不过多赘述。

求 a^b % N,我们知道,如果 a 或者 b 较大的话,先求幂再取模就有可能导致数据溢出,所以如何解决这一问题就显而易见了,根据引理,我们可以边求幂边取模,避免直接求幂导致的数据溢出。

long mod(long a, long b, long N)
{
	long result = 1;
	// 在求幂之前,我们可以先把 a 限制在模 N 范围内
	a = a % N;
	while (b > 0)
	{
		result = result * a;
		// 根据引理,每个中间结果均可先取模,减小数据溢出的可能性
		result = result % N;
		b--;
	}
	return result;
}

上面减小了数据溢出的可能性,但是仍然是简单的通过重复乘法求幂,速度比较慢,那我们要如何加快求幂的速度,实现快速幂呢?

首先,我们先来看 b 为偶数的情况,例如 2^8 % 5,我们知道,a^(m * n) = (a^m)^n,所以我们可以推出:

2^8 = 2^(2 * 4) = (2^2)^4 = 4^4

b 为偶数的情况下,我们可以通过上述方法将其减小为原来的二分之一,即可将乘法的数量减少为原来的一半,聪明的读者应该想到了,我们可以反复地使用这种方法,不断地减小 b,减少乘法的数量,实现快速求幂。

细心的读者会问了,那要是 b 是奇数怎么办呢?其实不难解决,b 为奇数时可以看作 (b - 1) + 1,此时 b - 1 为偶数,所以我们只需要把多出来的一次单独计算并带入结果即可,也许单纯看文字没办法理解,下面我们来看具体的实现。

long mod(long a, long b, long N)
{
	long result = 1;
	// 在求幂之前,我们可以先把 a 限制在模 N 范围内,即把 a 变小
	a = a % N;
	while (b > 0)
	{
		// 如果 b 是奇数,将多出来的一次单独计算并带入结果
		if (b % 2 == 1) 
		{
			result = result * a;
			result = result % N;
			b--;
		}
		// b 减小为原来的二分之一
		b /= 2;
		a = (a * a) % N;
	}
	return result;
}

以上便是对快速幂取模算法的简单描述和实现,它是一个非常有用的小算法,也比较容易掌握,可能在ACM题目或者密码学算法中会用到,希望读者看完这篇文章能够理解并掌握快速幂。

水平有限,如有错误,还请批评指正。

猜你喜欢

转载自blog.csdn.net/Chen__Mou/article/details/89407303