Teoría de la complejidad computacional: ¿Por qué algunas operaciones se consideran primitivas mientras que otras no?

Si está contando operaciones, entonces las declaraciones en la condición deben contarse.

Sin embargo, el autor del artículo (Michal Forišek) no cuenta operaciones primitivas para determinar la complejidad. Tiene una razón diferente para mencionar que j++ se ejecuta no más de [math] N [/ math] veces.

Lo que realmente está haciendo es razonar a un alto nivel cuántas veces se ejecuta el bucle interno y cuántas veces se ejecuta el bucle externo en el transcurso del algoritmo.

Permítanme repetir lo que está tratando de decir:

Después de que el bucle interno se ejecute [matemática] N-1 [/ matemática] veces, [matemática] j [/ matemática] se establecerá en [matemática] N-1 [/ matemática]. Cuando eso sucede, salimos del bucle interno y nunca volvemos a entrar en él (porque una de las condiciones del bucle interno es que lo ingresamos solo cuando [math] j <N-1 [/ math]). Por lo tanto, el bucle interno no puede ejecutarse más de [matemáticas] N-1 [/ matemáticas] veces.

Además, el bucle externo no se ejecuta más de [math] N [/ math] veces antes de salir de él.

Por lo tanto, en el transcurso de la ejecución de todo el algoritmo , el bucle interno ejecuta [matemática] N-1 [/ matemática] veces en el peor de los casos, y el bucle externo ejecuta [matemática] N [/ matemática] veces en la peor caso.

Sumando la cantidad de veces que se ejecuta cada ciclo, obtenemos:
[matemáticas] O (N-1 + N) = O (N) [/ matemáticas]

(Nota: la razón por la que agregamos la cantidad de veces que se ejecuta el bucle externo a la cantidad de veces que se ejecuta el bucle interno en lugar de multiplicarse es que el bucle interno ejecuta [math] N-1 [/ math] veces en todas las iteraciones del bucle externo bucle, no [matemática] N-1 [/ matemática] veces por iteración del bucle externo).

La respuesta de John Kurlak es probablemente lo que está buscando, pero me gustaría agregar una explicación esclarecedora y algo de intuición, así como una prueba de la corrección del algoritmo.

Realicemos un experimento mental simple. En este experimento mental asumiré que tienes un hijo.

Imagine que usted y su hijo están subiendo un tramo de escaleras. Debido a que siempre desea que su hijo esté cerca de usted, decide que su hijo debe estar a una distancia [matemática] D [/ matemática] de usted (o menos) antes de que pueda subir a la siguiente escalera. Suponiendo que se necesita [matemática] 1 [/ matemática] segundo para subir una sola escalera y hay N escaleras, ¿cuál es el tiempo máximo que les tomará a usted y a su hijo subir toda la escalera?

¡Resulta que la respuesta a la pregunta anterior es exactamente la peor complejidad del algoritmo! No es difícil ver que, como máximo, [matemática] 2N [/ matemática] segundos para que usted y su hijo suban toda la escalera.

¿Por qué?

Porque en el peor de los casos, su hijo debe subir exactamente la misma cantidad de escaleras que usted, independientemente de la distancia (en cualquier momento, usted o su hijo están subiendo). Como lleva [matemática] N [/ matemática] segundos llegar a la cima, también debería llevarle a su hijo [matemática] N [/ matemática] segundos, por lo que ambos deben gastar como máximo [matemática] 2N [ / matemáticas] segundos subiendo las escaleras. Esto nos da una complejidad temporal de [matemáticas] O (N) [/ matemáticas].

Ahora, volvamos al problema original. Piense en el índice [matemáticas] i [/ matemáticas] como la escalera actual en la que está parado (comenzando desde 0) y piense en [matemáticas] j [/ matemáticas] como la escalera actual en la que está parado su hijo. ¡Podemos ver que el experimento mental y el algoritmo son exactamente iguales ya que [matemática] A [i] -A [j] [/ matemática] es simplemente la distancia entre usted y su hijo!

La siguiente pregunta es ¿por qué funciona el algoritmo?

Para responder a esta pregunta, definamos [math] j_ {min} (i) [/ math] como el índice mínimo en la matriz [math] A [/ math] para el cual [math] A [i] -A [j_ {min} (i)] \ le D [/ math] (suponga [math] D \ ge 0 [/ math]). Tenga en cuenta que [math] j_ {min} (i + 1) \ ge j_ {min} (i) [/ math] ya que la matriz está ordenada en orden no decreciente. También tenga en cuenta que según nuestra definición de [matemáticas] j_ {min} (i) [/ matemáticas], si para un cierto [matemáticas] i [/ matemáticas] y [matemáticas] j [/ matemáticas], [matemáticas] A [i ] -A [j] = D [/ math], luego [math] A [j] = A [j_ {min} (i)] [/ math], por lo que es suficiente seguir buscando este índice [math] j_ {min} (i) [/ math] para cada índice [math] i [/ math] en nuestra matriz.

Daré una prueba más detallada de la corrección del algoritmo de la siguiente manera.

Una invariable para notar es que después del ciclo while, el algoritmo siempre tiene el índice mínimo [matemática] j [/ matemática] para una [matemática] i [/ matemática] dada para la cual [matemática] A [i] -A [j ] \ le D [/ matemáticas]. Esto se puede mostrar fácilmente por inducción en [math] i [/ math].

Prueba de invariante.
El caso base es trivial ya que para [matemática] i = j = 0 [/ matemática], sabemos que [matemática] j = 0 [/ matemática] es el índice mínimo para [matemática] i = 0 [/ matemática] para el cual [matemática] A [i] -A [j] \ le D [/ matemática], porque [matemática] D \ ge 0 [/ matemática].
Ahora, suponga que para [matemáticas] i = t [/ matemáticas] el algoritmo obtiene correctamente el valor mínimo de [matemáticas] j [/ matemáticas] (digamos [matemáticas] j_t [/ matemáticas]) para el cual [matemáticas] A [i ] -A [j] \ le D [/ matemáticas]. es decir, [matemáticas] A [t] -A [j_t] \ le D [/ matemáticas]. Ahora mostramos que para [math] i = t + 1 [/ math] el algoritmo también obtiene correctamente el índice más pequeño [math] j_ {t + 1} [/ math] para el cual [math] A [t + 1] – A [j_ {t + 1}] \ le D [/ matemáticas].
Tenga en cuenta que [math] j_ {t + 1} \ ge j_t [/ math] ya que [math] A [t + 1] \ ge A [t] [/ math] y, en consecuencia, [math] A [t + 1] -A [j_t] \ ge A [t] – A [j_t] [/ math] y más [math] j_t [/ math] es el índice mínimo para el cual [math] A [t] -A [j_t] \ le D [/ matemáticas].
Como el ciclo while interno continúa desde [math] j = j_t [/ math] hasta que encuentra un nuevo índice [math] j = j_ {t + 1} [/ math], sabemos que [math] j_ {t + 1 } [/ math] también debe ser el índice mínimo para nuestra [math] i = t + 1 [/ math] dada.
Entonces lo invariante también se sostiene.

Continuación de la prueba original.
Ahora que hemos demostrado que la invariante siempre es válida, observamos que para una matriz dada [matemática] A [/ matemática], hay dos posibilidades. O bien [math] A [/ math] contiene dos números con una diferencia igual a [math] D [/ math] o no lo tiene.

Suponga que la matriz [math] A [/ math] no contiene ninguno de estos pares. Dado que el algoritmo solo devuelve [matemática] 1 [/ matemática] si encuentra algún par [matemática] (i, j) [/ matemática] para la cual [matemática] A [i] -A [j] = D [/ matemática] , el algoritmo nunca genera un [math] 1 [/ math], por lo tanto, da el resultado correcto.

Ahora para el segundo caso, suponga que el algoritmo tiene un par [matemática] (p, q) [/ matemática] para la cual [matemática] A [p] -A [q] = D [/ matemática]. Además, deje que [math] (p, q) [/ math] sea el mínimo de tales índices. es decir, cualquier otro par con diferencia [matemática] D [/ matemática] tiene el índice mayor mayor que [matemática] p [/ matemática] y el índice menor mayor que [matemática] q [/ matemática]. Mostramos que el algoritmo encuentra correctamente [math] p [/ math] y [math] q [/ math]. Para mostrar esto, considere el punto cuando [matemáticas] i = p [/ matemáticas]. Usando nuestro invariante descrito anteriormente, notamos que nuestro valor de [math] j [/ math] en este punto es exactamente el índice mínimo para el cual [math] A [i] -A [j] \ le D [/ math]. Pero notamos que este índice mínimo debe ser [math] q [/ math] por nuestra suposición, por lo tanto, [math] j = q [/ math]. En particular, el algoritmo encuentra correctamente [math] (p, q) [/ math]. Por lo tanto, el algoritmo genera correctamente [matemáticas] 1 [/ matemáticas]

¡Espero que esto ayude!