Está en la página 1de 24

2 Primeros pasos

Este capítulo le permitirá familiarizarse con el marco vamos a utilizar a lo largo del libro para pensar en el
diseño y análisis de algoritmos. Es en sí misma, sino que incluye varias referencias a material que
presentamos en los capítulos 3 y 4. (que también contiene varias sumas, que el Apéndice A muestra cómo
resolver.)
Comenzamos examinando el algoritmo de ordenación por inserción para resolver el problema de
clasificación introducido en el capítulo 1. Se define un “pseudo” que debe ser familiar para usted si ha
hecho la programación informática, y lo usamos para mostrar la forma en que especificaremos nuestros
algoritmos. Después de haber especificado el algoritmo de ordenación por inserción, que luego
argumentar que ordena correctamente, y analizamos su tiempo de ejecución. El análisis presenta una
notación que se centra en la forma en que el tiempo aumenta con el número de elementos a ser ordenados.
Después de nuestra discusión de ordenación por inserción, se introduce el enfoque de divide y vencerás
para el diseño de algoritmos y utilizarlo para desarrollar un algoritmo llamado de mezcla tipo.
Terminamos con un análisis de tiempo de ejecución de combinación de tipo.
2.1 La ordenación por inserción
Nuestro primer algoritmo, ordenación por inserción, resuelve el problema de clasificación introducido en
pítulo ter 1:
De entrada: una secuencia de números N ha
1

;un
2

;:::;un
norte

yo. Salida: Una permutación (reordenamiento) que a0


1

Ä a0 2
Ä Ä a0
norte

.
HA0 1
; a0 2
; :::; a0
norte

i de la secuencia de entrada, tal


Los números que deseamos para ordenar también se conocen como las llaves. Aunque aliado
conceptualmente estamos ordenando una secuencia, la entrada viene a nosotros en la forma de una matriz
con n elementos.
En este libro, vamos a describir típicamente algoritmos como los programas escritos en un
pseudocódigo que es similar en muchos aspectos a C, C ++, Java, Python, o Pascal. Si usted ha sido
introducido a cualquiera de estos idiomas, usted debe tener pocos problemas
2.1 La ordenación por inserción 17
2

♣4♣


♣ 5 ♣ ♣ ♣ ♣♣ ♣ ♣ ♣

2 10 ♣

♣♣♣♣♣4♣♣
♣♣7


♣♣5♣♣
♣♣♣
♣♣
10 7
♣♣


Figura 2.1 Clasificación de una mano de cartas utilizando ordenación por inserción.

leer nuestros algoritmos. Lo que separa pseudocódigo a partir del código “real” es que en pseudocódigo,
empleamos el método que es más expresiva clara y concisa para especificar un algoritmo dado. A veces,
el método más claro es el Inglés, por lo que no se sorprenda si se encuentra con una frase u oración Inglés
incrustado dentro de una sección de código “real”. Otra diferencia entre pseudocódigo y el código real es
que pseudocódigo no se preocupa por lo general con las cuestiones de ingeniería de software. Las
cuestiones de la abstracción de datos, modularidad y manejo de errores se ignoran a menudo con el fin de
transmitir la esencia del algoritmo de manera más concisa.
Comenzamos con ordenación por inserción, que es un algoritmo eficiente para la clasificación de un
pequeño número de elementos. La ordenación por inserción funciona de la manera que muchas personas
ordenar una mano de cartas. Comenzamos con una mano vacía izquierda y las cartas boca abajo sobre la
mesa. Eliminamos después una carta a la vez de la mesa y la inserta en la posición correcta en la mano
izquierda. Para encontrar la posición correcta para una tarjeta, lo comparamos con cada una de las tarjetas
que ya están en la mano, de derecha a izquierda, como se ilustra en la Figura 2.1. En todo momento, las
cartas que tiene en la mano izquierda están ordenados, y estas tarjetas eran originalmente las cartas
superiores de la pila sobre la mesa.
Presentamos nuestra pseudocódigo para ordenar la inserción como un procedimiento llamado
ORDENAR la Inserción, que toma como parámetro un AŒ1 matriz: n ?? que contiene una secuencia de
longitud n que se va a clasificar. (En el código, el número n de elementos en A se denota por A:. De
longitud) El algoritmo ordena los números de entrada en su lugar: se reorganiza los números dentro de la
matriz A, con a lo sumo un número constante de ellos almacenado fuera de la matriz en cualquier
momento. La matriz de entrada A contiene la secuencia de salida ordenada cuando se termina el
procedimiento de inserción-SORT.
18 Capítulo 2 Introducción
1 2 3 4 5 6 (a)
524613123456
(segundo)
254613123456
(C) 2 4 5 6 1 3 1 2 3 4 5 6 (d)
245613123456
1 2 3 4 5 6 (e)
1 2 4 5 6 3 (f) 1 2 3 4 5 6 Figura 2.2 El funcionamiento de I
NSERTION
-S
ORT
en los h5 array AD; 2; 4; 6; 1; 3i. índices de matriz aparecen por encima de los
rectángulos, y los valores almacenados en las posiciones de la red aparecen dentro de los rectángulos. (A) - (e) Las iteraciones del
bucle de las líneas 1-8. En cada iteración, el rectángulo negro tiene la llave tomada de AŒj ??, que se compara con los valores en
rectángulos sombreados a su izquierda en la prueba de la línea 5. Sombreado flechas muestran valores de la matriz se trasladaron
una posición a la derecha en la línea 6, y flechas negras indican donde la clave se mueve a en la línea 8. (f) La matriz ordenada
final.

INSERCIÓN-SORT.A / 1 para j D 2 a A: longitud 2 clave D AŒj ?? 3 // Insertar AŒj ?? en la secuencia


ordenada AŒ1:: j 1 ??. 4 i D j 1 5 mientras i> 0 y AŒi ?? > Tecla 6 AŒi C 1 ?? D AŒi ?? 7 i D i 1 8
AŒi C 1 ?? tecla D
invariantes de bucle y la corrección de ordenación por inserción
La figura 2.2 muestra cómo este algoritmo funciona para h5 AD; 2; 4; 6; 1; 3i. El dex in- j indica la
“tarjeta actual” que se inserta en la mano. Al comienzo de cada iteración del bucle, que está indexado por
j, el subconjunto constituido por elementos AŒ1:: j 1 ?? constituye la parte actualmente ordenados, y el
subconjunto restante AŒj C1 :: n ?? corresponde a la pila de tarjetas sigue sobre la mesa. De hecho, los
elementos AŒ1:: j 1 ?? son los elementos originalmente en las posiciones 1 a través de j 1, pero ahora en
orden clasificado. Decimos estas propiedades de AŒ1:: j 1 ?? formalmente como una invariante de bucle:
Al comienzo de cada iteración del bucle de las líneas 1-8, el AŒ1 subarreglo:: j 1 ?? se compone de los
elementos originalmente en AŒ1:: j 1 ??, pero en orden clasificado.
Utilizamos invariantes de bucle para ayudar a entender por qué un algoritmo es correcto. Debemos
demostrar tres cosas acerca de un invariante de bucle:
2.1 La ordenación por inserción 19

Inicialización: Es cierto antes de la primera iteración del bucle.


Mantenimiento: Si es verdad antes de una iteración del bucle, sigue siendo cierto antes de la
siguiente iteración.
Terminación: Cuando el ciclo termina, el invariante nos da una propiedad útil
que ayuda a mostrar que el algoritmo es correcto.
Cuando las dos primeras propiedades se mantienen, el invariante bucle es cierto antes de cada iteración
del bucle. (Por supuesto, tenemos la libertad de utilizar hechos establecidos que no sea el propio bucle
invariante para demostrar que el invariante de bucle sigue siendo cierto antes de cada iteración.) Tenga en
cuenta la similitud de la inducción matemática, donde para demostrar que una propiedad se mantiene, a
probar un caso base y un paso inductivo. Aquí, mostrando que el invariante mantiene antes de la primera
iteración corresponde al caso base, y que muestra que el invariante sostiene de iteración a iteración
corresponde a la etapa inductiva.
La tercera propiedad es tal vez el más importante, ya que estamos utilizando el invariante de bucle para
mostrar la corrección. Típicamente, se utiliza el invariante de bucle junto con la condición que provocó el
bucle para terminar. La propiedad terminación difiere de la forma en que suelen utilizar la inducción
matemática, en la que aplicamos el paso inductivo infinitamente; aquí, nos detenemos la “inducción”
cuando el ciclo termina.
Veamos cómo estas propiedades se mantienen para la ordenación por inserción.
Inicialización: Comenzamos mostrando que el invariante de bucle mantiene antes de la primera iteración
del bucle, cuando j D 2.1 El AŒ1 subarreglo:: 1 j ??, por lo tanto, consiste en simplemente el único
elemento AŒ1 ??, que es de hecho el elemento original, en AŒ1 ??. Además, esta submatriz está
ordenada (trivialmente, por supuesto), que muestra que el invariante bucle mantiene antes de la primera
iteración del bucle.
Mantenimiento: A continuación, abordamos la segunda propiedad: mostrando que cada iteración
mantiene invariable el bucle. De manera informal, el cuerpo de la para las obras de bucle moviendo AŒj
1 ??, AŒj 2 ??, AŒj 3 ??, y así sucesivamente por una posición a la derecha hasta que encuentra la
posición adecuada para AŒj ?? (líneas 4-7), en cuyo punto se inserta el valor de AŒj ?? (Línea 8). El
AŒ1 subarreglo:: j ?? a continuación, se compone de los elementos originalmente en AŒ1:: j ??, pero en
orden clasificado. Incrementar j para la siguiente iteración del bucle for entonces conserva la invariante
bucle. Un tratamiento más formal de la segunda propiedad exigiría a estado y mostrar una invariante de
bucle para el bucle while de las líneas 5-7. En este punto, sin embargo,
1Cuando el bucle es un bucle, el momento en que comprobamos el invariante bucle justo antes de la primera iteración es
inmediatamente después de la asignación inicial a la variable de bucle de venta libre y justo antes de la primera prueba en la
cabecera del bucle. En el caso de inserción-ORDENAR, este tiempo es después de asignar 2 a la variable j pero antes de la
primera prueba de si j Ä A: longitud.
20 Capítulo 2 Introducción

preferimos no enredarse en tales formalismo, y así nos basamos en nuestro análisis informal para
demostrar que la segunda propiedad se cumple para el bucle externo. Terminación: Finalmente, se analiza
lo que sucede cuando el ciclo termina. La condición que causa el bucle de terminar es que j> A: longitud
D n. Debido a que cada iteración del bucle aumenta j por 1, debemos tener j D n C 1 en ese momento.
Sustituyendo n C 1 para j en la redacción de invariante bucle, tenemos que el AŒ1 subarreglo:: n ?? se
compone de los elementos originalmente en AŒ1:: n ??, pero en orden clasificado. Observando que la
AŒ1 subarreglo: n ?? es todo el conjunto, llegamos a la conclusión de que toda la matriz está ordenada.
Por lo tanto, el algoritmo es correcto.
Vamos a utilizar este método de invariantes de bucle para mostrar la corrección más adelante en este
capítulo y en otros capítulos también.
convenciones pseudocódigo
Nosotros utilizamos las siguientes convenciones en nuestro pseudocódigo.
Sangría indica estructura de bloque. Por ejemplo, el cuerpo del bucle que comienza en la línea 1 consiste
en líneas 2-8, y el cuerpo del bucle, mientras que comienza en la línea 5 contiene líneas 6-7, pero no la
línea 8. Nuestro estilo de indentación se aplica a IF statements2 demás también. Uso de indentación en
lugar de los indicadores convencionales de estructura de bloque, tales como comenzar y estados finales,
reduce en gran medida el desorden preservando al mismo tiempo, o incluso mejorando, clarity.3 Las
construcciones de bucle, mientras que, para, y repetir-hasta y el constructo condicional else if-tener
interpretaciones similares a los de C, C ++, Java, Python, y Pascal.4 en este libro, el contador de bucle
retiene su valor después de salir del bucle, a diferencia de algunas situaciones que surgen en C ++, Java, y
Pascal. Por lo tanto, inmediatamente después de un bucle, el valor del contador de bucles es el valor que
sobrepase por primera vez el bucle unido. Utilizamos esta propiedad en nuestro argumento corrección
para la ordenación por inserción. El bucle de cabeza en la línea 1 es para j D 2 a A: longitud, y así, cuando
este bucle termina, j DA: longitud C 1 (o, equivalentemente, j D n C 1, ya que n DA: longitud). Usamos
la palabra clave para cuando un bucle incrementa su bucle
2
En una sentencia if-else, Nos guión más al mismo nivel que su juego si. Aunque se omite la palabra clave entonces, de vez en
cuando nos referimos a la parte ejecutada cuando la prueba siguiente si es verdad como una continuación cláusula. Para las
pruebas de múltiples vías, utilizamos elseif para las pruebas después de la primera.
3
Cada procedimiento pseudocódigo en este libro aparece en una página de modo que usted no tendrá que discernir los niveles de
sangría en el código que se divide en varias páginas.
4La mayoría idiomas de bloque estructurado tienen construcciones equivalentes, aunque la sintaxis exacta puede variar. Python
carece de repetir-hasta bucles, y su bucles funciona un poco diferente de la de los bucles en este libro.
2.1 La ordenación por inserción 21

contador en cada iteración, y usamos la palabra clave downto cuando un bucle decrementa su contador de
bucle. Cuando los cambios de control del bucle por una cantidad mayor que 1, la cantidad de cambio
sigue la palabra clave opcional por.
El símbolo “//” indica que el resto de la línea es un comentario. Una asignación múltiple de la forma I D j
D e asigna a ambas variables i y j el valor de la expresión de E; debe ser tratado como equivalente a la
asignación j D E seguida de la asignación i D j.
Variables (tales como i, j, y la llave) son locales con el procedimiento dado. No vamos a utilizar variables
globales sin indicación explícita. Tenemos acceso a elementos de la matriz especificando el nombre de la
matriz seguido de la in- dex entre corchetes. Por ejemplo, AŒi ?? indica el i-ésimo elemento de la matriz
A. La notación “::” se utiliza para indicar una gama de valores dentro de una matriz. Por lo tanto, AŒ1:: j
?? indica el subconjunto de A que consiste en la AŒ1 elementos j ??; AŒ2 ??; :::; AŒj ??.
Por lo general organizar los datos en objetos compuestos, que se componen de atributos. Tenemos acceso
a un atributo particular utilizando la sintaxis que se encuentra en muchos lenguajes de programación
orientados a objetos: el nombre del objeto, seguido de un punto, seguido por el nombre del atributo. Por
ejemplo, tratamos a una matriz como un objeto con la longitud atributo que indica cuántos elementos que
contiene. Para especificar el número de elementos de una matriz A, escribimos A: longitud. Tratamos a
una variable que representa una matriz u objeto como un puntero a los datos que representan la matriz o
un objeto. Para todos los atributos de un objeto f x, el establecimiento y D x causa Y: f a la igualdad de x:
f. Por otra parte, si ahora conjunto X: f D 3, a continuación, después no solo x: f igual 3, pero y: f es igual
a 3 también. En otras palabras, X e Y punto al mismo objeto después de la asignación y D x. Nuestra
notación atributo puede “en cascada”. Por ejemplo, supongamos que el atributo f es en sí mismo un
puntero a algún tipo de objeto que tiene un atributo g. A continuación, la notación x: f: g se entre
paréntesis implícitamente como .X: f /: g. En otras palabras, si hubiéramos asignado y D x: f, entonces x:
f: g es el mismo que y: g. A veces, un puntero hará referencia a ningún objeto en absoluto. En este caso, le
damos el valor NIL especial.
Nos pasar parámetros a un procedimiento por el valor: el procedimiento llamado recibe su propia copia de
los parámetros, y si se asigna un valor a un parámetro, el cambio no es visto por el procedimiento de
llamada. Cuando se pasan los objetos, el puntero a los datos que representan el objeto se copia, pero los
atributos del objeto no lo son. Por ejemplo, si x es un parámetro de un procedimiento llamado, la
asignación x D y dentro del procedimiento llamado no es visible para el procedimiento de llamada. La
asignación x: f D 3, sin embargo, es visible. Del mismo modo, las matrices se pasan por el puntero, por lo
que
22 Capítulo 2 Introducción

un puntero a la matriz se pasa, en lugar de toda la matriz, y los cambios en los elementos de matriz
individuales son visibles para el procedimiento de llamada. Una instrucción de retorno inmediatamente
transfiere el control al punto de escala en el procedimiento de llamada. La mayoría de las sentencias de
retorno también toman un valor para pasar de nuevo a la persona que llama. Nuestra pseudocódigo se
diferencia de muchos lenguajes de programación en que permitimos múltiples valores a ser devueltos en
una sola instrucción de retorno. Los operadores lógicos “y” y “o” son un cortocircuito. Es decir, cuando
se evalúa la “X e Y” de expresión, primero evaluamos x. Si x se evalúa como FALSO, entonces toda la
expresión no puede evaluar a TRUE, por lo que no evalúan y. Si, por el contrario, x se evalúa como
TRUE, debemos evaluar y determinar el valor de toda la expresión. Similar, en la “X o Y” expresión que
eva- luar la expresión y sólo si x se evalúa como falsa. Los operadores de los cortocircuitos nos permiten
escribir expresiones booleanas como “x ¤ NIL y x: f D y” sin preocuparse de lo que sucede cuando
tratamos de evaluar x: f cuando x es nulo. El error de palabra clave indica que se produjo un error porque
las condiciones eran incorrectos para que el procedimiento ha sido llamado. El procedimiento de llamada
se respon- sable para manejar el error, por lo que no se especifica qué acción tomar.
Ceremonias
2.1-1 Utilizando la figura 2.2 como modelo, ilustran el funcionamiento de la inserción-ORDENAR en la
matriz h31 AD; 41; 59; 26; 41; 58i.
2.1-2 reescribir el procedimiento de inserción-sort para ordenar en orden no creciente en lugar de no
disminuir.
2.1-3 Considere el problema de búsqueda: Entrada: Una secuencia de n números de AD ha
1

;un
2

;:::;un
norte

i y un valor. Salida: Un índice i tal que D AŒi ?? o


el valor NIL especial si no lo hace
A. aparecer en pseudocódigo de escritura para búsqueda lineal, que explora a través de la secuencia,
buscando. El uso de un invariante de bucle, demuestran que su algoritmo es correcto. Asegúrese de que su
invariantes de bucle cumple con las tres propiedades necesarias.
2.1-4 considerar el problema de la adición de dos números enteros binarios de n bits, almacenados en dos
matrices de n elementos A y B. La suma de los dos números enteros debe almacenarse en forma binaria
en
2.2 Análisis de algoritmos
El análisis de un algoritmo ha llegado a significar la predicción de los recursos que el algoritmo requiere.
De vez en cuando, los recursos tales como la memoria, el ancho de banda de comunicación, o de equipos
informáticos son de interés primordial, pero más a menudo es Compu tiempo tational que se quiere medir.
Por lo general, mediante el análisis de varios algoritmos de candidatos para un problema, podemos
identificar una más eficiente. Tal análisis puede indicar más de un candidato viable, pero a menudo puede
descartar varios algoritmos inferiores en el proceso.
Para poder analizar un algoritmo, debemos tener un modelo de la tecnología tación aplica- que vamos a
utilizar, incluyendo un modelo para los recursos de esa tecnología y sus costos. Para la mayor parte de
este libro, supondremos un modelo genérico procesador de una, máquina de acceso aleatorio (RAM) de
cálculo que se imple- mentación nuestra tecnología y entender que nuestros algoritmos se implementan
como programas de ordenador. En el modelo de RAM, las instrucciones se ejecutan, uno tras otro an-, sin
operaciones simultáneas.
Estrictamente hablando, debemos definir con precisión las instrucciones del modelo de RAM y sus costes.
Para ello, sin embargo, sería tedioso y produciría poca penetración en el diseño y el análisis de algoritmo.
Sin embargo, debemos tener cuidado de no abusar del modelo de memoria RAM. Por ejemplo, ¿qué
pasaría si una memoria RAM tenía una instrucción que tipo? Entonces podríamos clasificar en una sola
instrucción. una memoria RAM sería poco realista, ya que las computadoras reales no tienen tales
instrucciones. Nuestro guía, por lo tanto, es cómo los ordenadores reales son de- firmado. El modelo de
memoria RAM contiene instrucciones que se encuentran comúnmente en las computadoras reales: la
aritmética (como sumar, restar, multiplicar, dividir, resto, suelo, techo), el movimiento de datos (carga,
almacenamiento, copia), y el control (salto condicional e incondicional, llamada de subrutina y volver).
Cada tal instrucción tiene una cantidad constante de tiempo. Los tipos de datos en el modelo de RAM son
enteros y de coma flotante (para el almacenamiento de números reales). A pesar de que normalmente no
se refieren a nosotros mismos con precisión en este libro, en algunas aplicaciones de precisión es crucial.
También asumimos un límite en el tamaño de cada palabra de datos. Por ejemplo, cuando se trabaja con
entradas de tamaño n, se typ- camente suponemos que los enteros son representados por los bits c LGN
para alguna constante c 1. Requerimos c 1, de modo que cada palabra puede contener el valor de n, lo que
nos permite indexar el elementos de entrada individuales, y se restringe c a ser una constante de manera
que el tamaño de la palabra no crece de forma arbitraria. (Si el tamaño de la palabra podría crecer de
manera arbitraria, podríamos almacenar grandes cantidades de datos en una sola palabra y operar sobre
todo en tiempo claramente un escenario realista constante). A pesar de que normalmente no se refieren a
nosotros mismos con precisión en este libro, en algunas aplicaciones de precisión es crucial. También
asumimos un límite en el tamaño de cada palabra de datos. Por ejemplo, cuando se trabaja con entradas de
tamaño n, se typ- camente suponemos que los enteros son representados por los bits c LGN para alguna
constante c 1. Requerimos c 1, de modo que cada palabra puede contener el valor de n, lo que nos permite
indexar el elementos de entrada individuales, y se restringe c a ser una constante de manera que el tamaño
de la palabra no crece de forma arbitraria. (Si el tamaño de la palabra podría crecer de manera arbitraria,
podríamos almacenar grandes cantidades de datos en una sola palabra y operar sobre todo en tiempo
claramente un escenario realista constante). A pesar de que normalmente no se refieren a nosotros mismos
con precisión en este libro, en algunas aplicaciones de precisión es crucial. También asumimos un límite
en el tamaño de cada palabra de datos. Por ejemplo, cuando se trabaja con entradas de tamaño n, se typ-
camente suponemos que los enteros son representados por los bits c LGN para alguna constante c 1.
Requerimos c 1, de modo que cada palabra puede contener el valor de n, lo que nos permite indexar el
elementos de entrada individuales, y se restringe c a ser una constante de manera que el tamaño de la
palabra no crece de forma arbitraria. (Si el tamaño de la palabra podría crecer de manera arbitraria,
podríamos almacenar grandes cantidades de datos en una sola palabra y operar sobre todo en tiempo
claramente un escenario realista constante). que typ- camente suponemos que los enteros son
representados por los bits c LGN para alguna constante c 1. Requerimos c 1, de modo que cada palabra
puede contener el valor de n, lo que nos permite indexar los elementos de entrada individuales, y nos
limitamos a ser un c constante, de modo que el tamaño de la palabra no crece de forma arbitraria. (Si el
tamaño de la palabra podría crecer de manera arbitraria, podríamos almacenar grandes cantidades de
datos en una sola palabra y operar sobre todo en tiempo claramente un escenario realista constante). que
typ- camente suponemos que los enteros son representados por los bits c LGN para alguna constante c 1.
Requerimos c 1, de modo que cada palabra puede contener el valor de n, lo que nos permite indexar los
elementos de entrada individuales, y nos limitamos a ser un c constante, de modo que el tamaño de la
palabra no crece de forma arbitraria. (Si el tamaño de la palabra podría crecer de manera arbitraria,
podríamos almacenar grandes cantidades de datos en una sola palabra y operar sobre todo en tiempo
claramente un escenario realista constante).
2.2 Análisis de algoritmos 23

un .n C 1 / -elemento matriz C. Estado el problema formalmente y escribir pseudocódigo para la adición


de los dos números enteros.
24 Capítulo 2 Introducción

ordenadores reales contienen instrucciones diferentes a los mencionados anteriormente, y tales


instrucciones REPRESENTA un área gris en el modelo de memoria RAM. Por ejemplo, es una
instrucción de exponenciación tiempo constante-? En el caso general, no; se tarda varias instrucciones
para calcular xy cuando x e y son números reales. En contadas ocasiones, sin embargo, la potencia es una
operación de tiempo constante. Muchas computadoras tienen una instrucción de “desplazamiento a la
izquierda”, que en tiempo constante desplaza los bits de un número entero de K posiciones a la izquierda.
En la mayoría de las computadoras, desplazar los bits de un número entero por una posición a la izquierda
es equivalente a multiplicación por 2, de modo que el desplazamiento de los bits de K posiciones a la
izquierda es equivalente a multiplicación por 2k. Por lo tanto, estos equipos pueden calcular 2k en una
instrucción de constante de tiempo al cambiar el número entero 1 por las posiciones k, a la izquierda,
siempre que k no es más que el número de bits en una palabra ordenador. Haremos todo lo posible para
evitar este tipo de zonas grises en el modelo de memoria RAM, pero vamos a tratar el cálculo de 2k como
una operación de tiempo constante cuando k es un número entero positivo pequeño suficiente.
En el modelo de memoria RAM, no intentamos modelar la jerarquía de memoria que es común en las
computadoras modernas. Es decir, que no modelamos caches o memoria virtual. Varios modelos
computacionales intentan dar cuenta de los efectos de memoria jerarquía, que son a veces significativas
en programas reales en máquinas reales. Un puñado de problemas en este libro examina los efectos de
memoria jerarquía, pero en su mayor parte, los análisis en este libro no va a considerar. Los modelos que
incluyen la jerarquía de memoria son un poco más complejo que el modelo de memoria RAM, y para que
puedan ser difíciles de trabajar. Por otra parte, los análisis RAM-modelo son generalmente excelentes
predictores del rendimiento en las máquinas reales.
Analizando incluso un simple algoritmo en el modelo de memoria RAM puede ser un desafío. Las
herramientas matemáticas requeridas pueden incluir combinatoria, teoría de la probabilidad, la destreza
algebraica, y la capacidad de identificar los términos más significativos en una fórmula. Debido a que el
comportamiento de un algoritmo puede ser diferente para cada entrada posible, necesitamos un medio
para resumir que el comportamiento en simple, fácil de entender fórmulas. A pesar de que normalmente
seleccionamos sólo un modelo de máquina para analizar un gorithm al- dado, todavía nos enfrentamos a
muchas opciones para decidir cómo expresar nuestro análisis. Nos gustaría una manera que sea fácil de
escribir y manipular, muestra las importantes carac- terísticas de las necesidades de recursos de un
algoritmo, y suprime los detalles tediosos.
Análisis de ordenación por inserción
El tiempo empleado por el procedimiento de inserción-ORDENAR depende de la entrada: clasificar los
mil números lleva más tiempo que la clasificación tres números. Por otra parte, la Inserción ORDENAR
puede tomar diferentes cantidades de tiempo para ordenar a dos secuencias de entrada del mismo tamaño
en función de cómo casi ordenados que ya son. En general, el tiempo empleado por un algoritmo crece
con el tamaño de la entrada, por lo que es tradicional para describir el tiempo de ejecución de un
programa en función del tamaño de su entrada. Para ello, es necesario definir los términos “tiempo de
funcionamiento” y “tamaño de entrada” con más cuidado.
2.2 Análisis de algoritmos 25

La mejor idea para el tamaño de la entrada depende del problema que se está estudiando. Para muchos
problemas, tales como la clasificación o el cálculo de transformadas de Fourier discretas, la medida ural
más NAT es el número de elementos en el ejemplo de entrada-para, el tamaño n matriz para la
clasificación. Para muchos otros problemas, tales como la multiplicación de dos números enteros, la
mejor medida del tamaño de entrada es el número total de bits necesarios para representar la entrada en
notación binaria ordinaria. A veces, es más apropiado para describir el tamaño de la entrada con dos
números en lugar de uno. Por ejemplo, si la entrada a un Rithm algo- es un gráfico, el tamaño de entrada
puede ser descrita por el número de vértices y aristas en el gráfico. Vamos a indicar qué medida el tamaño
de entrada se utiliza con cada problema que estudiamos.
El tiempo de ejecución de un algoritmo en una entrada en particular es el número de operaciones
primitivas o “pasos” ejecutados. Es conveniente definir la noción de paso de modo que sea lo más
independiente de la máquina como sea posible. Por el momento, vamos a adoptar la siguiente vista. Se
requiere una cantidad constante de tiempo para ejecutar cada línea de nuestro pseudocódigo. Una línea
puede tomar una cantidad diferente de tiempo que otra línea, pero se supone que cada ejecución de la
línea de ITH lleva tiempo c
yo

, donde C
yo

es una constante. Este punto


de vista está de acuerdo con el modelo de memoria RAM, y también refleja cómo el pseudocódigo se
llevaría a cabo en la mayoría computers.5 real
En la siguiente discusión, nuestra expresión para el tiempo de ejecución de la Inserción ORDENAR
evolucionará de una fórmula desordenado que utiliza todos los costos Boletín C
yo

a una notación mucho más


simple que es más concisa y más fácil de manipular. Esta notación más simple también hará más fácil
determinar si un algoritmo es más eficiente que otro.
Comenzamos presentando el procedimiento de inserción-ORDENAR con el “costo” de tiempo de cada
declaración y el número de veces que se ejecuta cada instrucción. Para cada j D 2; 3; :::; n, donde n DA:
longitud, dejamos que t
j

denotar el número de veces que se ejecuta la prueba de


bucle, mientras que en la línea 5 para ese valor de j. Cuando un para o mientras las salidas de bucle de la
forma habitual (es decir, debido a la prueba en la cabecera del bucle), la prueba se ejecuta una vez más
que el cuerpo del bucle. Suponemos que los comentarios no son sentencias ejecutables, y por lo tanto
ocupan muy poco tiempo.
5Hay son algunas sutilezas aquí. pasos de cálculo que especifique en Inglés a menudo son variantes de un procedimiento que
requiere algo más que una cantidad constante de tiempo. Por ejemplo, más adelante en este libro, podríamos decir “ordenar los
puntos de coordenada x”, que, como veremos más adelante, lleva más de una cantidad constante de tiempo. Además, tenga en
cuenta que una declaración que llama a una subrutina toma tiempo constante, a pesar de la subrutina, una vez invocado, puede
tardar más. Es decir, separamos el proceso de llamar a los parámetros de subrutina de paso a ella, etc., desde el proceso de
ejecución de la subrutina.
26 Capítulo 2 Introducción

INSERCIÓN-SORT.A / veces coste 1 para j D 2 a A: longitud c


1

n 2 clave D AŒj ?? do
2

n 1 3 // Introducir AŒj ?? en el ordenado


secuencia AŒ1:: j 1 ??. 0 n 1 4 i D j 1 c
4

n 1 5 mientras i> 0 y AŒi ?? > Tecla c


5

PAG
n JD2

t
j 6 AŒi C 1 ?? D AŒi ?? do
6

PAG
n JD2

Séptima i D i 1 c
7

PAG
.t
j

8 AŒi C 1 ?? D c clave
8
n JD2 n

1
.t
j

1/
El tiempo de ejecución del algoritmo es la suma de los tiempos para cada ción estatal ejecutada
funcionamiento; una declaración que lleva c
yo

pasos para ejecutar y ejecuta n veces contribuirán c


yo

n a la tiempo.6 total de ejecución Para calcular T .n /, el tiempo de ejecución de la inserción-


ORDENAR en una entrada de los valores de n, se suma los productos de las columnas de coste y tiempos,
la obtención de
T .n / D c
1

nCC
2

.n 1 / C c
4

.n 1 / C c
5

nX
t
j

Cc
6

nX
.t
j

1/
JD2
JD2

Cc
7

nX
JD2

.t
j

1/Cc
8

.n 1 /:
Incluso para las entradas de un determinado tamaño, tiempo de ejecución de un algoritmo puede
depender de lo que se da de entrada de ese tamaño. Por ejemplo, en la inserción en el ORDENAR, el
mejor de los casos se produce si la matriz ya está ordenada. Para cada j D 2; 3; :::; n, entonces
encontramos que AŒi ?? llave A en la línea 5 cuando i tiene su valor inicial de j 1. Así t
j

D 1 para j D 2; 3; :::; n, y el
mejor de los casos el tiempo de funcionamiento es
T .n / D c
1

nCC
2

.n 1 / C c
4

.n 1 / C c
5

.n 1 / C c
8

.n 1 / D .c
1

Cc
2

Cc
4

Cc
5

Cc
8

/ .c n
2

Cc
4

Cc
5

Cc
8

/:
Podemos expresar este tiempo de funcionamiento como un C b para las constantes a y b que dependerá de
los costos de los estados c
yo

; por lo que es una función lineal de n. Si la matriz es a la inversa ordenados fin, es


decir, en orden decreciente: los peores resultados del caso. Hay que comparar cada elemento AŒj ?? con
cada elemento en todo el AŒ1 subarreglo ordenados:: j 1 ??, y así t
j
D j para j D 2; 3; :::; n. Señalando que
6Este característica no es necesariamente válida para un recurso como la memoria. Una declaración que hace referencia a m
palabras de la memoria y se ejecuta n veces no necesariamente referencia a las palabras mn distintas de memoria.
2.2 Análisis de algoritmos 27

nX
JD2

nn C 1 /
1
y nX
JD2

jD
.j 1 / D
nn 2
1/
(Véase el Apéndice A para una revisión de la forma de resolver estas sumas), nos encontramos con que en
el peor de los casos, el tiempo de ejecución de inserción es un género
T .n / D c
1

nCC
2

.n 1 / C c
4

.n 1 / C c
5

UN
nn C 1 /
.n 1 /
re
do
5

UN
norte
.do
2

Cc
4

Cc
5
Cc
8

/:
Podemos expresar esta peor de los casos el tiempo de funcionamiento como an2 C bn C c para las
constantes a, b, c, y que una vez más dependerá de los costos de los estados c
yo

; por lo que es una función cuadrática de n.


Por lo general, al igual que en la ordenación por inserción, el tiempo de ejecución de un algoritmo se
fija para una entrada dada, aunque en los capítulos siguientes veremos algunos interesantes “aleatorios”
algoritmos cuyo comportamiento puede variar incluso para una entrada fija.
Peor de los casos y el análisis promedio de los casos
En nuestro análisis de ordenación por inserción, analizamos tanto el mejor de los casos, en los que ya se
solucionó la matriz de entrada, y el peor de los casos, en el que la matriz de entrada se solucionó inversa.
Para el resto de este libro, sin embargo, por lo general vamos a concentrarse en encontrar sólo el peor de
los casos el tiempo de funcionamiento, es decir, el mayor tiempo de ejecución para cualquier entrada de
tamaño n. Le damos tres razones para esta orientación.
El tiempo del peor caso de ejecución de un algoritmo nos da un límite superior en el tiempo de ejecución
para cualquier entrada. Sabiendo que ofrece una garantía de que el algoritmo nunca tomará más tiempo.
No necesitamos hacer alguna conjetura sobre el tiempo de ejecución y esperamos que nunca se pone
mucho peor. Para algunos algoritmos, el peor de los casos se produce con bastante frecuencia. Por
ejemplo, en la búsqueda de una base de datos para una determinada pieza de información, peor caso del
algoritmo de búsqueda a menudo se producen cuando la información no está presente en la base de datos.
En algunas aplicaciones, la búsqueda de información ausente pueden ser frecuentes.
UN
Cc
6


nn 1 /
2
2
do
do
6

2
do
2
do
7

2
UN
n2 C c
1

UN
Cc
7

UN
nn 1 /
Cc
2

2
Cc
4

UN
Cc
8

do
2
do
5

2
do
6

2
do
7

2
Cc
8
28 Capítulo 2 Introducción

El “caso medio” es a menudo más o menos tan malo como el peor de los casos. Supongamos que
elegimos al azar n números y aplicamos ordenación por inserción. ¿Cuánto tiempo se tarda en determinar
en qué lugar AŒ1 subarreglo:: j 1 ?? insertar elemento AŒj ??? En promedio, la mitad de los elementos
en AŒ1:: j 1 ?? son menos de AŒj ??, y la mitad de los elementos son mayores. En promedio, por lo
tanto, comprobamos medio de la AŒ1 subarreglo:: j 1 ??, y así t
j

es de aproximadamente j = 2. La media de tiempo de funcionamiento resultante caso resulta ser una


función cuadrática del tamaño de entrada, al igual que el peor de los casos el tiempo de funcionamiento.
En algunos casos particulares, vamos a estar interesado en la media de los casos el tiempo de ejecución
de un algoritmo; veremos la técnica de análisis probabilístico aplicarse a diversos algoritmos en este libro.
El objeto de análisis en el caso promedio es limitada, ya que no puede ser aparente lo que constituye una
entrada “promedio” para un problema particular. A menudo, supondremos que todas las entradas de un
determinado tamaño son igualmente probables. En la práctica, este supuesto puede ser violada, pero a
veces se puede utilizar un algoritmo aleatorio, lo que hace opciones al azar, para permitir un análisis
probabilístico y producir un tiempo de ejecución esperado. Exploramos algoritmos aleatorios más en el
capítulo 5 y en varios otros capítulos subsiguientes.
Orden de crecimiento
Utilizamos algunas abstracciones simplificación para facilitar el análisis del procedimiento ORDENAR la
Inserción. En primer lugar, ignoramos el costo real de cada declaración, usando las constantes c
yo

para representar estos costos. A continuación, se observó que incluso estas constantes nos
dan más detalles de los que realmente necesitamos: expresamos el peor de los casos el tiempo de
funcionamiento como an2 C bn C c para algunas constantes a, b, c y que dependen de los costos de los
estados c
yo

. por lo tanto de haber ignorado no sólo los costes reales de los estados, sino también los costos
abstractos c
yo

. Ahora vamos a hacer una más abstracción simplificar: es la tasa de crecimiento, o el orden de
crecimiento, del tiempo de funcionamiento que realmente nos interesa. Por lo tanto, con- Sider sólo el
término principal de una fórmula (por ejemplo, AN2), ya que los términos de orden inferior son
relativamente insignificantes para valores grandes de n. También pasamos por alto coeficiente constante
del término principal, ya que los factores constantes son menos importantes que la tasa de crecimiento en
la determinación de la eficiencia computacional para grandes entradas. Para la ordenación por inserción,
cuando ignoramos los términos de orden inferior y coeficiente constante del término principal, nos
quedamos con el factor de n2 del término principal. Nos escribir que la ordenación por inserción tiene un
peor caso de tiempo de, .n2 / (pronunciado ‘theta de n-cuadrado’) que se ejecuta. Vamos a utilizar, -
notation informalmente en este capítulo, y vamos a definir con precisión en el capítulo 3. Por lo general,
considerar un algoritmo para ser más eficiente que otro si su caso el tiempo de funcionamiento más
desfavorable tiene un orden inferior de crecimiento. Debido a factores constantes y términos de orden
minúsculas, un algoritmo cuyo tiempo de funcionamiento tiene un orden superior de crecimiento podría
tomar menos tiempo para entradas pequeñas que un algoritmo cuyo tiempo de funcionamiento tiene una
menor
2.3 Diseño de algoritmos
Podemos elegir entre una amplia gama de técnicas de diseño de algoritmos. Para la ordenación por
inserción, se utilizó un enfoque incremental: habiendo ordenado la AŒ1 subarreglo:: j 1 ??, que inserta el
elemento individual AŒj ?? en su lugar apropiado, produciendo el AŒ1 subarreglo ordenados:: j ??.
En esta sección, examinamos un enfoque de diseño alternativo, conocido como “dividir-y vencerás”,
que vamos a explorar en más detalle en el capítulo 4. Utilizaremos dividir-y-conquista para diseñar un
algoritmo de ordenación cuya peor de los casos tiempo de ejecución es mucho menor que la de la
ordenación por inserción. Una de las ventajas de los algoritmos divide y vencerás es que sus tiempos de
trabajo a menudo se determinan fácilmente utilizando técnicas que veremos en el capítulo 4.
2.3 Diseño de algoritmos 29

orden de crecimiento. Pero para las entradas suficientemente grandes, una, .n2 / algoritmo, por ejemplo,
se ejecutará más rápidamente en el peor de los casos que a, .n3 / algoritmo.
Ceremonias
2.2-1 expresar la función n3 = 1,000 100N2 100n C 3 en términos de, -notation.
2.2-2 Considere clasificación n números almacenados en la matriz A por encontrar primero el elemento
más pequeño de A y el intercambio con el elemento en AŒ1 ??. A continuación, busque el segundo
elemento más pequeño de A, e intercambiarlo con AŒ2 ??. Continúe de esta manera para los primeros n
elementos de 1 A. pseudocódigo de escritura para este algoritmo, que se conoce como selección especie.
Lo invariantes de bucle no mantener este algoritmo? ¿Por qué necesita para funcionar sólo para los
primeros n elementos 1, en lugar de para todos los elementos n? Dar a los tiempos mejor de los casos y el
peor de los casos funcionamiento de ordenación por selección en, -notation.
Considere 2.2-3 búsqueda lineal de nuevo (véase el ejercicio 2.1-3). ¿Cuántos elementos de la secuencia
ponen in- deben ser verificados por término medio, en el supuesto de que el elemento que se busca es la
misma probabilidad de ser cualquier elemento de la matriz? ¿Y en el peor de los casos? ¿Cuáles son la
media de los casos y el peor de los casos los tiempos de búsqueda lineal corriendo, -notation? Justificar
sus respuestas.
2.2-4 ¿Cómo podemos modificar casi cualquier algoritmo para pasar un buen rato mejor de los casos en
funcionamiento?

También podría gustarte