Josephus Problem

JAY VINCHHI's photo
·

5 min read

The Josephus problem is a fascinating mathematical puzzle involving people standing in a circle, where every k-th person is eliminated until only one remains. Named after Flavius Josephus, this problem dates back to ancient history and has significant algorithmic importance today.

Historical Background

The Josephus problem originated during the Jewish-Roman War, where Josephus and his companions, facing capture, agreed to eliminate themselves in a specific order. Josephus, who didn’t want to die, positioned himself strategically to be the last survivor, thus giving birth to the puzzle we now study.

Problem Statement

The Josephus problem asks:

  • Given n people standing in a circle and eliminating every k-th person, determine the position of the last remaining survivor.

For example, with n = 7 and k = 3, the sequence of eliminations is: 3, 6, 2, 7, 5, 1. The last remaining person is at position 4.

Visualization

To better understand the Josephus problem, visualizations can help. Imagine a group of people in a circle with eliminations occurring step by step. An interactive animation or flowchart of this process clarifies how the recursive elimination unfolds.

Recursive and Iterative Solutions

Recursive Formula:

The problem can be expressed recursively as:

$$J(n, k) = \left\{ \begin{array}{ll} 1 & n= 1 \\ ((J(n-1, k) + k-1)\mod n) + 1 & n > 1 \\ \end{array} \right.$$

Iterative Solution:

In C++ and Python, this solution can also be implemented iteratively:

#include <bits/stdc++.h>
using namespace std;

int josephus(int n, int k) {
    int result = 0; // Base case for 1 person

    for (int i = 2; i <= n; i++) {
        result = (result + k) % i;
    }

    return result;
}

int main() {
    int n, k;
    cin >> n >> k;
    int result = josephus(n, k) + 1;
    cout << "The position of the last person standing is: " << result << endl;

    return 0;
}
def josephus(n, k):
    if n == 1:
        return 0
    else:
        return (josephus(n - 1, k) + k) % n

def main():
    n = int(input())
    k = int(input())
    result = josephus(n, k) + 1
    print(result)

if __name__ == "__main__":
    main()

Special Case (k = 2):

For k = 2, the solution has a direct mathematical pattern. If the number of people n is written in terms of powers of 2 (i.e., n = 2^m + l, where l is the remainder when dividing n by the nearest lower power of 2), the position of the last person can be calculated by the formula:

$$J(n,2) = 2l + 1$$

Formal Proof

Here’s a formal proof for the Josephus problem when k = 2:

  • Base Cases: $$J(1,2)=1 \text{ and } J(2,2)=1$$

  • Inductive Hypothesis: Assume the formula holds for the first i-1 natural numbers.

  • Odd Case: Let i = 2*j + 1 $$J(2j+1,2)=2J(j,2)+1$$ Using modular arithmetic, this becomes: $$2j+1=2^{m_1}*2+2l_1+1=2^{m_2}+l_2 \text{ where } l_2=2l_1+1$$ Now, we can say $$J(2j+1,2)=2l_2 +1$$

  • Even Case: Let i = 2*j $$J(2j,2)=2J(j,2)−1$$ Using modular arithmetic, $$2j=2^{m_1}*2+2l_1=2^{m_2}+l_2 \text{ where } l_2=2l_1$$ Now, we can say $$J(2j,2)=2l_2 +1$$

Thus, the recursive formula holds for all cases.

Table of Survivor Positions for k = 2

n(number of people)Survivor's Position (k = 2)
11
21
33
41
53
65
77
81
93
105

Is there a more optimal way ? (Yes, using DP)

Yes, there exists a more optimal way. Here we introduce the concept of Dynamic Programming. Using Dynamic Programming, we calculate our answer using the results of sub-problems. This allows to multiple same executions by storing them. Here we will store the answers of smaller values of n depending on the value of n.

If given multiple queries we will be answer for multiples of number of people(<=n and for the same k) in O(1) time.

Even better approach for small values of k

For small k and large n, there is another approach. This approach also uses DP but has running time O(k*log(n)). It is based on considering killing k-th, 2k-th…as one step, then changing the numbering. This improved approach takes the form:

$$ g(n, k) = \begin{cases} 0 & \text{if } n = 1 \\ \left( g(n - 1, k) + k \right) \mod n & \text{if } 1 < n \leq k \\ g\left(n - \left\lfloor \frac{n}{k} \right\rfloor, k \right) - n \mod k + n & \text{if } g\left(n - \left\lfloor \frac{n}{k} \right\rfloor, k \right) < n \mod k \\ \lfloor\frac{k\left(g\left(n - \left\lfloor \frac{n}{k} \right\rfloor, k \right) - n \mod k\right)}{k - 1} \rfloor & \text{if } g\left(n - \left\lfloor \frac{n}{k} \right\rfloor, k \right) \geq n \mod k \text{ and } k \leq n \end{cases} $$

Applications

1. Scheduling and Resource Allocation

  • Round-robin scheduling: The Josephus problem has applications in round-robin algorithms used in operating systems and distributed systems for scheduling tasks, where processes are eliminated in a circular order.

  • Token passing: The problem mirrors the behavior in token-passing algorithms used in network systems, such as Token Ring networks, to control which system gets to send data.

2. Data Structure Rotation

  • Circular linked lists: In situations where elements are stored in a circular linked list (a data structure where each node points to its successor and the last node points back to the first), the Josephus problem applies when elements need to be removed in a round-robin fashion.

3. Cryptography and Security

  • Secure message transmission: The Josephus problem has been used to design secure methods for message transmission where the choice of a surviving node or system can represent secure channels or keys.

Conclusion

The Josephus problem is more than a theoretical exercise; it teaches valuable lessons in recursion, modular arithmetic, and algorithmic design. By mastering this problem, you can apply its principles in various real-world scenarios, from game theory to computer science algorithms.