estoy entrometerse en este antiguo hilo de dar una explicación detallada de por qué funciona el algoritmo de kadane. El algoritmo fue presentado en una clase que estoy tomando actualmente, pero con solo una vaga explicación. Aquí está una implementación del algoritmo en Haskell:
maxCont l = maxCont' 0 0 l
maxCont' maxSum _ [] = maxSum
maxCont' maxSum thisSum (x:xs)
| newSum > maxSum = maxCont' newSum newSum xs
| newSum < 0 = maxCont' maxSum 0 xs
| otherwise = maxCont' maxSum newsum xs
where
newSum = thisSum + x
Ahora ya sólo estamos tratando de entender el algoritmo, vamos a deshacer la optimización de menor importancia de nombrar newSum
:
maxCont l = maxCont' 0 0 l
maxCont' maxSum _ [] = maxSum
maxCont' maxSum thisSum (x:xs)
| thisSum + x > maxSum = maxCont' (thisSum + x) (thisSum+x) xs
| thisSum + x < 0 = maxCont' maxSum 0 xs
| otherwise = maxCont' maxSum (thisSum+x) xs
¿Cuál es esta función loca maxCont'
? Vamos a dar con una simple especificación de lo que se supone que debe hacer. Queremos que el siguiente para sostener, con la condición de que 0 ≤ thisSum ≤ maxSum
:
maxCont' maxSum thisSum [] = maxSum
maxCont' maxSum thisSum l = maximum [maxSum, thisSum + maxInit l, maxCont l]
donde maxInit l
es la mayor suma de un segmento inicial de l
y maxCont
es la suma máxima contigua de l
.
Hecho trivial pero importante: para todos l
, maxInit l ≤ maxCont l
. Debería ser obvio que la especificación anterior garantiza maxCont l = maxCont' 0 0 l
, que es lo que queremos. En lugar de tratar de explicar directamente por qué la versión final de maxCont 'implementa la especificación anterior (que realmente no sé cómo hacer), mostraré cómo se puede derivar de ella, transformando la especificación un paso a la vez hasta se convierte en el código, que sin duda será correcto. Tal como está escrito, esta especificación no proporciona una implementación: si se define maxCont
en términos de maxCont'
como se describió anteriormente, se repetirá para siempre como maxCont'
llama a maxCont
llama a maxCont'
con la misma lista. Así que vamos a ampliar a cabo sólo un poco para exponer las piezas que necesitaremos:
maxCont' maxSum thisSum (x:xs) =
maximum [maxSum, thisSum + maxInit (x:xs), maxCont (x:xs)]
Esto no se soluciona nada todavía, pero expuestos cosas. Usemos eso. thisSum + maxInit (x:xs)
es thisSum
o thisSum + x + maxInit xs
. Pero thisSum ≤ maxSum
por la condición previa, por lo que podemos ignorar esta posibilidad al calcular el máximo. maxCont (x:xs)
es una suma que incluye x
o no. Pero si incluye x
, entonces es lo mismo que maxInit (x:xs)
, que está cubierto por el anterior, por lo que podemos ignorar esa posibilidad, y solo considerar el caso donde maxCont (x:xs) = maxCont xs
. Así, llegamos a la siguiente versión:
maxCont' maxSum thisSum (x:xs) = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs]
Ésta, por último, es propiamente recursiva, pero tenemos mucho camino por recorrer para llegar a un código eficiente, sobre todo debido a que maxInit mítica sería demasiado lento. Vamos a romper hacia abajo en los tres casos considerados en el código Java (abuso de notación Haskell un poco):
maxCont' maxSum thisSum (x:xs)
| maxSum < thisSum + x = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs]
| thisSum + x < 0 = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs]
| 0 ≤ thisSum + x ≤ maxSum = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs]
En el primer caso, sabemos que maxSum
no puede ser la máxima: thisSum+x
es mayor y maxInit xs
es siempre positivo. En el segundo caso, sabemos que thisSum+x+maxInit xs
no puede ser el máximo: maxCont xs
siempre es al menos tan grande como maxInit xs
, y thisSum+x
es negativo.Así que podemos eliminar esas posibilidades:
maxCont' maxSum thisSum (x:xs)
| maxSum < thisSum + x = maximum [ thisSum+x+maxInit xs, maxCont xs]
| thisSum + x < 0 = maximum [maxSum, maxCont xs]
| 0 ≤ thisSum + x ≤ maxSum = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs]
Ahora tenemos apenas una ventaja suficiente para cambiar las cosas. Ahora que hemos eliminado casos imposibles, vamos a agregar algunos casos duplicados, que colocarán estos tres casos en la misma forma para que podamos sustituirlos por la especificación original maxCont'
. En el primer caso, no tenemos un primer término, por lo que debemos usar algo que sabemos que no excederá los otros términos. Para mantener el invariante que thisSum ≤ maxSum
, necesitaremos usar thisSum+x
. En el segundo caso, no tenemos un segundo término que se parece a something+maxInit xs
, pero sabemos que maxInit xs ≤ maxCont xs
, por lo que podemos pegar con seguridad en 0+maxInit xs
. La incorporación de estos términos adicionales para la regularidad se obtiene la siguiente:
maxCont' maxSum thisSum (x:xs)
| maxSum < thisSum + x = maximum [(thisSum+x), (thisSum+x)+maxInit xs, maxCont xs]
| thisSum + x < 0 = maximum [maxSum, 0+maxInit xs, maxCont xs]
| 0 ≤ thisSum + x ≤ maxSum = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs]
Por último, después de haber comprobado la condición previa, sustituimos en la especificación,
maxCont' maxSum thisSum l = maximum [maxSum, thisSum + maxInit l, maxCont l]
para obtener
maxCont' maxSum thisSum (x:xs)
| maxSum < thisSum + x = maxCont' (thisSum+x) (thisSum+x) xs
| thisSum + x < 0 = maxCont' maxSum 0 xs
| 0 ≤ thisSum + x ≤ maxSum = maxCont' maxSum (thisSum+x) xs
La fijación de este arriba en la sintaxis real y las tachuelas en el caso base omitido dan como resultado el algoritmo real, que ahora hemos demostrado que cumple con la especificación siempre que termine. Pero cada paso sucesivo recursivo opera en una lista más corta, por lo que de hecho termina.
Sólo hay una última cosa que hacer, por mi causa, que es escribir el código final más idiomáticamente y flexible:
maxCont :: (Num a, Ord a) => [a] -> a
maxCont = fst . foldl maxCont' (0,0)
where
maxCont' (maxSum, thisSum) x
| maxSum < newSum = (newSum, newSum)
| newSum < 0 = (maxSum, 0)
| otherwise = (maxSum, newSum)
where newSum = thisSum + x
De qué tipo son los números? Dice enteros en el título, "números reales" en la pregunta. Los "números reales" suenan como números negativos y los números flotantes están permitidos, los "enteros" suenan bastante al revés. – schnaader
Gracias por capturar eso. En verdad, no importa si son enteros o reales, siempre que no sean complejos y que los negativos estén permitidos. – PengOne
Estaba esperando algo como esto SOLO para que pueda decir: esto pertenece a http://cstheory.stackexchange.com – Kiril