Skip to Content

Problema de Josefo

Enunciado

Se dan los números naturales nn y kk. Todos los números naturales de 11 a nn se escriben en un círculo. Primero, se cuenta el kk-ésimo número empezando desde el primero y se elimina. Luego se cuentan kk números empezando desde el siguiente y de nuevo se elimina el kk-ésimo, y así sucesivamente. El proceso se detiene cuando queda un solo número. Se pide hallar el último número.

Esta tarea la planteó Flavio Josefo (Flavius Josephus) en el siglo I (aunque en una formulación algo más estrecha: para k=2k = 2).

Este problema se puede resolver modelando el procedimiento. El modelado por fuerza bruta funciona en O(n2)O(n^{2}). Usando un Árbol de Segmentos, podemos mejorarlo a O(nlogn)O(n \log n). Sin embargo queremos algo mejor.

Modelar una solución O(n)O(n)

Intentaremos hallar un patrón que exprese la respuesta del problema Jn,kJ_{n, k} a través de la solución de los problemas anteriores.

Usando modelado por fuerza bruta podemos construir una tabla de valores, por ejemplo la siguiente:

nk123456789101111111111122121212121333221133224411223233455341244124665151453527774263547588176314487993118723881010545339178nkamp;1amp;2amp;3amp;4amp;5amp;6amp;7amp;8amp;9amp;101amp;1amp;1amp;1amp;1amp;1amp;1amp;1amp;1amp;1amp;12amp;2amp;1amp;2amp;1amp;2amp;1amp;2amp;1amp;2amp;13amp;3amp;3amp;2amp;2amp;1amp;1amp;3amp;3amp;2amp;24amp;4amp;1amp;1amp;2amp;2amp;3amp;2amp;3amp;3amp;45amp;5amp;3amp;4amp;1amp;2amp;4amp;4amp;1amp;2amp;46amp;6amp;5amp;1amp;5amp;1amp;4amp;5amp;3amp;5amp;27amp;7amp;7amp;4amp;2amp;6amp;3amp;5amp;4amp;7amp;58amp;8amp;1amp;7amp;6amp;3amp;1amp;4amp;4amp;8amp;79amp;9amp;3amp;1amp;1amp;8amp;7amp;2amp;3amp;8amp;810amp;10amp;5amp;4amp;5amp;3amp;3amp;9amp;1amp;7amp;8\begin{array}{ccccccccccc} n\setminus k & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 & 10 \ 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 \ 2 & 2 & 1 & 2 & 1 & 2 & 1 & 2 & 1 & 2 & 1 \ 3 & 3 & 3 & 2 & 2 & 1 & 1 & 3 & 3 & 2 & 2 \ 4 & 4 & 1 & 1 & 2 & 2 & 3 & 2 & 3 & 3 & 4 \ 5 & 5 & 3 & 4 & 1 & 2 & 4 & 4 & 1 & 2 & 4 \ 6 & 6 & 5 & 1 & 5 & 1 & 4 & 5 & 3 & 5 & 2 \ 7 & 7 & 7 & 4 & 2 & 6 & 3 & 5 & 4 & 7 & 5 \ 8 & 8 & 1 & 7 & 6 & 3 & 1 & 4 & 4 & 8 & 7 \ 9 & 9 & 3 & 1 & 1 & 8 & 7 & 2 & 3 & 8 & 8 \ 10 & 10 & 5 & 4 & 5 & 3 & 3 & 9 & 1 & 7 & 8 \ \end{array}

Y aquí podemos ver claramente el siguiente patrón:

Jn,k=((Jn1,k+k1)modn)+1J_{n,k} = \left( (J_{n-1,k} + k - 1) \bmod n \right) + 1

J1,k=1J_{1,k} = 1

Aquí, la indexación desde uno da una fórmula un tanto desordenada; si en cambio se numeran las posiciones desde 0, se obtiene una fórmula muy elegante:

Jn,k=(Jn1,k+k)modnJ_{n,k} = (J_{n-1,k} + k) \bmod n

Así, hallamos una solución al problema de Josefo, que trabaja en O(n)O(n) operaciones.

Implementación

Implementación recursiva sencilla (con indexación desde uno)

int josephus(int n, int k) { return n > 1 ? (josephus(n-1, k) + k - 1) % n + 1 : 1; }

Forma no recursiva:

int josephus(int n, int k) { int res = 0; for (int i = 1; i <= n; ++i) res = (res + k) % i; return res + 1; }

Esta fórmula también se puede hallar analíticamente. De nuevo aquí asumimos indexación desde cero. Después de eliminar el primer número, nos quedan n1n-1 números. Cuando repetimos el procedimiento, empezaremos con el número que originalmente tenía el índice kmodnk \bmod n. Jn1,kJ_{n-1, k} sería la respuesta para el círculo restante, si empezamos a contar en 00, pero como en realidad empezamos con kk tenemos Jn,k=(Jn1,k+k) modnJ_{n, k} = (J_{n-1,k} + k) \ \bmod n.

Modelar una solución O(klogn)O(k \log n)

Para kk relativamente pequeño podemos idear una solución mejor que la solución recursiva anterior en O(n)O(n). Si kk es mucho menor que nn, entonces podemos eliminar varios números (nk\lfloor \frac{n}{k} \rfloor) en una pasada sin dar la vuelta. Después nos quedan nnkn - \lfloor \frac{n}{k} \rfloor números, y empezamos con el (nkk)(\lfloor \frac{n}{k} \rfloor \cdot k)-ésimo número. Así que tenemos que desplazar esa cantidad. Podemos notar que nkk\lfloor \frac{n}{k} \rfloor \cdot k es simplemente nmodk-n \bmod k. Y como eliminamos cada kk-ésimo número, tenemos que sumar el número de números que eliminamos antes del índice resultado. Lo cual podemos calcular dividiendo el índice resultado por k1k - 1.

También necesitamos manejar el caso en que nn se vuelve menor que kk. En este caso, la optimización anterior causaría un bucle infinito.

Implementación (por conveniencia con indexación desde cero):

int josephus(int n, int k) { if (n == 1) return 0; if (k == 1) return n-1; if (k > n) return (josephus(n-1, k) + k) % n; int cnt = n / k; int res = josephus(n - cnt, k); res -= n % k; if (res < 0) res += n; else res += res / (k - 1); return res; }

Estimemos la complejidad de este algoritmo. Nótese de inmediato que el caso n<kn < k se analiza con la solución antigua, que en este caso trabajará en O(k)O(k). Ahora consideremos el algoritmo en sí. De hecho, después de cada iteración, en lugar de nn números, nos quedan n(11k)n \left( 1 - \frac{1}{k} \right) números, así que el número total de iteraciones xx del algoritmo se puede hallar de forma aproximada a partir de la siguiente ecuación:

n(11k)x=1, n \left(1 - \frac{1}{k} \right) ^ x = 1,

al tomar logaritmo en ambos lados, obtenemos:

lnn+xln(11k)=0,\ln n + x \ln \left(1 - \frac{1}{k} \right) = 0, x=lnnln(11k),x = - \frac{\ln n}{\ln \left(1 - \frac{1}{k} \right)},

usando la descomposición del logaritmo en serie de Taylor, obtenemos una estimación aproximada:

xklnnx \approx k \ln n

Así, la complejidad del algoritmo es de hecho O(klogn)O (k \log n).

Solución analítica para k=2k = 2

En este caso particular (en el que esta tarea la planteó Flavio Josefo) el problema se resuelve mucho más fácil.

En el caso de nn par obtenemos que todos los números pares se tacharán, y luego quedará un problema restante para n2\frac{n}{2}, entonces la respuesta para nn se obtendrá de la respuesta para n2\frac{n}{2} multiplicando por dos y restando uno (desplazando posiciones):

J2n,2=2Jn,21 J_{2n, 2} = 2 J_{n, 2} - 1

De manera similar, en el caso de un nn impar, se tacharán todos los números pares, luego el primer número, y quedará el problema para n12\frac{n-1}{2}, y teniendo en cuenta el desplazamiento de posiciones, obtenemos la segunda fórmula:

J2n+1,2=2Jn,2+1J_{2n+1,2} = 2 J_{n, 2} + 1

Podemos usar esta dependencia recurrente directamente en nuestra implementación. Este patrón se puede traducir a otra forma: Jn,2J_{n, 2} representa una secuencia de todos los números impares, “reiniciándose” desde uno siempre que nn resulte ser una potencia de dos. Esto se puede escribir como una sola fórmula:

Jn,2=1+2(n2log2n)J_{n, 2} = 1 + 2 \left(n-2^{\lfloor \log_2 n \rfloor} \right)

Solución analítica para k>2k > 2

A pesar de la forma sencilla del problema y de un gran número de artículos sobre este y problemas relacionados, todavía no se ha hallado una representación analítica simple de la solución del problema de Josefo. Para kk pequeño se derivan algunas fórmulas, pero al parecer todas son difíciles de aplicar en la práctica (por ejemplo, véase Halbeisen, Hungerbuhler “The Josephus Problem” y Odlyzko, Wilf “Functional iteration and the Josephus problem”).