He aquí un algoritmo sencillo y eficaz, expresado explícitamente en Python. Se parece al algoritmo euclidiano para calcular los máximos comunes divisores, y puede derivarse de las descripciones de otras respuestas en términos de fracciones continuas y/o del árbol de Stern-Brocot, pero también es sencillo dar una prueba de corrección que no utilice esos conceptos directamente. Primero daremos el código y luego haremos un esbozo de la prueba de corrección.
Aquí está el código:
def simplest_in_interval(r, s, u, v):
"""
Find simplest fraction in an open subinterval of the positive reals.
Given nonnegative integers r, s, u and v with r*v < s*u, return a pair
x, y of relatively prime positive integers such that x/y is the
simplest fraction in the open interval (r/s, u/v) (in the case v > 0),
or the simplest fraction in the interval (r/s, ∞) (in the case v = 0).
"""
a, b, c, d = 1, 0, 0, 1
while s <= r or u <= v:
q = r // s
r, u, b, d = r - q * s, u - q * v, b + q * a, d + q * c
q = v // u
s, v, a, c = s - q * r, v - q * u, a + q * b, c + q * d
return a + b, c + d
Una nota para los que no hablan Python: el //
en Python realiza la división por el suelo; es decir, a // b
da el valor de $\lfloor a/b\rfloor$ .
He aquí un ejemplo de la función anterior en acción: encontrar la fracción más simple en el intervalo (314/100, 315/100)
. (Tenga en cuenta que no es necesario que las fracciones de entrada se den en términos mínimos).
>>> simplest_in_interval(314, 100, 315, 100)
(22, 7)
Esquema de la prueba de corrección.
Escriba $J$ para el intervalo abierto especificado originalmente $(r/s, u/v)$ . A medida que el algoritmo avanza, mantenemos ocho variables de valor entero separadas: $a$ , $b$ , $c$ , $d$ , $r$ , $s$ , $u$ y $v$ . Ahora, en cada etapa del algoritmo, se mantienen las siguientes invariantes del bucle:
- $b$ , $c$ , $r$ y $v$ son no negativos
- $a$ , $d$ , $s$ y $u$ son positivos
- $rv < su$ (de hecho, $su - rv$ permanece constante), por lo que $r$ , $s$ , $u$ y $v$ determinar un subintervalo abierto $K = (r/s, u/v)$ de la recta real positiva (donde como caso especial permitimos $v=0$ para representar un punto final derecho de $\infty$ )
- $ad - bc = 1$ y $a$ , $b$ , $c$ y $d$ describir una transformación bilineal $T: x \mapsto (ax + b) / (cx + d)$
- $T$ mapas $K$ de forma biyectiva (y monótona) al intervalo original $J$ por lo que se establece una biyección preservadora del orden entre los racionales en $K$ y los de $J$
Los invariantes anteriores pueden establecerse de la forma habitual, comprobando que son verdaderos justo antes de que el while
se entra en el bucle por primera vez (en ese momento $K = J$ y $T$ es el mapa de identidad), y luego comprobando que si son verdaderos al comienzo de cualquier iteración del cuerpo del bucle, entonces también son verdaderos al final del cuerpo del bucle para esa iteración.
Cuando el while
sale del bucle, debemos tener $r < s$ y $v < u$ o en otras palabras, $r/s < 1 < u/v$ . Así que en ese momento el intervalo $K$ contiene $1$ . Ahora cada fracción en el intervalo original $J$ tiene la forma $(ap + bq) / (cp + dq)$ para algunos $p/q$ sur $K$ y asumiendo que $p/q$ se escribe en términos mínimos, se deduce del hecho de que $ad-bc = 1$ que $(ap + bq) / (cp + dq)$ también se escribe en términos mínimos. Como $a$ , $b$ , $c$ y $d$ son todas no negativas, la fracción más simple posible surge cuando $p$ y $q$ son mínimos, es decir, cuando $p = q = 1$ y esa fracción más simple es $(a + b) / (c + d)$ .
Hay una última cosa que debemos saber, y es que el while
bucle hace salida - es decir, que el while
la condición del bucle finalmente se convierte en falsa, y que no entramos simplemente en un bucle infinito. Para ello, basta con observar que en cada iteración del bucle, o bien $r$ o $v$ disminuye, y ninguno aumenta. Dado que ambos $r$ y $v$ permanecen no negativas, no podemos continuar esos decrecimientos indefinidamente, por lo que el bucle debe terminar después de un número finito de iteraciones. Esto completa el esbozo de la prueba.