Perrin Sequence In C++ - C++ Programming Tutorial
C++ Course / Miscellaneous / Perrin Sequence In C++

Perrin Sequence In C++

BLUF: Mastering Perrin Sequence In C++ is a critical step in becoming a proficient C++ developer. This lesson provides a deep dive into the syntax, performance considerations, and real-world applications of this concept.
Key Performance Insight: Perrin Sequence In C++

C++ is renowned for its efficiency. Learn how Perrin Sequence In C++ enables low-level control and high-performance computing in the tutorial below.

In this article, we will discuss the Perrin Sequence in C++ with its mathematical properties, Recursive and optimized technique, and an example.

What is the Perrin Sequence in C++?

The Perrin series is a numerical sequence that adheres to a specific recursive formula. It commences with the values 3, 0, and 2, and each subsequent term is the summation of the term positioned two places prior and the term situated three places earlier. In a symbolic representation, P(n) = P(n-2) + P(n-3) for n ≥ 3, having initial values P(0) = 3, P(1) = 0, and P(2) = 2. This progression is intriguing due to its intricate link to prime numbers and its association with the broader domain of number theory. As a result, individuals frequently explore this sequence in mathematical courses and leverage computational tools to uncover any additional patterns that may arise.

The Perrin Sequence offers insights into discovering certain prime numbers, as the number P(p) can be evenly divided by p in these cases. While not always accurate (leading to Perrin pseudoprimes), this method introduces a novel approach to verifying prime status. This technique sometimes allows for a faster determination of non-prime numbers compared to other traditional tests. Unlike Fibonacci numbers, the Perrin Sequence exhibits a more moderate exponential growth rate, which researchers have extensively analyzed. This distinct growth pattern is evident when examining specific numbers within the sequence, such as 5 (the sum of 2 and 3).

Example

2^2 > 5, but 2^1 = 2 < 5.

This series plays a role in graph theory challenges related to object combinations and in elucidating the arrangement of components in structural combinatorics. There exist effective techniques for determining the value at position n within the Perrin Sequence. This becomes crucial as n increases significantly, surpassing the computational capacity of our initial approach. Innovations by computer scientists have led to speedier methods, leveraging concepts previously discussed in this article. These approaches involve creating a record of numbers encountered, modifying the processing method for new numbers to minimize recalculations in subsequent iterations.

Mathematical Properties:

The Perrin series exhibits a range of intriguing mathematical characteristics, particularly in relation to prime numbers. An interesting conjecture suggests that when n is a prime number, P(n) will be divisible by n. This speculation has led to the belief that the Perrin sequence could serve as a valuable tool for determining the primality of a number. However, there exist composite numbers for which P(n) is divisible by n, despite n not being a prime number. These numbers are known as Perrin pseudoprimes. The existence of Perrin pseudoprimes introduces uncertainty regarding the reliability of using this sequence for primality testing, with 271441 serving as an illustration of a Perrin pseudoprime.

Another thing to note about this sequence is that each term is gotten by adding the two before it and the three before it together again. It is a little different from how we get Fibonacci numbers.

  • This rule tells us what every number in the list has to be, so once we know two numbers, we can figure out what comes after them.
  • The Perrin sequence grows more slowly than Fibonacci numbers, but it still follows a pattern.
  • If we want to find big numbers in the Perrin sequence, like the millionth number, we can use matrices (linear algebra) instead of lots of addition.
  • Sometimes, researchers want to know how many paths there are between certain points in a network. It is called graph theory.
  • One way to calculate this number looks at whether or not nodes (dots) in the network are connected to each other in a circle.
  • When mathematicians talk about putting things in order without repeating any, it is called permutations. There is a connection between permutations and the Perrin sequence.
  • Recursive and Optimized Approaches to the Perrin Sequence in C++

Once the Perrin sequence’s math concepts are clear, it is important to see how we can use programming to get the same results. The sequence is simple: each number in it is found by adding the two before it, but not the immediately before number; instead, we go back two steps (past number one). Recursion can be used to write code for this easily enough. However, if we are not careful, we will end up doing a lot of extra work.

  • Finding Perrin numbers of large sizes still takes O(n) steps with dynamic programming, which can be slow for really big inputs.
  • Matrix Exponentiation can find the answer in O(log n) time complexity, which is immensely better than O(n) for very large values of n.
  • Matrices can also represent Perrin numbers. Using this fact, along with matrix exponentiation, we can find the nth Perrin number using a logarithmic number of steps.
  • Competitive programming and cryptography are some areas that use this idea a lot.

Each of these techniques comes with advantages and disadvantages. Recursion, while straightforward, tends to be slow due to the numerous redundant computations it requires. Memoization addresses this issue by storing solutions for each problem, but it demands a significant amount of memory. Dynamic programming optimizes space usage by retaining only essential data, yet it still consumes a considerable memory footprint. In contrast, Matrix Exponentiation stands out as the most efficient option, as it minimizes the number of computations needed and operates within logarithmic space. In general, dynamic programming suffices for most scenarios unless the value of n is exceptionally large.

Example:

Let's consider a scenario to demonstrate the Perrin Sequence in the C++ programming language.

Example

#include <iostream> 
#include <unordered_map>
#include <vector>

using namespace std;

long long perrin_recursive(int n) {
    if (n == 0) return 3;
    if (n == 1) return 0;
    if (n == 2) return 2;
    return perrin_recursive(n - 2) + perrin_recursive(n - 3);
}

unordered_map<int, long long> memo;
long long perrin_memoization(int n) {
    if (n == 0) return 3;
    if (n == 1) return 0;
    if (n == 2) return 2;
    if (memo.find(n) != memo.end()) return memo[n];
    return memo[n] = perrin_memoization(n - 2) + perrin_memoization(n - 3);
}

long long perrin_iterative(int n) {
    if (n == 0) return 3;
    if (n == 1) return 0;
    if (n == 2) return 2;
    long long p0 = 3, p1 = 0, p2 = 2, p;
    for (int i = 3; i <= n; i++) {
        p = p0 + p1;
        p0 = p1;
        p1 = p2;
        p2 = p;
    }
    return p2;
}

vector<vector<long long>> multiplyMatrix(vector<vector<long long>> A, vector<vector<long long>> B) {
    vector<vector<long long>> C(3, vector<long long>(3, 0));
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            for (int k = 0; k < 3; k++)
                C[i][j] += A[i][k] * B[k][j];
    return C;
}

vector<vector<long long>> matrixExponentiation(vector<vector<long long>> M, int n) {
    vector<vector<long long>> result = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
    while (n > 0) {
        if (n % 2 == 1)
            result = multiplyMatrix(result, M);
        M = multiplyMatrix(M, M);
        n /= 2;
    }
    return result;
}

long long perrin_matrix_exponentiation(int n) {
    if (n == 0) return 3;
    if (n == 1) return 0;
    if (n == 2) return 2;
    vector<vector<long long>> M = {{0, 1, 1}, {1, 0, 0}, {0, 1, 0}};
    vector<vector<long long>> result = matrixExponentiation(M, n - 2);
    return result[0][0] * 2 + result[0][1] * 0 + result[0][2] * 3;
}

int main() {
    int n = 20;

    cout << "Recursive Approach: ";
    for (int i = 0; i <= n; i++) 
        cout << perrin_recursive(i) << " ";
    cout << endl;

    cout << "Memoization Approach: ";
    for (int i = 0; i <= n; i++) 
        cout << perrin_memoization(i) << " ";
    cout << endl;

    cout << "Iterative Approach: ";
    for (int i = 0; i <= n; i++) 
        cout << perrin_iterative(i) << " ";
    cout << endl;

    cout << "Matrix Exponentiation Approach: ";
    for (int i = 0; i <= n; i++) 
        cout << perrin_matrix_exponentiation(i) << " ";
    cout << endl;

    return 0;
}

Output:

Output

Recursive Approach: 3 0 2 3 2 5 5 7 10 12 17 22 29 39 51 68 90 119 158 208 273  
Memoization Approach: 3 0 2 3 2 5 5 7 10 12 17 22 29 39 51 68 90 119 158 208 273  
Iterative Approach: 3 0 2 3 2 5 5 7 10 12 17 22 29 39 51 68 90 119 158 208 273  
Matrix Exponentiation Approach: 3 0 2 3 2 5 5 7 10 12 17 22 29 39 51 68 90 119 158 208 273

Conclusion:

In summary, the Perrin sequence is computed in a unique manner that proves valuable in both mathematical calculations and computer algorithms. While the conventional approach yields a clear understanding, its efficiency is compromised due to redundant steps. To expedite the process, alternative methods such as memoization and iterative dynamic programming offer quicker solutions. These approaches significantly reduce the processing time, especially for larger computations. However, the most efficient technique remains matrix exponentiation, which boasts the fastest computation speed. This method is commonly preferred for its logarithmic time complexity, making it a go-to choice when dealing with sizable inputs. Understanding the intricacies of these methodologies enables us to make informed decisions when tackling similar problems in the future.

Input Required

This code uses input(). Please provide values below:

Logic Practice
Install Logic Practice
Add to home screen for a faster app-like experience