Saltar al contenido principal

Máximo de un arreglo

Consideremos el problema de buscar el máximo elemento en un arreglo A[1,n]A[1,n] mediante comparaciones. Es decir, los elementos de AA son cajas negras donde la única operación que podemos hacer sobre ellos es compararlos. Para simplificar, supondremos que todos los elementos son distintos.

El algoritmo más simple es tomar A[1]A[1] como máximo provisional, y luego comparar ese máximo con A[2]A[2], A[3]A[3], y así hasta A[n]A[n], manteniendo siempre el máximo visto hasta ahora. Para un arreglo A[1,n]A[1,n] requerimos entonces n1n-1 comparaciones.

Un algoritmo alternativo es el llamado "torneo de tenis": Comparamos A[1]A[1] con A[2]A[2], comparamos A[3]A[3] con A[4]A[4], A[5]A[5] con A[6]A[6], etc. Luego, en un nuevo arreglo de los n/2n/2 ganadores (mayores que el otro) en las comparaciones, volvemos a hacer una ronda de comparar a cada impar con el par que le sigue, y continuamos hasta tener un único ganador. Las comparaciones forman un árbol binario donde las nn hojas son los elementos de AA y cada nodo interno es una comparación, por lo que se hacen también n1n-1 comparaciones.

Cota inferior

Veamos que efectivamente se necesitan n1n-1 comparaciones para encontrar el máximo. Usaremos el modelo siguiente. El conocimiento que algoritmo tiene sobre el input es un grafo de nn nodos (1),,(n)(1),\ldots,(n). Cada vez que el algoritmo compara dos elementos A[i]A[i] y A[j]A[j], agregamos una arista entre los nodos (i)(i) y (j)(j). El estado inicial es el grafo sin aristas.

Para establecer los estados finales, notemos que un grafo no conexo no puede ser final. Si no existe un camino entre (i)(i) y (j)(j), el algoritmo no puede saber cuál es mayor a partir de las comparaciones realizadas, incluso aplicando transitividad. El adversario puede decidir que todos los elementos de la componente conexa de (i)(i) son mayores o menores que todos los de la de (j)(j) sin violar ninguna de las respuestas que ya ha dado. Por lo tanto, si el algoritmo declara que el máximo es un determinado A[i]A[i], existe un input para el cual el resultado es incorrecto si existe un A[j]A[j] en otra componente conexa.

Por lo tanto, los estados finales deben ser grafos conexos. Como se necesitan al menos n1n-1 aristas para conectar un grafo de nn nodos, n1n-1 es una cota inferior al problema de encontrar el máximo de un arreglo. Como tenemos algoritmos que usan n1n-1 comparaciones, sabemos que son óptimos y que esta cota inferior es ajustada, a pesar de que sólo consideramos una condición bastante débil acerca de lo que debe conocer el algoritmo sobre el input: nos bastó que existiera un camino de comparaciones que conectara a cualquier par de elementos.

Otro modelo

Consideremos ahora un modelo completamente distinto. El conocimiento del algoritmo sobre el input se describirá con tres variables (a,b,c)(a,b,c):

  • aa es el cardinal del conjunto AA de los elementos que nunca han sido comparados (no confundir con el arreglo AA);
  • bb es el cardinal del conjunto BB de los elementos que se han comparado alguna vez y han ganado (han resultado mayores) en todas sus comparaciones; y
  • cc es el cardinal del conjunto CC de los elementos que han perdido (han resultado menores) en alguna comparación.

El estado inicial es (n,0,0)(n,0,0), y está claro que el estado final debe ser (0,1,n1)(0,1,n-1), pues si a>0a>0 hay un elemento sin comparar (y el adversario se encargará de que ése sea el máximo) y si b>1b>1 hay dos elementos que han ganado todas sus comparaciones (y si el algoritmo declara ganador a uno de ellos el adversario puede decidir que es menor que el otro). Cada comparación mueve elementos dentro de la tupla (a,b,c)(a,b,c). Según de qué conjunto vengan los elementos que el algoritmo compara, la siguiente tabla indica los posibles nuevos estados a partir de un estado (a,b,c)(a,b,c):

ABCA(a2,b+1,c+1)(a1,b,c+1)(a1,b+1,c)(a1,b,c+1)B(a,b1,c+1)(a,b,c)(a,b1,c+1)C(a,b,c)\begin{array}{|c|c|c|c|} \hline & A & B & C \\ \hline A & (a-2,b+1,c+1) & (a-1,b,c+1) & (a-1,b+1,c) \\ & & & (a-1,b,c+1) \\ \hline B & & (a,b-1,c+1) & (a,b,c) \\ & & & (a,b-1,c+1) \\ \hline C & & & (a,b,c) \\ \hline \end{array}

Por ejemplo, el resultado de la celda A,A\langle A,A\rangle es el de comparar dos elementos que nunca habían sido comparados: ambos salen del conjunto AA, uno pasa a haber ganado todas sus comparaciones (BB) y otro a haber perdido alguna comparación (CC), por lo tanto el nuevo estado es (a2,b+1,c+1)(a-2,b+1,c+1). El caso A,B\langle A,B \rangle nos lleva a (a1,b,c+1)(a-1,b,c+1) independientemente de que el ganador sea el elemento de AA o el de BB (si el de AA gana, pasa a estar en BB pero el que estaba en BB pasa a CC). El caso A,C\langle A,C \rangle puede llevar a dos estados distintos dependiendo de quién gane la comparación. Como el elemento de AA nunca se había comparado, el adversario puede decidir cuál de los dos resultados es el que ocurrirá. En particular, el adversario podría elegir la estrategia de "los que han perdido siguen perdiendo", con lo cual elige (a1,b+1,c)(a-1,b+1,c) en este caso, y elige (a,b,c)(a,b,c) para el caso B,C\langle B,C \rangle (pues el adversario puede hacer que un elemento de BB sea tan grande como desee).

En cualquier caso, podemos observar que cc crece a lo sumo de a uno, y como debe pasar de 00 a n1n-1, se necesitan al menos n1n-1 comparaciones para llegar al estado final.

Esta técnica nos sugiere algoritmos óptimos más claramente que la del grafo. Primero es necesario comparar A,A\langle A,A \rangle. Luego, manteniendo b=1b=1, podemos seguir siempre comparando A,B\langle A,B \rangle (es decir, el único que ha ganado siempre contra uno que no se ha comparado), para mover los otros n2n-2 elementos de AA a CC. Puede verse que esta es la estrategia de nuestro primer algoritmo. Alternativamente, podemos utilizar A,A\langle A,A \rangle n/2n/2 veces, hasta vaciar AA y tener b=n/2b=n/2, y luego comparar B,B\langle B,B \rangle n/21n/2-1 veces hasta dejar un solo elemento en BB y el resto en CC. Note que en la segunda fase se comparan siempre ganadores con ganadores, lo que es compatible con nuestro algoritmo del torneo de tenis. Está claro que ningún algoritmo debería volver a comparar elementos de CC, pues el adversario podría hacer que no avance hacia el estado final.