Linear time Euler's Totient Function calculation

牧云@^-^@ 提交于 2019-12-04 18:36:30
DarthGizka

The coding is neither here nor there but the algorithm as such is brilliant. It has little to do with the Sieve of Eratosthenes, though. The approach is somewhat reminiscent of the Sieve of Sundaram because it systematically produces multiples of primes in order to mark composites; it's better than Sundaram's in that each composite is crossed off exactly once (no overdraw). Likewise, each phi value is computed and assigned exactly once.

It is easier to understand the code if it is changed a little first:

enum {  N = 3000000  };
vector<unsigned> primes;
vector<unsigned> phi(N + 1);        // indexed directly with numbers, hence the +1
vector<bool> is_composite(N + 1);   // indexed directly with numbers, hence the +1

phi[1] = 1;   // phi[0] is 0 already; phi[1] needs to be set explicitly

for (unsigned i = 2; i <= N; ++i)
{
   if (not is_composite[i])  // it's a prime
   {
      phi[i] = i - 1;        // a prime is coprime to all numbers before it
      primes.push_back(i);
   }

   for (auto current_prime: primes)
   {
      unsigned ith_multiple = i * current_prime;

      if (ith_multiple > N)
         break;

      is_composite[ith_multiple] = true;

      if (i % current_prime)  // i and current_prime are coprime -> phi is multiplicative in this case
      {
         phi[ith_multiple] = phi[i] * (current_prime - 1);  // (current_prime - 1) == phi[current_prime]
      }
      else
      {
         phi[ith_multiple] = phi[i] * current_prime;  // based on phi(p^(k+1)) / phi(p^k) == p

         break;
      }
   }
}

The computation of the values for phi(k) has to consider three different cases:

(1) k is prime: phi(k) = k - 1, which is trivial

(2) k = m * p with m and p coprime and p prime: phi(k) = phi(m * p) = phi(m) * phi(p) = phi(m) * (p - 1)

(3) k = m * p with m = m' * p^n and m' and p coprime and p prime: phi(k) = phi(m) * p

The two relevant mathematical facts are listed under Euler's product formula in the Wikipedia article on Euler's totient function. Case 1 is dealt with in the outer loop, case 2 is the then branch of the condition in the inner loop, and case 3 is the else branch which also terminates the inner loop. Cases 2 and 3 build phi values for composites out of preexisting phi values for smaller numbers, all of which ultimately derive from phi values for primes (set in the outer loop).

The brilliance of the algorithm lies in the way in which it arranges the work to be done, such that each value is computed exactly once and has already been computed when it is needed. The recursion it implements for composites is based on effectively splitting each composite by factoring out its smallest prime factor: m = m' * p. This facilitates the computation of the composite's phi as per cases 2 and 3 above, and it leads to a simple rule for producing composites without duplicates. Among other things, the outer loop presents all possible m' to the inner loop (although for i > N/2 the inner loop never takes any and the outer loop spins only for collecting the remaining primes). The inner loop then produces all composites that are the product of m' and a prime factor not exceeding the smallest of its own prime factors.

The code has been verified against a list of the first 100,000 phi values retrieved from the OEIS page for the phi function. It was written expressly to showcase the workings of the algorithm; before being used in a program it would have to be reviewed, tweaked, and hardened a bit (in particular to guard against overflow).

Addendum - In case anyone inadvertently skips DanaJ's comment: the is_composite[] array is superfluous because the non-compositeness (primality) of a given i can be ascertained by testing phi[i] for zero in the outer loop. The reason is that phi values for composites are propagated up - they get computed during an earlier iteration when i is one of their factors. Another way of reasoning is that is_composite[m] is only ever set to true when the corresponding phi value gets computed and stored, and those values can never be zero. Hence the test in the outer loop becomes if (phi[i] == 0). And implementers (as opposed to 'mere' connoisseurs) might want to look at DanaJ's comment even more closely...

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!