Documentos de Académico
Documentos de Profesional
Documentos de Cultura
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
Ä a0 2
Ä Ä a0
norte
.
HA0 1
; a0 2
; :::; a0
norte
♣
♣
♣ 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.
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
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
n 2 clave D AŒj ?? do
2
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
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
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
1Â
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
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?