Anda di halaman 1dari 81

secuencias

Los ordenadores son muy buenos en tratar con grandes cantidades de información.
Pueden repetir una tarea una y otra vez sin aburrirse. Cuando repiten una tarea que
generalmente están haciendo lo mismo a los datos u objetos similares. Es natural
querer organizar los objetos en algún tipo de estructura para que nuestro programa
puede cambiar fácilmente de un objeto a otro. ¿Cómo se añaden objetos a una
secuencia o una colección y cómo nos movemos de un elemento al siguiente tiene
cierto impacto en la forma en que podríamos querer organizar la recogida de datos
en un programa.
En este capítulo nos fijamos en las diferentes formas de organización de los
datos en una secuencia. También examinaremos cómo utilizar Python para
facilitar el trabajo con secuencias convenientes. La sobrecarga de operadores en
Python nos permite construir secuencias que podemos manipular con las
operaciones intuitivas. Por último, también examinaremos cómo la organización
de una secuencia afecta a la complejidad de cálculo de las operaciones en él.
Un tipo abstracto de datos es un término que se utiliza para describir una forma
de organizar los datos. Las listas son una forma de organizar una secuencia de
datos, pero en este capítulo vamos a descubrir otras maneras de organizar
secuencias también. Ascendiendo y descendiendo secuencias, listas enlazadas,
pilas y colas son todos los tipos de datos abstractos que vamos a explorar en este
capítulo.

4.1 Objetivos Capítulo

En este capítulo usted leerá acerca de las diferentes formas de organización de los datos
dentro de un programa.
Al final del capítulo usted debe ser capaz de responder a estas preguntas.

• Cuando se presentan con un algoritmo que se requiere para mantener una


secuencia de datos, lo que se ajusta mejor esquema de organización?
• ¿Cuáles son las ventajas y desventajas de seleccionar un tipo de secuencia en
contraposición a otro?
• ¿Cuáles son algunos algoritmos interesantes que utilizan listas, listas enlazadas, pilas, o
colas?
• ¿Qué algoritmo de clasificación se usa con más frecuencia al ordenar una
secuencia de valores ordenados?
• Lo algoritmos de búsqueda son posibles en una secuencia?
© Springer International Publishing Suiza 2015 91
KD Lee y S. Hubbard, estructuras de datos y algoritmos con Python, Temas de
licenciatura en Ciencias de la Computación, DOI 10.1007 / 978-3-319-13072-9_4
92 4 secuencias

• ¿Cuál es la complejidad de muchas de las operaciones más comunes en las


secuencias y cómo es que la complejidad afectada por la organización
subyacente de los datos.

También se le presentará con algunos problemas de programación interesantes


que le ayudarán a aprender a seleccionar y utilizar estructuras de datos adecuadas
para resolver algunos problemas interesantes.

4.2 Liza

En el primer y segundo capítulo hemos desarrollado una secuencia llamada


PyList. La clase PyList es en realidad un nuevo envoltorio de la clase lista de
Python. La secuencia de ejemplo demuestra algunos de los operadores que son
compatibles con Python. En esta sección queremos mirar más profundamente en
cómo se implementan las listas. Hay muchas operaciones apoyadas en las listas.
Capítulodieciséiscontiene la lista completa. La tabla de la figura.4.1 es un
subconjunto de las operaciones apoyadas por listas.
Cada una de las operaciones de la tabla tiene una complejidad asociada. El
rendimiento de un algoritmo depende de la complejidad de las operaciones utilizadas
en la ejecución de dicho algoritmo. En las siguientes secciones vamos a desarrollar aún
más nuestra propia lista de tipo de datos, llamado PyList, utilizando la lista integrada
sólo para establecer y obtener elementos de una lista. El conjunto indexado
operaciones get y pueden ser indexados observa que tiene O (1) la complejidad. Esta
complejidad se logra debido a la memoria de un ordenador se puede acceder al azar,
por lo que se llama memoria de acceso aleatorio. En el Cap.2pasamos un tiempo
demostrando que cada lugar dentro de una lista se puede acceder en la misma cantidad
de tiempo, independientemente del tamaño de la lista y la ubicación de ser
recuperados. En las siguientes secciones vamos a realzar el tipo de datos PyList para
apoyar las operaciones indicadas en esta tabla.

Operación Complejidad Uso Método


creación de la
lista O (n) o O (1) x = lista (y) llamadas __init __ (y)
la indexación O (1) a = x [i] x .__ GetItem __ (i)
conjunto
indexado O (1) x [i] = a x .__ SetItem __ (i, a)
concatenar En) z=x+y z = x .__ añadir __ (y)
adjuntar O (1) x.append (a) x.append (a)
insertar En) X.Insert (i, e) X.Insert (i, e))
borrar En) del X [i] x .__ delItem __ (i)
igualdad En) x == y x .__ eq __ (y)
iterar En) para una en x: x .__ __ iter ()
longitud O (1) len (x) x .__ len __ ()
afiliación En) una en x x .__ contiene __ (a)
ordenar O (n log n) x.sort () x.sort ()

Fig. 4.1 Complejidad de operaciones con listas


4.2Lists 93

4.2.1 El tipo de datos PyList

En los dos primeros capítulos comenzamos a desarrollar nuestra estructura de


datos PyList. Para apoyar el O (1) la complejidad de la operación de anexión, el
PyList contiene emplazamientos vacíos que pueden ser llenados cuando append se
llama como primero descrito en la Sección.2.10. Vamos a mantener un registro del
número de localizaciones que se utiliza y el tamaño real de la lista interna en
nuestros objetos PyList. Por lo tanto, vamos a necesitar tres tipos de información:
la propia lista denominada artículos, el tamaño de la lista interna llamada el
tamaño y el número de lugares en la lista interna que se están utilizando
actualmente llamados numItems. A pesar de que no tendríamos que hacer un
seguimiento del tamaño de la lista, ya que podríamos llamar a la función len,
vamos a almacenar el tamaño en el objeto de evitar la sobrecarga de llamar len en
múltiples lugares en el código.
Todos los lugares utilizados en la lista interna tendrán lugar al comienzo de la
lista. En otras palabras, no habrá agujeros en el medio de una lista que vamos a
tener que preocuparse. Llamaremos a este supuesto un invariante en nuestra
estructura de datos. Un invariante es algo que es verdad, antes y después de
cualquier llamada al método en la estructura de datos. El invariante para esta lista
es que la lista interna tendrá las primeras numItems llenos de agujeros. El código
en la Sección.4.2.3 proporciona un constructor que también se puede pasar una
lista de su contenido inicial.
Almacenamiento de todos los productos al comienzo de la lista, sin agujeros,
también significa que podemos acceder aleatoriamente elementos de la lista en O
(1) tiempo. Nosotros no tenemos que buscar la ubicación adecuada de un
elemento. Indexación en el PyList simplemente índice en la lista de elementos
internos para encontrar el elemento correcto como se ve en las siguientes
secciones.

4.2.2 El Constructor PyList


1 clase PyList:
2 def __init __ (self, contenidos = [], tamaño = 10):
3 # El contenido permite al programador construir una lista con
4 # El contenido inicial de este valor. el initial_size
5 # Permite al programador elegir un tamaño para el tamaño interno de
la
6 # lista. Esto es útil si el programador sabe que él / ella va
7 # Para añadir un número determinado de artículos de inmediato a la
lista.
8 self.items = [Ninguno] * tamaño
9 self.numItems = 0
10 self.size = tamaño
11
12 para e en los contenidos:
13 self.append (e)

El código en la Sección. 4.2.3construye un objeto PyList mediante la creación de


una lista de 10 valores Ninguno. Ninguno es el valor especial en Python para las
referencias que apuntan a la nada. Figura4.2 muestra una lista de muestra después de
haber sido creado y tres artículos se añade a éste. El valor especial Ninguno se indica
en la figura por las tres líneas horizontales donde las ranuras vacías en el punto de
lista. El tamaño inicial de la lista de elementos interna es 10 por defecto, pero un
usuario podría pasar inicialmente un tamaño más grande si querían. Esto es sólo el
tamaño inicial. La lista sigue crecerá cuando se necesita. El parámetro de contenidos
permite
94 4 secuencias

Fig. 4.2 Un objeto Pylist Muestra

el pase programador en una lista o secuencia para poner en la lista inicialmente.


Por ejemplo, el objeto en la Fig.4.2 podría haber sido creado por escribiendo al
siguiente.
sampleList = PyList ([ "a", "b", "c"])

Cada elemento de la secuencia se añade como un elemento de lista separada. La


complejidad de crear un objeto PyList es O (1) si no hay valor se pasa al
constructor y O (n) si una secuencia se pasa al constructor, donde n es el número
de elementos en la secuencia.

4.2.3 PyList obtener y establecer


1 def __getitem __ (self, índice):
2 Si index> = 0 e índice <self.numItems:
3 regreso self.items [índice]
4
5 aumento IndexError ( "índice PyList fuera de alcance")
6
7 def __setitem __ (self, índice, val):
8 Si index> = 0 e índice <self.numItems:
9 self.items [índice] = val
10 regreso
11
12 aumento IndexError ( "PyList índice de asignación fuera de rango")

Nuestra clase PyList es un contenedor para la clase incorporado en la lista. Por lo


tanto, para poner en práctica el elemento get y operaciones Conjunto de objetos en
PyList, usaremos el obtener y establecer operaciones en la clase incorporado en la
lista. El código se da aquí. La complejidad de ambas operaciones es O (1). En ambos
casos, queremos para asegurarse de que el índice se encuentra en el rango de índices
aceptables. Si no es así, vamos a lanzar una excepción IndexError al igual que la clase
incorporado en la lista lo hace.
4.2Lists 95

4.2.4 PyList Concatenate


1 def __add __ (self, otro):
2 resultado = PyList (tamaño = self.numItems + other.numItems)
3
4 para i en Rango (self.numItems):
5 result.append (self.items [i])
6
7 para i en Rango (other.numItems):
8 result.append (other.items [i])
9
10 regreso resultado

Para concatenar dos listas hay que construir una nueva lista que contiene los
contenidos de ambos. Se trata de un método de acceso, ya que no muta cualquiera
de las listas. En su lugar, se construye una nueva lista. Podemos hacer esta
operación en tiempo O (n), donde n es la suma de las longitudes de las dos listas.
Aquí hay un código para lograr esto.
En la Sección. 4.2.5el tamaño se establece en el tamaño necesario para el resultado
de la concatenación de las dos listas. La complejidad del método __add__ es O (n),
donde n es la longitud de las dos listas. El tamaño inicial de la lista no tiene que ajustar
porque append tiene O (1) la complejidad como vimos en la Sección.2.10. Sin
embargo, ya sabemos que el tamaño de la lista resultante, el ajuste del tamaño inicial
debe acelerar la operación de concatenación ligeramente.

4.2.5 PyList Anexar


1 # Este método se oculta ya que comienza con dos guiones bajos.
2 # Sólo está disponible para la clase que se utiliza.
3 def __makeroom (self):
4 # Aumentar tamaño de la lista por cuarto para hacer más espacio.
5 # Agrega uno en caso de que por alguna razón self.size es 0.
6 Newlen = (self.size // 4) + self.size + 1
7 newlst = [Ninguno] * Newlen
8 para i en Rango (self.numItems):
9 newlst [i] = self.items [i]
10
11 self.items = newlst
12 self.size = Newlen
13
14 def append (auto, artículo):
15 Si self.numItems == self.size:
16 self .__ makeroom ()
17
18 self.items [self.numItems] = item
19 self.numItems + 1 = # mismo que escribir self.numItems = self.numItems + 1

En la Sección. 2.10hemos aprendido que el método de agregación tiene O (1)


amortiza complejidad. Si se anexa, nos limitaremos a añadir un elemento más al
final de la lista self.items si hay espacio. En la descripción del constructor
decidimos los objetos PyList contendrían una lista que había espacio para más
elementos. Cuando añadiendo que podemos hacer uso de ese espacio extra. De vez
en cuando (es decir, después añadiendo algún número de elementos), la lista
self.items internos se llenará. En ese momento hay que aumentar el tamaño de la
lista de elementos para hacer espacio para el nuevo elemento que está anexando
por un tamaño proporcional a la longitud actual de self.items.
96 4 secuencias

Como aprendimos en el Cap. 2, Para hacer la operación de anexión corremos en O


(1) tiempo, no podemos simplemente añadir una localización más cada vez
necesitamos más espacio. Resulta que la adición de un 25% más de espacio cada vez
es suficiente para garantizar O (1) la complejidad. La elección del 25% no es
significativa. Si añadimos incluso el 10% más de espacio cada vez que iba a conseguir
O (1) la complejidad. En el otro extremo podríamos duplicar el tamaño de la lista
interna cada vez que necesitamos más espacio como lo hicimos en la Sección.2.10. Sin
embargo, el 25% parece una cantidad razonable para expandir la lista y sin engullir
demasiada memoria en el ordenador. Sólo tenemos unos pocos dólares más
cibernéticos almacenados para cada operación de anexar a pagar por la ampliación de
la lista cuando nos encontramos fuera de la habitación. El código en la
Sección.4.2.6implementa la operación de anexión con una complejidad amortizado de
O (1). La división entera por 4 es muy rápido en un ordenador, ya que puede ser
implementado por el desplazamiento de los bits del número entero a la derecha, por lo
que el cálculo de nuestra nueva longitud, cuando sea necesario, es relativamente
rápido.
Los implementos intérprete de Python anexan de manera similar. El intérprete de
Python se implementa en C, por lo que el intérprete utiliza código C. Python también
opta por aumentar el tamaño de la lista por otros valores. En la lista de Python tamaños
de aumento por 4, 8, 16, 25, y así sucesivamente. El espacio adicional para añadir a la
lista interna se calcula a partir del tamaño recién necesaria de la lista y crece por 4, 8,
16, 25, 35, 46, 58, 72, 88, y así sucesivamente. Se puede ver que la cantidad a añadir
crece a medida que crece la lista y que conduce a una complejidad amortizado de O (1)
para la operación de anexión en el intérprete de Python.

4.2.6 Insertar PyList


1 def insertar (self, i, e):
2 Si self.numItems == self.size:
3 self .__ makeroom ()
4
5 Si i <self.numItems:
6 para j en el rango (self.numItems-1, i-1, -1):
7 self.items [j + 1] = self.items [j]
8
9 self.items [i] = e
10 self.numItems + = 1
11 más:
12 self.append (e)

Para insertar en esta lista secuencial tenemos que hacer espacio para el nuevo
elemento. Dada la forma en que la lista está organizada, no hay más remedio que
copiar cada elemento después del punto donde queremos insertar el nuevo valor a la
siguiente ubicación en la lista. Esto funciona mejor si empezamos desde el extremo
derecho de la lista y trabajamos nuestro camino de regreso al punto donde se insertará
el nuevo valor. La complejidad de esta operación es O (n), donde n es el número de
elementos en la lista después de que el punto de inserción.
El índice i es el lugar donde el nuevo valor de e se va a insertar. Si el índice de pro-
RESPETA es más grande que el tamaño de la lista el nuevo elemento, e, se añade al final de
la lista.
4.2Lists 97

4.2.7 Eliminar PyList


1 def __delitem __ (self, índice):
2 para i en el rango de (índice, self.numItems-1):
3 self.items [i] = self.items [i + 1]
4 self.numItems - = 1 # mismo que escribir self.numItems = self.numItems - 1

Cuando la eliminación de un elemento de un índice específico en la lista, hay que


mover todo después de que el elemento hacia abajo para preservar nuestro invariante
que no hay agujeros en la lista interna. Esto se traduce en una aplicación de O (n) en el
medio y el peor caso donde n es el número de elementos después de que el índice de la
lista. Aquí está el código que lleva a cabo la eliminación.
En el intérprete Python, para conservar espacio, si una lista alcanza un punto
después de la eliminación, donde se están utilizando menos de la mitad de los
lugares dentro de la lista interna, entonces el tamaño del espacio disponible se
reduce a la mitad.

4.2.8 Prueba de Igualdad PyList


1 def __eq __ (self, otro):
2 Si escribir (otra) = Tipo de (auto):
3 regreso Falso
4
5 Si self.numItems = other.numItems:
6 regreso Falso
7
8 para i en Rango (self.numItems):
9 Si ! Self.items [i] = other.items [I]:
10 regreso Falso
11
12 regreso Cierto

Comprobación de la igualdad de dos listas requiere las dos listas sean del
mismo tipo. Si son de diferentes tipos, a continuación, vamos a decir que no son
iguales. Además, las dos listas deben tener la misma longitud. Si ellos no tienen la
misma longitud, que no pueden ser iguales. Si se cumplen estas dos condiciones,
entonces las listas son iguales si todos los elementos en las dos listas son iguales.
Aquí está el código que implementa las pruebas de igualdad de dos objetos PyList.
pruebas de igualdad es un O (n) la operación.

4.2.9 PyList iteración


1 def __iter __ (self):
2 para i en Rango (self.numItems):
3 rendimiento self.items [i]

La capacidad de iterar sobre una secuencia es sin duda un requisito. Secuencias


tienen una colección de elementos de datos similares y con frecuencia quieren
hacer algo con cada elemento de una secuencia. Por supuesto, la complejidad de
iterar sobre cualquier secuencia es O (n), donde n es el tamaño de la secuencia.
Aquí está el código que lleva a cabo esta para la secuencia PyList. La llamada
rendimiento en Python suspende la ejecución del método __iter__ y devuelve el
elemento cedido a la iterador.
98 4 secuencias

4.2.10 Longitud PyList


1 def __len __ (self):
2 regreso self.numItems

Si el número de artículos que no se mantiene un seguimiento de dentro del objeto


PyList, a continuación, contando el número de elementos de la lista sería un O (n) la
operación. En cambio, si hacemos un seguimiento de la cantidad de elementos en la
lista como artículos se añaden o se eliminan de la lista, a continuación, sólo tenemos
que devolver el valor de numItems del objeto, lo que resulta en O (1) la complejidad.

4.2.11 PyList membresía


1 def __contains __ (self, artículo):
2 para i en Rango (self.numItems):
3 Si self.items [i] == artículo:
4 regreso Cierto
5
6 regreso Falso

Las pruebas para la pertenencia a una lista de medios de comprobación para ver
si un artículo es uno de los elementos de la lista. La única manera de hacerlo es
examinar cada punto en la secuencia en la lista. Si el artículo se encuentra a
continuación, se devuelve true, de lo contrario se devuelve Falso. Esto resulta en
O (n) la complejidad.
Esta idea de la búsqueda de un elemento en una secuencia es tan común que los
informáticos han nombrado. Esto se conoce como búsqueda lineal. Se llama así
porque de su O (n) la complejidad.

4.2.12 Conversión de cadenas PyList


1 def __str __ (self):
2 s = "["
3 para i en Rango (self.numItems):
4 s = s + repr (self.items [i])
5 Si i <self.numItems - 1:
6 s = s + ""
7 s = s + "]"
8 regreso s

Es conveniente ser capaz de convertir una lista en una cadena para que pueda
ser impreso. Python incluye dos métodos que se pueden utilizar para la conversión
de una cadena. La primera probablemente estará ya familiarizado. La función str
llama al método __str__ en un objeto para crear una representación de cadena de
por sí apta para la impresión. Aquí está el código que implementa el método
__str__ para la clase PyList.
4.2Lists 99

Representación 4.2.13 PyList Cadena


1 def __repr __ (self):
2 s = "PyList (["
3 para i en Rango (self.numItems):
4 s = s + repr (self.items [i])
5 Si i <self.numItems - 1:
6 s = s + ""
7 s = S + "])"
8 regreso s

El otro método para la conversión de un objeto a una cadena tiene un propósito


diferente. Python incluye una función llamada eval, que tendrá una cadena que
contiene una expresión y evaluar la expresión de la cadena. Por ejemplo, eval ( “6 +
5”) da como resultado 11 y eval ( “[1,2,3]”) resulta en la lista [1,2,3]. La función repr
en Python llama al método __repr__ en una clase. Este método, si está definido, debe
devolver una cadena represen-tación de un objeto que es adecuado para ser dada a la
función eval. En el caso de la clase PyList, la forma repr de la cadena sería algo así
como “PyList ([1,2,3])” para la secuencia PyList que contiene estos elementos. Aquí
está el código que logra esto. Es casi idéntico al código __str__, excepto que PyList
prefijos la secuencia.
Nótese que en ambas sectas. 4.2.13y 4.2.14 que repr se pidió a los elementos de
la lista. Llamando repr es necesario porque de lo contrario una lista que contiene
cadenas como [”Hola”,”no”] se convertiría en [hi, hay] en su str o representación
repr.

4.3 Objetos de clonación

Es interesante notar que ahora tenemos un método de hacer una copia de un objeto. Si
x es un objeto PyList, entonces eval (repr (x)) es una copia o clon de este objeto. Dado
que todos los elementos del objeto PyList también se clonan mediante la evaluación de
la representación del objeto, la clonación de un objeto como esto se llama un clon
profunda o copia en profundidad del objeto x.
También es posible hacer lo que se llama una copia superficial de un objeto.
Una copia superficial se produce cuando se copia el objeto, pero los elementos del
objeto son compartidos con el clon. Si deseamos crear una copia superficial de un
objeto PyList llamados x, escribiríamos lo siguiente.
x = PyList ([1,2,3])
y = PyList (x)

Aquí, y es una copia superficial de x porque tanto x como y dé los artículos 1, 2


y 3. En la mayoría de los casos si algunos elementos son compartidos o no,
probablemente no importa. En este caso no importa si los artículos son
compartidos porque: 1, 2, y 3 son números enteros y números enteros son
inmutables. Sin embargo, si los elementos compartidos son mutables, entonces es
posible que se preocupan por los clones superficiales o profundas de los objetos.
Cuando se trabaja con un clon superficial de un objeto que contiene objetos
mutables el programador debe ser consciente de que los elementos de la colección
podrían cambiar los valores sin ningún tipo de llamada a un método en el objeto.
Esto no ocurrirá a una profunda clon de un objeto.
100 4 secuencias

¿Qué es mejor, la clonación superficial o profunda clonación, depende de la


aplicación que está siendo escrito. Uno no es necesariamente mejor que el otro.
Hay un rendimiento adicional y la memoria golpeado para hacer clones de
profundidad, pero que son más seguros. El tipo de aplicación siendo desarrollado
probablemente ayudar a determinar qué tipo de clonación se elige deben ser útiles
en la aplicación clones de objetos.

4.4 Ordenando elemento

Ahora vamos a centrar nuestra atención en la implementación del método para


ordenar en nuestro tipo de datos PyList. Para ordenar una secuencia de elementos,
los elementos de la secuencia deben ser ordenados de alguna manera. Por ejemplo,
considere una clase que se utiliza para representar las coordenadas cartesianas en
un avión. Vamos a llamar a la clase Punto y contendrá un (x, y) par. Pediremos los
objetos de punto por su distancia dirigida desde el eje x. En otras palabras, van a
ser ordenados por sus coordenadas y. Aquí está nuestra clase Point. Por razones
que serán evidentes pronto, nuestra clase Point va a heredar de RawTurtle.

4.4.1 La clase de punto


1 clase Point(Turtle.RawTurtle):
2 def __init __ (self, lona, x, y):
3 super () .__ init __ (lienzo)
4 canvas.register_shape ( "punto", ((3,0), (2,2), (0,3), (- 2,2), (- 3,0), (- 2, -2), (
0, -3),
5 (2, -2)))
6 self.shape ( "dot")
7 self.speed (200)
8 self.penup ()
9 self.goto (x, y)
10
11 def __str __ (self):
12 regreso "(" + Str (self.xcor ()) + " "+ str (self.ycor ()) +")"
13
14 def __lt __ (self, otro):
15 regreso self.ycor () <other.ycor ()

Objetos de la clase Point tienen un ordenamiento porque hemos definido el


operador menor que (es decir, <) Escribiendo un método __lt__ en la clase. Una
vez definidos, esto Menos de órdenes de operador todos los elementos de la clase.
La mayoría de las clases o tipos incorporados en Python ya tiene una
implementación para el método __lt__ así que no tenemos que definir este método
para tipos como int, float, y str. Las cadenas se comparan lexicográfico en Python,
ya que son en casi todos los idiomas que soporta la comparación de cadenas.
ordenamiento lexicográfico significa que las cadenas se comparan de izquierda a
derecha hasta que un personaje se encuentra que es diferente que el carácter en la
posición correspondiente en la otra cadena. En otras palabras, la clasificación una
secuencia de cadenas significa que terminarán en orden alfabético como se vería
en un diccionario. Bajo algunas condiciones son listas que se puede pedir,
también. Para las listas tengan un ordenamiento, los elementos en índices dentro
de las listas correspondientes deben ser ordenable. Tenga en cuenta estas
comparaciones de muestras.
Ordenando 4.4Item 101

1 Python 3.2 (R32: 88452, 20 Feb 2011, 10:19:59)


2 [GCC 4.0.1 (Apple Inc. construir 5493)]
3 Tipo de "ayuda", "derechos de autor", "créditos" o "licencia" para más
información.
4 [Evaluar untitled-3.py]
5 = Lst [1,2,3]
6 LST2 = lista ( "abc")
7 LST2
8 ['a B C']
9 lst <LST2
10 Rastreo (llamadas recientes más última):
11 Archivo "<cadena>", línea 1, en <fragmento>
12 builtins.TypeError: tipos unorderable: int () <str ()
13 lst3 = [4,5,6]
14 lst <lst3
15 Cierto
16 lst4 = [1,3,2]
17 lst <lst4
18 Cierto
19 lst5 = [1,2,2]
20 lst5 <lst
21 Cierto
22 lst6 = [1,1, 'a']
23 lst6 <lst
24 Cierto

Comparando LST y LST2 no funcionó porque los elementos de las dos listas no
pueden ser pedidos. No se puede comparar un entero y una cadena. Sin embargo,
dos listas con los elementos similares se pueden comparar. Lista de comparación
se realiza lexicographi-camente como cuerdas. Tenga en cuenta el último ejemplo.
Las dos listas [1,2,3] y [1,1 'a'] se puede comparar porque el orden lexicográfico
no requiere que 3 y 'a' pueden comparar.
Hay algunos tipos de Python que no tienen un orden natural. Por ejemplo, no
hay un orden natural para los diccionarios de Python. No es posible ordenar una
lista de diccionarios. Por supuesto, es poco claro por qué querría así. Puesto que
no hay orden natural no parece ser una razón para ordenarlos. Sin embargo, si
había una manera de crear un orden en un conjunto de diccionarios se podría
definir por sí mismo escribiendo su propia clase que hereda de la clase dict y
definir su propio método __lt__ tal como lo hicimos para la clase Point.
Una vez que tenemos un ordenamiento de los elementos en una lista, podemos
ordenar los elementos de acuerdo a ese pedido. Las listas tienen un método para
ordenar que ordenar los elementos de una lista en función de su pedido. El código
en la Sección.4.4.2 ilustra cómo los elementos de una lista se pueden ordenar.

4.4.2 Al llamar al método clasificar


1 def principal():
2 t = turtle.Turtle ()
3 t.ht ()
4 pantalla = t.getscreen ()
5 = Lst []
6
para i en el rango
7 de (10):
102 4 secuencias

8 para j en el rango de (10):


par = Point (pantalla,
9 i, j)
10 lst.append (par)
11
12 lst.sort ()
13
14 para p en LST:
15 impresión(pag)

Cuando el código en la Sección. 4.4.2 se llama que imprime los puntos en el


orden de su distancia desde el eje x de la siguiente manera.
(0,0)
(1,0)
(2,0)
(3,0)
(4,0)
(5,0)
(6,0)
(7,0)
(8,0)
(9,0)
(0,1)
(1,1)
(2,1)
(3,1)
(4,1)
(5,1)
(6,1)
(7,1)
...

Pero, lo que hace este tipo método de trabajo y cuál es su costo? En otras
palabras, ¿qué tipo de algoritmo de ordenación hace uso de Python y cuál es su
complejidad computacional? Exploramos estas preguntas en las siguientes
secciones.

4.5 selección Ordenar

En la última sección hemos aprendido que podemos llamar un método llamado


tipo en una lista para ordenar los elementos de la lista en orden ascendente. Orden
ascendente se determina por el menos de operador tal como se define en los
artículos. Entonces, ¿cómo funciona el algoritmo de ordenación. Uno de los
primeros algoritmos de clasificación se llama selección Ordenar y sirve como un
buen punto de partida para entender los algoritmos de clasificación. Sin embargo,
este no es el algoritmo de ordenación utilizado por Python. Vamos a averiguar qué
pronto.
El algoritmo de ordenación por selección es bastante simple de describir. El
algoritmo comienza por encontrar el valor más pequeño para colocar en la primera
posición en la lista. Esto lo hace mediante una búsqueda lineal a través de la lista y
en el camino recordando el índice del elemento más pequeño que encuentra. El
algoritmo utiliza la conjetura y comprobar el patrón por primera adivinar que el
más pequeño es el primer elemento de la lista y después de comprobar los
elementos posteriores para ver si se hace una suposición incorrecta. Esta parte del
algoritmo es la parte de selección. La función de selección hace esta selección.
Ordenar 4.5Selection 103

4.5.1 Selección Ordenar la función de selección de


1 def seleccionar (SEC, comenzar):
2 minindex = inicio
3
4 para j en el rango (start + 1, len (SEC)):
5 Si SEQ [minindex]> SEQ [j]:
6 minindex = j
7
8 regreso minindex

El argumento de arranque indica a la función de selección por dónde empezar a


buscar los elementos más pequeños. Se busca desde el principio hasta el final de la
secuencia para el elemento más pequeño.
El algoritmo de ordenación por selección funciona mediante la búsqueda de los
elementos más pequeños usando la función de selección y colocación de dicho
elemento en la primera posición de la secuencia. Ahora el valor en la primera
posición se debe poner en otro sitio. Simplemente se intercambia con la ubicación
del valor que se está moviendo. El algoritmo procede de lado buscando el valor
más pequeño de la segunda secuencia. Dado que el valor más pequeño se
encuentra ahora en la primera ubicación en la secuencia, el algoritmo de
ordenación por selección empieza a buscar desde la segunda posición en la lista
para el valor más pequeño. Cuando se encuentra el valor más pequeño (que es
realmente el valor más pequeño para el segundo de la lista) el valor en la segunda
posición y este valor se intercambian. A continuación, el algoritmo de ordenación
por selección busca el elemento más pequeño a partir de la tercera ubicación en la
secuencia. Este patrón se repite hasta que todos los elementos de la secuencia han
sido ordenados. La función selSort hace la clasificación real para el algoritmo.

4.5.2 El Código de selección Ordenar


1 def selSort (SEC):
2 para i en el rango (len (SEC) -1):
3 minindex = select (SEC, i)
4 tmp = SEQ [i]
5 SEQ [i] = SEQ [minindex]
6 SEQ [minindex] = tmp

Podemos visualizar el algoritmo de ordenación por selección mediante la ejecución


de una animación de la misma clasificación. La animación se representa en la
Fig.4.3habiendo ordenado más de la mitad de los valores en una secuencia. Los puntos
verdes representan los elementos que están ahora en su ubicación correcta en la
secuencia. La altura del punto desde el eje x (es decir, el valor de y) es su valor. El eje
x es la posición en la lista. En esta animación todos los valores entre 0 y 199 se están
ordenadas en orden ascendente. La esquina superior derecha representa los valores que
aún no han sido ordenados. El algoritmo comienza a buscar el siguiente valor más
pequeño justo a la derecha de la línea diagonal verde. Se encuentra que el valor
mínimo (es decir, más cerca del eje x) pasando a través de todos los puntos restantes
sin ordenar. Una vez que encuentra el pequeño, punto más corto, se intercambia con el
extremo izquierdo de puntos para ponerlo en está posición en la lista ordenada. El
código completo de esta animación se da en la Sección.20.3y puede ser descargado
desde el sitio web que acompaña a este texto. ¡Pruébalo!
104 4 secuencias

Fig. 4.3 Ordenar la selección de instantáneas

Considere la clasificación de la lista [5 8 2 6 9 1 0 7] tal como se representa en


la Fig.4.4. Después de cada llamada de la función de selección de la función
selSort el siguiente elemento de la lista se coloca en su lugar definitivo.
Clasificación de la lista conduce a los pasos intermedios como se muestra. Cada
vez que la función de selección se llama el nuevo elemento más pequeño se
intercambia con el primer lugar en el resto de la lista para moverse al siguiente
elemento más pequeño en su ubicación dentro de la lista ordenada.
Para encontrar cada nuevo elemento más pequeño que llamamos de selección que
debe pasar por el resto de la lista buscando el elemento mínimo. Después de cada paso
a la lista en la Fig.4.4es un elemento más cerca de la clasificación toda la lista. Resulta
que este temprano intento de escribir un algoritmo de ordenación no es tan grande. La
2
complejidad de este algoritmo es O (n ) Debido a que cada vez a través del bucle for
de la función selSort que llamamos la función de selección que tiene su propio bucle.
El i de bucle se ejecuta n veces y cada vez que se ejecuta el bucle j debe pasar por uno
menos artículo que busca el valor más pequeño que se deja de ser ordenados. Así, la
primera vez que ejecuta el cuerpo del bucle n para j- 1 veces, entonces n - 2 veces la
segunda vez seleccione se llama, entonces n -3 veces y así sucesivamente. Hemos visto
2
este patrón antes. La suma de los primeros n enteros tiene un n término en su fórmula.
2
Por lo tanto, la selección de especie es O (n ). Esto significa que a medida que
tratamos de resolver algunas listas más grandes del algoritmo realmente comenzará a
disminuir. Nunca se debe utilizar este algoritmo para la clasificación. Incluso en las
listas pequeñas que podemos hacer mucho mejor.
Ordenar 4.6Merge 105

Fig. 4.4 Selección Una especie de lista

4.6 combinar Ordenar

Dividir y conquistar, como los antiguos romanos podrían haber dicho, es una
estrategia eficaz de batalla. Resulta que este concepto es muy importante a la hora
de escribir los algoritmos. El algoritmo de ordenamiento por mezcla es un ejemplo
de un algoritmo de divide y vencerás. Dividir y conquistar algoritmos suelen ser
escrita de forma recursiva, pero no necesariamente tiene que ser. La premisa
básica es que dividimos un problema en dos partes. Cada una de las dos piezas es
fácil de resolver que tratar de abordar todo el problema a la vez porque las dos
piezas son cada uno más pequeño.
106 4 secuencias

4.6.1 El código de fusión Ordenar


1 def fusionar (SEC, inicio, mediados, detener):
2 = Lst []
3 i = inicio
4 j = mediados
5
6 # Combinar las dos listas, mientras que cada uno tiene más elementos
7 mientras i <mediados y j <parada:
8 Si SEQ [i] <SEQ [j]:
9 lst.append (SEC [i])
10 i + = 1
11 más:
12 lst.append (SEC [j])
13 j + = 1
14
15 # Copia en el resto de la secuencia de inicio a mediados
16 mientras i <media:
17 lst.append (SEC [i])
18 i + = 1
19 # Muchas implementaciones de combinación de ordenar copiar el resto
20 # De la secuencia de j para detener en este punto.
21 # Esto no es necesario ya que en la siguiente parte
22 # Del código de la misma parte de la secuencia haría
23 # Copiarse de vuelta al mismo lugar.
24 # Mientras que j <parada:
lst.append (SEC
25 # [j])
26 # j + = 1
27 # Copia los elementos de nuevo a la secuencia original
28 para i en el rango (len (LST)):
29 ss [START + i] = lst [i]
30
31 def mergeSortRecursively (SEC, iniciar, detener):
32 # Debemos utilizar> = aquí sólo cuando la secuencia estamos ordenando
33 # esta vacio. De lo contrario iniciar == stop-1 en el caso base.
34 Si empezar> = stop-1:
35 regreso
36
37 mediados = (start + parada) // 2
38
39 mergeSortRecursively (SEC, iniciar, a mediados)
40 mergeSortRecursively (SEC, mediados, parada)
41 fusionar (SEC, inicio, mediados, parar)
42
43 def mergesort (SEC):
44 mergeSortRecursively (SEC, 0, len (SEC))

El algoritmo de ordenamiento por mezcla toma esta estrategia de dividir y


conquistar al extremo. Divide la lista, luego se divide una y otra vez, hasta que nos
quedamos con las listas de tamaño 1. Una lista secundaria de longitud 1 ya está
ordenado. Dos sublistas ordenados se pueden combinar en una sola lista ordenada
en el tiempo O (n). Una lista se puede dividir en las listas de tamaño 1 dividiendo
repetidamente en O (log n). Cada una de las listas de Split están a continuación, se
fusionaron juntos en tiempo O (n). Esto resulta en una complejidad de O (n log n)
para la fusión de clasificación. El código de fusión especie aparece en la
Sección.4.6.1.
La función de fusión se encarga de la fusión de dos sublistas adyacentes. La primera
lista secundaria se extiende desde el principio hasta mediados-1. La segunda sublista
se extiende desde mediados de stop-1. Los elementos
Ordenar 4.6Merge 107

de los dos sublistas según se copian, en tiempo O (n), a una nueva lista. A
continuación, la lista ordenada se copia de nuevo en la secuencia original, de nuevo en
tiempo O (n). En la función de fusión, el primer bucle mientras se ocupa de la fusión
de las dos sublistas hasta la una o la otra sub-lista está vacía. El segundo y el tercero,
mientras que los bucles se encargan de terminar lo que tenía sublista los elementos
sobrantes. Sólo una lista secundaria habrá sobras de elementos tan sólo una condición
en el segundo y tercer ciclos while volverá a ser cierto.
Observe que el tercer bucle while en el código está comentado. Copiar elementos de
J a tope en la tercera mientras bucle no es necesario, ya que sólo se copian la derecha
de nuevo al mismo lugar cuando el contenido de lst se copian de nuevo a la secuencia
SEC. Esta optimización acelera ordenamiento por mezcla un poco. Otra optimización
consiste en asignar previamente una lista más en la que copiar los valores y luego se
alternan entre la fusión en el original y la copia preasignados. De esta manera se evita
la sobrecarga de crear y añadiendo a las listas. Codificación de cualquiera de estos dos
optimizaciones no mejora la complejidad computacional del algoritmo, pero puede
mejorar su rendimiento general ligeramente. Una de las críticas del algoritmo de
ordenamiento por mezcla es que los elementos de las dos sublistas no se pueden
combinar sin copiar a una nueva lista y luego de vuelta otra vez.
La función mergesort llama a una función de ayuda para conseguir todo comenzó.
Se llama a la función mergeSortRecursively con la secuencia y los valores de inicio y
de parada que indican la lista entera debe ser ordenada. Los parámetros de inicio y de
parada se utilizan cuando se divide la lista. La lista no está dividida físicamente al
llamar mergeSortRecursively. En cambio, los valores de inicio y de parada se utilizan
para calcular el punto medio entre ellos y luego las dos mitades están ordenados de
forma recursiva mitad. Dado que cada sublista es más pequeño, podemos estar seguros
de que la llamada recursiva hace su trabajo y ordena los dos sublistas. Entonces nos
queda más que combinar los dos sub-listas ordenadas llamando a la función de fusión.
El caso base para la función recursiva es cuando el tamaño de la lista secundaria es 1.
En ese momento tenemos una sublista ordenada.
En la Fig. 4.5toda la mitad izquierda de la lista ha sido ordenada. Además, tres
sublistas en la mitad derecha han sido ordenados y la tercera y cuarta lista
secundaria están en proceso de ser fusionado. Los puntos verdes representan las
porciones de la secuencia que se ordenan y los puntos negros indican la parte no
seleccionada de la secuencia original. Las líneas rojas en la parte inferior reflejan
las llamadas recursivas que se encuentran actualmente en la pila de tiempo de
ejecución. La longitud de la línea roja muestra la porción de la secuencia que se
está ordenada por su llamada recursiva correspondiente. La línea azul subraya
actualmente las dos sublistas se fusionaron.
El argumento que se funden tipo se ejecuta en O (n log n) tiempo necesita sólo un
poco de explicación. La división repetitiva de los resultados de la lista en O (log n) se
divide. Al final tenemos listas de tamaño 1. Si tuviéramos que contar cada
combinación que se produce no habría n / 2 se funde en la parte inferior, seguido de n /
4 se funde en el siguiente nivel, y así sucesivamente conduce a esta suma.
Iniciar
sesión2no
rte
yo Iniciar
El número de fusiones ≥ 2 =2
sesión norte
2 -1≈
norte
yo =0
Este análisis parece sugerir que la complejidad del algoritmo de ordenamiento por
2
mezcla es O (n ), Ya que hay más o menos n se funde cada uno de los cuales es O (n)
en sí. sin embargo, el
108 4 secuencias

Fig. 4.5 Combinar Ordenar instantánea

Fig. 4.6 Combinar Ordenar Merges

2
algoritmo no es O (n ). Para ver por qué, considere la clasificación de la lista [5 8 2 6
9 1 0 7]. Después de dividir en varias ocasiones las listas nos ponemos manos a las
listas de tamaño de uno como se muestra en la primera lista de la Fig.4.6. Los artículos
individuales se combinan de dos en dos para formar listas ordenadas de dos, que se
muestran en la segunda lista de elementos. Mientras que hay cuatro fusiones que tienen
lugar en el nivel más bajo de la fusión tipo, las cuatro fusiones son cada uno para las
listas de dos elementos (es decir, uno de cada lista) y juntos forman una lista de n
elementos. Así que podemos agrupar todos estos cuatro combina junto al descubrir que
todas las fusiones en que toma más profundo nivel de O (n). No cada uno, pero todas
las fusiones en el nivel más profundo cuando se combina son O (n).
Ordenar 4.6Merge 109

En la segunda versión de la lista, dos fusiones se hacen para las listas de


longitud dos. Sin embargo, cada combinación se realiza en la mitad de la lista. El
medio púrpura es una mezcla, el medio verde incluye los elementos que están en
la segunda combinación. En conjunto, estas dos fusiones incluyen todos los
elementos n de nuevo. Por lo tanto, en el nivel de segundo más profundo de nuevo
en la mayoría de los n elementos se combinan en tiempo O (n).
Finalmente, la última combinación es de todos los elementos de color amarillo
de las dos sub-listas ordenadas. Esta combinación también toma tiempo O (n), ya
que combina todos los elementos de la lista, lo que resulta en la lista ordenada se
ve en la última versión de la lista.
Así, mientras que la concentración es una operación O (n), las fusiones tienen
lugar en sublistas de los n elementos de la lista lo que significa que podemos
contar con la fusión en cada nivel como O (n) y no tenemos que contar cada
individuo fusionar operación como O (n). Puesto que hay registro de n niveles
para el algoritmo de combinación de tipo y cada nivel toma O (n) para combinar,
el algoritmo es O (n log n).

4.7 Ordenación rápida

En un sentido, el algoritmo de ordenación rápida es exactamente lo contrario del


algoritmo de ordenamiento por mezcla. También es el más ampliamente utilizado y
uno de los algoritmos de ordenación más eficaces conocidos. Ordenación rápida es de
nuevo un algoritmo divide y vencerás y por lo general se escribe de forma recursiva.
Pero, en combinación especie se divide la lista hasta llegar a un tamaño de 1 y luego se
fusiona listas ordenadas, el algoritmo quicksort hace la primera fusión y luego se
divide la lista. No podemos combinar una lista sin ordenar. En su lugar, dividimos en
dos listas. Lo que queremos es preparar la lista para la clasificación rápida se puede
llamar de forma recursiva. Pero, si vamos a tener éxito, las dos sublistas deben ser de
alguna manera más fácil de resolver que el original. Esta preparación para la división
se llama partición. Para particionar una lista elegimos un elemento de pivote. Piense en
la clasificación rápida particionar una lista en todos los artículos más grandes que el
pivote y todos los elementos menores que el pivote. Ponemos todos los artículos más
grandes a la derecha del pivote y todos los artículos fueran algo a la izquierda del
pivote. Una vez hecho esto, dos cosas son ciertas:

• El pivote está en su ubicación final en la lista.


• Los dos sublistas son ahora más pequeñas y por lo tanto se pueden quicksorted. Una
vez que las dos sublistas se clasifican esto hará que toda la lista para estar en forma
ordenada, porque la izquierda serán los valores que ascienden hasta el pivote, el
pivote está en el lugar correcto, y los valores mayores que el pivote todo será en su
ubicación correcta, también.

Quicksort es un algoritmo de divide y vencerás. Para obtener el mejor rendimiento,


nos gustaría dividir la secuencia por la mitad en dos secuencias de igual tamaño. Esto
significaría que tendríamos que escoger el valor exactamente en el medio a ser el eje
desde el pivote se utiliza para dividir las dos listas. Por desgracia esto no es posible si
vamos a hacerlo de manera eficiente. Para la clasificación rápida de tener O (n log n)
com-complejidad, como la combinación de una especie, debe particionar la lista en
tiempo O (n). Debemos elegir un pivote de forma rápida y hay que elegir bien. Si no
elegimos un pivote cerca de la media, no vamos a obtener la O (n log n) la complejidad
que se espera. Resulta que la elección de un pivote azar de la lista es lo
suficientemente bueno. Una forma de garantizar una elección al azar
110 4 secuencias

del pivote es tener el inicio algoritmo quicksort por aleatorización de la secuencia.


El algoritmo quicksort se da en la Sección. 4.7.1.

4.7.1 El Código ordenación rápida


1 la importación al azar
2
3 def partición (SEC, arranque, parada):
4 # PivotIndex proviene de la ubicación de inicio en la lista.
5 pivotIndex = inicio
6 pivote = SEQ [pivotIndex]
7 i = start + 1
8 j = stop-1
9
10 mientras i <= j:
11 #while i <= j y SEQ [i] <= pivote:
12 mientras i <= j y no pivote <SEQ [i]:
13 i + = 1
14 #while i <= j y SEQ [j]> pivote:
15 mientras i <= j y el pivote <SEQ [j]:
16 j- = 1
17
18 Si i <j:
19 tmp = SEQ [i]
20 SEQ [i] = SEQ [j]
21 SEQ [j] = tmp
22 i + = 1
23 j- = 1
24
25 SEQ [pivotIndex] = SEQ [j]
26 SEQ [j] = pivote
27
28 regreso j
29
30 def quicksortRecursively (SEC, iniciar, detener):
31 Si empezar> = stop-1:
32 regreso
33
34 # PivotIndex termina en entre las dos mitades
35 # Donde el valor de pivote se encuentra en su ubicación final.
36 pivotIndex = partición (SEC, iniciar, detener)
37
38 quicksortRecursively (SEC, inicio, pivotIndex)
39 quicksortRecursively (SEC, pivotIndex + 1, parar)
40
41 def quicksort (SEC):
42 # Aleatorizar la secuencia de primero
43 para i en el rango (len (ss)):
44 j = random.randint (0, len (SEC) -1)
45 tmp = SEQ [i]
46 SEQ [i] = SEQ [j]
47 SEQ [j] = tmp
48
49 quicksortRecursively (SEC, 0, len (SEC))

Una vez que la lista se asignaron al azar, recogiendo un pivote al azar se vuelve más
fácil. La función de partición recoge el primer elemento de la secuencia que el pivote.
La partición se inicia desde
4.7Quicksort 111

ambos extremos y que funciona a la manera de la media. Esencialmente cada vez que
se encuentra un valor más grande que el pivote en el lado izquierdo y un valor menor
que el pivote se encuentra en el lado derecho, los dos valores se intercambian. Una vez
que se llega a la media de los dos lados, el pivote se intercambia en su lugar. Una vez
que se divide la secuencia, el algoritmo quicksort se llama de forma recursiva sobre las
dos mitades. Variables i y j son los índices de los valores de izquierda y derecha,
respectivamente, durante el proceso de partición.
Si nos fijamos en el código de partición, los dos comentaron mientras que las
condiciones de bucle son probablemente más fácil de entender que el código sin
comentar. Sin embargo, el código de menú reduci-mentado sólo utiliza el menor de
operador. Quicksort es el algoritmo de ordenación utilizado por el método de
clasificación en las listas. Sólo se requiere que se defina el menos de operador entre los
elementos de la secuencia. Al escribir los dos bucles while como lo hemos hecho, la
única orden requerido es definido por el operador menor que al igual que requiere
Python.
La instantánea en la Fig. 4.7muestra el efecto de partición en una secuencia. En
esta figura, la secuencia ha sido dividido dos veces. La primera partición recogió
un pivote que era centro casi muerto. Sin embargo, la segunda partición recogió un
pivote que no era tan bueno. La línea roja indica la parte de la secuencia que se
está repartió actualmente. Ver cómo el más a la izquierda valor en ese sub-
secuencia es el valor de pivote. Los dos puntos verdes son los valores de pivote
que ya están en sus posiciones correctas. Todos los valores por encima del pivote
va a terminar en la partición a la derecha del pivote y todos los valores a la
izquierda del pivote son menores que el pivote. Esta es la naturaleza de la
clasificación rápida. Una vez más, por la complejidad amortizado podemos
encontrar que el algoritmo de ordenación rápida se ejecuta en O (n log n) tiempo.
Considere la clasificación de la lista [5 8 2 6 9 1 0 7] utilizando quicksort.
Figura4.8representa la lista después de cada llamada a la función de partición. El
pivote en cada llamada se identifica por el elemento de color naranja. La función de
partición divide de la lista que se extiende a la derecha de su pivote. Después de la
partición, el pivote se trasladó a su emplazamiento definitivo mediante el canje
Fig. 4.7 ordenación rápida instantánea
112 4 secuencias

Fig. 4.8 Quicksorting una Lista

con el último elemento que es menor que el pivote. Luego de partición se realiza
sobre los dos sublistas resultantes.
La aleatorización hecho en el primer paso de la clasificación rápida ayuda a
elegir un valor de pivote más al azar. Esto tiene consecuencias reales en el
algoritmo de ordenación rápida, especialmente cuando la secuencia pasa a la
clasificación rápida tiene una oportunidad de ser ya ordenados. Si la secuencia
dada a quicksort está ordenada, o casi ordenada, ya sea en orden ascendente o
descendente, entonces quicksort no logrará O (n log n) la complejidad. De hecho,
2
el peor de los casos la complejidad del algoritmo es O (n ). Si el pivote elegido es
el siguiente valor de menos o más grande, entonces la partición no dividir el
problema en que sublistas más pequeñas como ocurrió cuando 9 fue elegido como
un pivote para una sublista en la Fig.4.8. El algoritmo simplemente poner un valor
en su lugar y terminar con una gran partición de todo el resto de los valores. Si
2
esto sucedió cada vez que un pivote fue elegido daría lugar a O (n ) Complejidad.
La aleatorización de la lista antes de la quicksorting que ayudará a garantizar que
esto no suceda.
Combinar especie no se ve afectada por la elección de un pivote, ya que no es necesaria
ninguna elección. Por lo tanto, combinar especie no tiene un caso peor o mejor de los casos
a considerar. Siempre va a lograr O (n log n) la complejidad. Aun así, la clasificación
rápida tiene un mejor rendimiento en la práctica que se funden especie porque el algoritmo
de ordenación rápida no tiene que copiar a una nueva lista y luego de vuelta otra vez. El
algoritmo de ordenación rápida es el estándar de facto de los algoritmos de ordenación.

4.8 Las secuencias de dos dimensiones

A veces los programadores necesitan para representar secuencias bidimensionales en


un programa. Esto puede hacerse fácilmente mediante la creación de una lista de listas.
La lista principal puede representar cualquiera de las columnas o las filas de la matriz.
Si la lista principal contiene referencias a
4.8 Secuencias bidimensionales 113

Fig. 4.9 Una matriz de 2 dimensiones

las filas, a continuación, la matriz se dice que es en forma importante fila. Si la


lista principal contiene referencias a las columnas de la matriz, entonces es en
forma importante la columna. La mayoría del tiempo, las matrices están
construidos en forma importante fila. En la Fig.4.9una matriz se dibuja con una
orientación importante fila, pero la matriz podría representar ya sea la fila
principal forma importante o columna. La organización real de los datos es la
misma en ambos sentidos. hacen referencia a los artículos puntos en la lista
principal. La lista de artículos contiene referencias a cada una de las filas de la
matriz.
Por ejemplo, considere un programa que reproduce tres en raya contra un
oponente humano. Habría que representan el tablero de tres en raya que se juega
en. Para ello, vamos a crear una clase Junta que imita nuestra clase PyList en la
sección anterior. La organización de nuestra clase junta se muestra gráficamente
en la Fig.4.9. El esquema para la clase Junta se da en la Sección.4.8.1.

4.8.1 La Clase Junta


1 Junta de clase:
2 # Cuando se construye una tabla, es posible que desee hacer una copia de
la junta.
3 # Esto puede ser una copia superficial de la junta porque los objetos son
tortuga
4 # Inmutable desde la perspectiva de un objeto bordo.
5 def __init __ (self, tablero = None):
6 self.items = []
7 para i en el rango de (3):
8 rowlst = []
9 para j en el rango de (3):
10 Si Junta == None:
rowlst.append
11 (simulada ())
12 más:
114 4 secuencias

13 rowlst.append (tablero [i] [j])


14
15 self.items.append (rowlst)
dieciséis
17 # El método GetItem se utiliza para indexar en la junta. Debería
18 # Devolver una fila de la tabla. Esa fila en sí es indexable (que es sólo
19 # Una lista) para acceder a una fila y columna en el tablero se pueden
escribir
20 # Bordo [fila] [columna] a causa de este método.
21 def __getitem __ (self, índice):
22 regreso self.items [índice]
23
24 # Este método debe devolver cierto si los dos tableros, yo y el otro,
25 # Representan exactamente el mismo estado.
26 def __eq __ (self, otro):
27 pasar
28
29 # Este método mutar este tablero para contener todos ficticia
30 # tortugas. De esta manera la junta se puede restablecer cuando un nuevo
juego
31 se selecciona #. NO se debe utilizar, excepto cuando se inicia
32 # Un nuevo juego.
33 def reset (auto):
34 screen.tracer (1)
35 para i en el rango de (3):
36 para j en el rango de (3):
self.items [i] [j] .goto (-100,
37 -100)
self.items [i] [j] = simulada
38 ()
39
40 screen.tracer (0)
41
42 # Este método debe devolver un entero que representa la
43 # Estado de la junta. Si el equipo ha ganado, devuelve 1.
44 # Si el ser humano ha ganado, devuelven -1. De lo contrario, devuelve 0.
45 def eval (self):
46 pasar
47
48 # Este método debe devolver True si la junta
49 # Está completamente llena (no hay tortugas ficticias).
50 # En caso contrario, se debe devolver false.
51 def completa (auto):
52 pasar
53
54 # Este método debe elaborar las X y O de
55 # De este tablero en la pantalla.
56 def drawXOs (self):
57 para fila de alcance (3):
58 para col en el rango de (3):
59 Si auto [fila] [columna] .eval () = 0:
60 auto [fila] [col] .st ()
auto [fila] [col] .goto (col * 100 + 50,
61 fila * 100 + 50)
62
63 screen.update ()

Debido a que cada fila es en sí una lista en la clase Junta, sólo puede utilizar la
clase incorporado en la lista de las filas de la matriz. Cada ubicación en cada fila
de la matriz puede contener o bien un X, un O, o un objeto ficticio. Los objetos
ficticios están allí por conveniencia y representan un lugar abierto en el tablero. La
igualdad, eval, y los métodos completos se dejan como ejercicio para el estudiante.
El propósito de cada se describirá en la siguiente sección.
4.8 Secuencias bidimensionales 115

Muchos juegos, tanto de animación como de otro tipo, son fáciles de implementar el
uso de Tkinter y gráficos de tortuga. Los personajes animados o fichas en un juego
pueden ser implementados como una tortuga que se mueve por la pantalla según sea
necesario. Para el juego de tres en raya las X y O del pueden ser implementados como
RawTurtles. Un RawTurtle es igual que un objeto tortuga excepto que debemos
proporcionar el lienzo donde un RawTurtle se mueve alrededor. El código en la
Sección.4.8.2 contiene las tres clases que definen las X, la junta de la clase, y maniquí
especial que es un marcador de posición para lugares abiertos en el tablero.

4.8.2 Los X, O, y las clases simuladas


1 Human = -1
2 Equipo = 1
3
4 # Esta clase es sólo para los objetos de marcador de posición cuando se ha hecho
ningún movimiento
5 # Todavía en una posición en el tablero. Tener eval () devuelven 0 es conveniente
cuando hay
6 # Movimiento ha sido realizado.
7 clase simulada:
8 def __en si mismo):
9 pasar
10
11 def eval (self):
12 regreso 0
13
14 def Goto (self, x, y):
15 pasar
dieciséis
17 # En los X y O clases por debajo del constructor comienza inicializando el
18 # RawTurtle parte del objeto con la llamada a super () .__ init __ (lienzo). los
19 # Super () llamada devuelve la clase de la superclase (la clase por encima de la
X o O
20 # En la jerarquía de clases). En este caso, la superclase es RawTurtle. Entonces,
21 # Llamando __init__ en la superclase inicializa la parte del objeto que está
22 # A RawTurtle.
23 la clase X(RawTurtle):
24 def __init __ (self, lona):
25 super () .__ init __ (lienzo)
26 self.ht ()
27 . Self.getscreen () register_shape ( "X", ((- 40, -36), (- 40, -44), (0, -4), (40, -
44), \
28 (40, -36), (4,0), (40,36), (40,44), (0,4), (- 40,44), (- 40,36), (- 4,0 ), (- 40, -
36)))
29 self.shape ( "X")
30 self.penup ()
31 self.speed (5)
32 self.goto (-100, -100)
33
34 def eval (self):
35 regreso Computadora
36
37 clase O(RawTurtle):
38 def __init __ (self, lona):
39 super () .__ init __ (lienzo)
40 self.ht ()
41 self.shape ( "círculo")
42 self.penup ()
43 self.speed (5)
44 self.goto (-100, -100)
45
46 def eval (self):
47 regreso Humano
116 4 secuencias

4.9 El Algoritmo Minimax

El maniquí, X, y las clases O todos tienen un método eval que devuelve un 1 para
un movimiento de ordenador, una -1 para un movimiento humano, o un 0 para
ningún movimiento todavía. Los valores de estos movimientos se utilizan en un
algoritmo llamado Minimax. El algoritmo Minimax es un algoritmo recursivo que
se utiliza en dos personas de juego juego en el que un jugador es el equipo y cada
jugador tiene la opción de algún número de movimientos que se pueden hacer al
tomar las curvas. El algoritmo minimax es simple. La idea es que cuando es el
turno de la computadora debe elegir el movimiento que será lo mejor para el
equipo. Cada movimiento posible es analizado para encontrar el valor que sería lo
mejor para el equipo. Vamos a dejar que un 1 representan el mejor movimiento
para el ordenador. La peor jugada del ordenador podría hacer que estará
representada por una-1. Los valores de movimiento puede variar entre 1 y -1.
Cuando es el turno de la computadora que captará el movimiento que da como
resultado el valor máximo movimiento. Esa es la porción máxima de Minimax.
Para encontrar la mejor jugada, el equipo va a jugar el juego, alternando entre el
mejor movimiento que podría hacer el mejor movimiento y el ser humano podría
hacer. Una mejor movimiento para un ser humano sería una-1. Cuando es el turno de
los humanos, el equipo asumirá que el ser humano hará el mejor movimiento que él /
ella puede hacer. Esta es la parte min de minimax.
La función Minimax se da dos argumentos, el jugador (ya sea una -1 para el ser
humano o un 1 por equipo) y el tablero para el juego. El caso base para esta
función recursiva comprueba si se ha producido una de las tres cosas.

1. La actual junta es una victoria para el equipo. En ese caso Minimax devuelve
un 1 por una victoria del equipo.
2. La actual junta es una victoria para el ser humano. En ese caso devuelve un
Minimax-1 para una victoria humana.
3. La actual junta está llena. En ese caso, ya que ganaron ni humano o un
ordenador, Minimax devuelve un 0.

La parte recursiva de la función Minimax examina el argumento jugador. Si el


jugador es el equipo, a continuación, la función intenta cada posible movimiento
en el tablero haciendo un movimiento ordenador. Se coloca un movimiento
ordenador en ese lugar en una copia de la junta y llama Minimax con el ser
humano como el siguiente jugador en ese tablero. El Algo-rithm utiliza el patrón
de respuesta y verificación para encontrar el máximo de todos los valores posibles
que vienen de vuelta de las llamadas recursivas a Minimax. La función minimax
vuelve entonces que el valor máximo.
Cuando Minimax se llama con el ser humano como el siguiente jugador para
hacer un movimiento, que hace lo mismo que cuando el equipo se llama como el
jugador. Se hace un movimiento humano en una copia de la junta y llama de
forma recursiva Minimax en la copia de la tabla con el equipo que el siguiente
jugador para jugar. El algoritmo utiliza el patrón de la conjetura y verificación
para encontrar el mínimo de todos los valores posibles que regresan de llamar
Minimax de forma recursiva.
Hay una pequeña parte difícil de Minimax. El algoritmo minimax se llama con una
tabla y el siguiente jugador para hacer un movimiento. Devuelve una serie en algún
lugar entre-1 y 1 que indica qué tan probable es que el ordenador o el ser humano va a
ganar dada
4.9 El Minimax Algoritmo 117

ese tablero. Sin embargo, no le dice que se mueven es la mejor para hacer. Para hacer
frente a esto podemos tener el código que se ejecuta el turno del equipo hacer un poco
de la obra. Para el código de ordenador a su vez para encontrar el mejor movimiento
que hace un movimiento en una copia de la junta, las llamadas Minimax con el ser
humano como el siguiente jugador para hacer un movimiento, y luego se registra el
valor que regresa de la llamada a Minimax. El código de ordenador a su vez utiliza el
patrón de respuesta y verificación para encontrar el valor máximo para todos los
movimientos posibles y el movimiento asociado que dio lugar a ese valor. Después de
encontrar el mejor movimiento, a su vez de la computadora termina por la toma de
ordenador que se mueven en el tablero y regresar por lo que el ser humano puede hacer
su siguiente movimiento. El código de tres en raya, que se puede encontrar en el sitio
web que acompaña al texto o en la Sección.20.5, Contiene el esquema para el juego.
La función Minimax y el código computerTurn se dejan como ejercicio para el lector.
Minimax puede ser utilizado en muchos juegos de dos personas de información
perfecta, tales como las damas y conectar cuatro. La información perfecta término
significa que ambos jugadores pueden ver el estado del juego [3]. El poker es un juego
de información imperfecta por lo que no sería adecuado para el algoritmo minimax.
Cabe señalar que de tres en raya tiene un pequeño espacio de búsqueda basta con que
el equipo puede resolver el juego. Eso significa que nunca se perderá. La mayoría de
los juegos sin embargo, no tienen solución, al menos con el medio de la computadora.
Por ejemplo, conectar cuatro tiene un espacio de búsqueda mucho más grande y no
puede ser resuelto por completo. La heurística se aplican a juegos como Conecta
Cuatro para estimar lo bueno o malo es un tablero después de que el algoritmo
Minimax ha buscado tan profunda como la que puede dadas sus limitaciones de
tiempo. Juegos como estos se estudian a menudo en el campo de la Inteligencia
Artificial [3]. Inteligencia Artificial incluye el estudio de los algoritmos de búsqueda
que puede ser utilizado incluso cuando el espacio de búsqueda es demasiado grande
para usar una búsqueda exhaustiva. En este texto, Cap.12 abarca unos algoritmos de
búsqueda heurística, junto con una heurística aplicada al algoritmo minimax para el
juego de Conecta Cuatro.

4.10 Las listas enlazadas

Las secuencias se pueden organizar de varias maneras diferentes. La secuencia PyList


era una lista accesible al azar. Esto significa que podemos acceder a cualquier
elemento de la lista en O (1) tiempo para almacenar o recuperar un valor. Al añadir un
elemento era posible en O (1) tiempo utilizando análisis de complejidad amortizado,
pero se inserta un elemento tomó O (n) tiempo en el que n es el número de artículos
después de la ubicación en la que estaba siendo inserta el nuevo elemento.
Si un programador quiere insertar un gran número de artículos hacia el
principio de una lista, una organización diferente para una secuencia podría ser
mejor se adapte a sus necesidades. Una lista enlazada es una organización de una
lista en la que cada elemento de la lista se encuentra en un nodo separado. Las
listas enlazadas se ven como los eslabones de una cadena. Cada enlace está unido
al siguiente eslabón por una referencia que apunta al siguiente eslabón de la
cadena. Cuando se trabaja con una lista enlazada, cada eslabón de la cadena se
denomina un nodo. Cada nodo se compone de dos piezas de información, un
elemento, que es los datos asociados con el nodo, y un enlace para el siguiente
nodo en la lista vinculada, a menudo llamada siguiente. El código en la
Sección.4.10.1 define la clase de nodo que puede ser utilizado para crear los nodos
en una lista enlazada.
118 4 secuencias

4.10.1 La clase Node


1 nodo de clase:
2 def __init __ (self, artículo, junto = None):
3 self.item = item
4 self.next = próximo
5
6 def getItem (self):
7 regreso self.item
8
9 def getNext (self):
10 regreso self.next
11
12 def setItem (auto, artículo):
13 self.item = item
14
15 def SetNext (auto, siguiente):
16 self.next = próximo

En la clase de nodo hay dos piezas de información: el artículo es una referencia


a un valor en la lista, y la siguiente referencia que apunta al siguiente nodo en la
secuencia. Figura4.10es una lista enlazada con tres elementos añadidos a la
misma. Hay cuatro nodos en esta figura. El primer nodo es un nodo ficticio. Tener
un nodo ficticio adicional al principio de la secuencia elimina una gran cantidad de
casos especiales que deben tenerse en cuenta cuando se trabaja con la lista
enlazada. Una secuencia de vacío todavía tiene un nodo ficticio. Los nodos en
listas enlazadas se representan como un rectángulo redondeado con dos mitades.
La mitad izquierda de un nodo es una referencia al elemento o el valor para ese
nodo en la secuencia. La mitad derecha es una referencia al siguiente nodo en la
secuencia o al valor especial Ninguno si es el último nodo en la secuencia.
Figura 4.10representa una lista enlazada que consta de tres piezas de
información. Mantenemos una referencia al primer nodo en la secuencia para que
podamos atravesar los nodos cuando sea necesario. La referencia al último nodo
en la lista hace que sea posible para anexar un elemento a la lista en O (1) tiempo.
También realizar un seguimiento de la cantidad de artículos por lo que no tenemos
que contar cuando alguien quiere recuperar el tamaño de la lista.
La mesa de operaciones en la fig. 4.11contiene la complejidad computacional
de diversas operaciones de lista en el tipo de datos ListaEnlazada presentado en
esta sección. Muchos
Fig. 4.10 Un objeto LinkedList Muestra
Listas 4.10Linked 119

Operación Complejidad Uso Método


creación de la
lista O (len (y)) x = LinkedList (y) llamadas __init __ (y)
la indexación En) a = x [i] x .__ GetItem __ (i)
conjunto
indexado En) x [i] = a x .__ SetItem __ (i, a)
concatenar En) z=x+y z = x .__ añadir __ (y)
adjuntar O (1) x.append (a) x.append (a)
insertar En) X.Insert (i, e) X.Insert (i, e))
borrar En) del X [i] x .__ delItem __ (i)
igualdad En) x == y x .__ eq __ (y)
iterar En) para una en x: x .__ __ iter ()
longitud O (1) len (x) x .__ len __ ()
afiliación En) una en x x .__ contiene __ (a)
ordenar N/A N/A N/A

Fig. 4.11 Complejidad de las operaciones ListaEnlazada

de las operaciones parecen tener la misma complejidad que las operaciones de la lista
de tipos de datos presentados en la Fig. 4.11. Hay algunas diferencias importantes sin
embargo. Las siguientes secciones proporcionan las implementaciones para algunas de
estas operaciones y señalar las diferencias en comparación con las operaciones lista de
tipos de datos dados en la Fig.4.11.

4.10.2 El Constructor ListaEnlazada


1 class LinkedList:
2
3 # Esta clase se utiliza internamente por la clase LinkedList. Es
4 # Invisible desde fuera de esta clase debido a los dos guiones
5 # Que preceden al nombre de la clase. Python destroza nombres para que se
6 # No son reconocibles fuera de la clase cuando dos guiones
7 # Preceder a un nombre, pero no son seguidos por dos subrayados en el
8 final # del nombre (es decir, un nombre de operador).
9 clase __Node:
10 def __init __ (self, artículo, junto = None):
11 self.item = item
12 self.next = próximo
13
14 def getItem (self):
15 regreso self.item
dieciséis
17 def getNext (self):
18 regreso self.next
19
20 def setItem (auto, artículo):
21 self.item = item
22
23 def SetNext (auto, siguiente):
24 self.next = próximo
25
26 def __init __ (self, contenidos = []):
27 # Aquí mantenemos una referencia al primer nodo de la lista enlazada
120 4 secuencias

28 # Y el último elemento de la lista enlazada. Ambos apuntan a una


29 # nodo ficticio para empezar. Este nodo ficticio siempre estará en
30 # La primera posición en la lista y nunca contendrá un elemento.
31 # Su objetivo es eliminar los casos especiales en el código de abajo.
32 self.first = LinkedList .__ Node (Nada, Nada)
33 self.ultimo = self.first
34 self.numItems = 0
35
36 para e en los contenidos:
37 self.append (e)

Creación de un objeto LinkedList tiene exactamente la misma complejidad ha


construir un objeto de lista. Si se crea una lista vacía, entonces el tiempo tomado
es O (1) y si una lista se copia, a continuación, se trata de un O (n) la operación.
Un objeto LinkedList tiene referencias a ambos extremos de la lista enlazada. La
referencia a la cabeza de la lista apunta a un nodo ficticio. Tener un nodo ficticio
al principio elimina muchos casos especiales que existen cuando la lista estaba
vacía si se utilizara ningún nodo ficticio.
La declaración de la clase de nodo dentro de la clase LinkedList, y que precede
al nombre con dos subrayados, se esconde la clase __Node de cualquier código
fuera de la clase LinkedList. La idea aquí es que sólo la clase LinkedList necesita
saber acerca de la clase __Node. Inicialmente, tanto la primera y la última
referencias apuntan al nodo ficticio. El método de agregación se usa para añadir
elementos a la Lista enlazada podrá traspasarse una lista para el constructor.

4.10.3 ListaEnlazada obtener y establecer


1 def __getitem __ (self, índice):
2 Si index> = 0 e índice <self.numItems:
3 cursor = self.first.getNext ()
4 para i en el rango (índice):
5 cursor = cursor.getNext ()
6
7 regreso cursor.getItem ()
8
9 aumento IndexError ( "índice LinkedList fuera de alcance")
10
11 def __setitem __ (self, índice, val):
12 Si index> = 0 e índice <self.numItems:
13 cursor = self.first.getNext ()
14 para i en el rango (índice):
15 cursor = cursor.getNext ()
dieciséis
17 cursor.setItem (val)
18 regreso
19
20 aumento IndexError ( "LinkedList índice de asignación fuera de rango")

Implementaciones para obtener y establecer las operaciones reajustables se


incluyen en la Sección. 4.10.4en gran parte como un ejemplo de atravesar una lista
enlazada. Ellos son de poco valor práctico. Si se desea un acceso aleatorio a una
lista, a continuación, la lista de la clase debe ser utilizado. Las listas enlazadas no
son accesibles al azar. Requieren búsqueda lineal a través del tipo de datos para
acceder a un lugar determinado en la lista. Cada una de estas operaciones es O (n),
donde n es el valor del índice.
Listas 4.10Linked 121

4.10.4 ListaEnlazada Concatenate


1 def __add __ (self, otro):
2 Si ! Tipo (auto) = Tipo de (otros):
3 aumento TypeError ( "concatenación definida para" + \
4 str (tipo (auto)) + "+" + str (tipo (otro)))
5
6 resultado = LinkedList ()
7
8 cursor = self.first.getNext ()
9
10 mientras cursor = Ninguno:
11 result.append (cursor.getItem ())
12 cursor = cursor.getNext ()
13
14 cursor = other.first.getNext ()
15
16 mientras cursor = Ninguno:
17 result.append (cursor.getItem ())
18 cursor = cursor.getNext ()
19
20 regreso resultado

La concatenación es un método de acceso que devuelve una nueva lista


compuesta de las dos listas originales. La operación es una vez más O (n) para
listas enlazadas como lo fue para el tipo de datos PyList presentado en la Fig.4.1.
En este código de concatenación un llamado cursor variable se utiliza para el paso
a través de los nodos de las dos listas. Este es el método común de pasar a través
de una lista enlazada. Colocar el cursor en el primer elemento de la lista enlazada.
A continuación, utilice un bucle while que termina cuando el cursor alcanza el
final (es decir, el valor especial Ninguno). Cada vez a través del bucle, mientras
que el cursor se hace avanzar mediante el establecimiento de al siguiente nodo en
la secuencia.
Observe en el código que el nodo ficticio de ambas listas (es decir, auto y otros)
se salta al concatenar las dos listas. El nodo ficticio en la nueva lista fue creada
cuando el constructor se llama.

4.10.5 ListaEnlazada Anexar


1 def append (auto, artículo):
2 nodo = LinkedList .__ nodo (elemento)
3 self.last.setNext (nodo)
4 self.ultimo = nodo
5 self.numItems + = 1

El código en la Sección. 4.10.6es la primera vez que vemos una pequeña


ventaja de una LinkedList más de una lista. La operación de anexión tiene una
complejidad de O (1) para LinkedLists donde con las listas de la complejidad era
un O amortizado (1) la complejidad. Cada append siempre tendrá la misma
cantidad de tiempo con un LinkedList. Un LinkedList es también no más grande
que tiene que ser. Sin embargo, LinkedLists ocupan alrededor de dos veces el
espacio de una lista de acceso al azar, ya que tiene que haber espacio para tanto la
referencia al elemento y la referencia a la siguiente nodo en la lista.
122 4 secuencias

El código para el método de agregación es bastante simple. Desde los puntos de


referencia self.ultimo en el nodo inmediatamente anteriores al lugar donde
queremos poner el nuevo nodo, que acabamos de crear un nuevo nodo y hacer el
último punto en la misma. Después hacemos el nuevo nodo del nuevo nodo
self.ultimo y el incremento del número de artículos por 1.

4.10.6 ListaEnlazada Insertar


1 def inserte (auto, índice, elemento):
2 cursor = self.first
3
4 Si índice <self.numItems:
5 para i en el rango (índice):
6 cursor = cursor.getNext ()
7
8 nodo = LinkedList .__ nodo (elemento, cursor.getNext ())
9 cursor.setNext (nodo)
10 self.numItems + = 1
11 más:
12 self.append (punto)

La operación de inserción, mientras que tiene la misma complejidad que inserto


en una lista, es un poco diferente para las listas enlazadas. Inserción en una lista es
una operación O (n), donde n es el número de elementos que están en la lista
después de que el punto de inserción, ya que deben todos ser movidos hacia abajo
para hacer espacio para el nuevo elemento. Cuando se trabaja con un LinkedList el
n es el número de elementos que aparecen antes del punto de inserción porque hay
que buscar el punto de inserción correcta.
Esto significa que mientras que la inserción en el principio de una lista es un O
(n) la operación, la inserción en el comienzo de una LinkedList es un (1)
operación O. Si va a tener que hacer muchas inserciones de cerca el comienzo de
una lista, a continuación, una lista enlazada puede ser mejor utilizar una lista de
acceso al azar.

4.10.7 Otras operaciones de lista enlazada

Las otras operaciones de lista enlazada se dejan como ejercicio para el lector. En
muchos casos, la clave para trabajar con una lista enlazada es para obtener una
referencia al nodo que preceds la ubicación que desea trabajar. Por ejemplo, para
eliminar un nodo de una lista vinculada solamente desea hacer el siguiente campo
del punto precedente nodo al nodo siguiente nodo que desea eliminar. Considere la
lista de muestras vinculado en la Fig.4.10. Para eliminar el segundo material (es
decir, la “b”) de esta lista queremos eliminar el nodo que contiene la referencia a
la “b”. Para ello podemos utilizar un cursor para encontrar el nodo anterior al que
hace referencia a la “b”. Una vez encontrado, el siguiente campo del cursor puede
hacerse para que apunte al nodo después de la “b” como se muestra en la Fig.4.12.
Cambiar el siguiente puntero de nodo del cursor para apuntar al nodo después
de los resultados “B” en el nodo que contiene el “b” abandono de la lista. Dado
que no existen otras referencias al nodo que contiene la “b”, y de hecho a la “b” en
sí, los dos objetos son basura recogida.
Listas 4.10Linked 123

Fig. 4.12 Cómo borrar un nodo de una lista enlazada

Por último, la operación de ordenación no es aplicable en las listas enlazadas.


Eficientes de clasificación Algo-ritmos requieren acceso aleatorio a una lista.
2
Inserción tipo, un O (n ) Algoritmo, podría funcionar, pero sería muy ineficiente.
Si se necesitara clasificación, que sería mucho más eficiente para copiar la lista
enlazada a una lista accesible al azar, más o menos, y luego construir una nueva
lista enlazada ordenada de la lista ordenada.

4.11 Pilas y colas

Hay otras dos estructuras de datos secuenciales que son muy comunes en la
programación de computadoras. Una pila es una estructura de datos, donde el acceso
es solamente en un extremo de la secuencia. Los nuevos valores se insertan en la pila
para añadirlos a la secuencia y extraen de la pila para eliminarlos de la secuencia. La
pila de tiempo de ejecución, que se describe en el Cap.3, Era un tal caso de una pila.
Las pilas se utilizan en muchos algoritmos en la informática. Las pilas se puede utilizar
para evaluar expresiones numéricas. Son útiles al analizar la información. Pueden ser
utilizados para que coincida con paréntesis en los programas y expresiones. Las
operaciones en una pila se dan en la Fig.4.13.
Las pilas se llaman estructuras de datos LIFO último en entrar / primero en salir o.
El último elemento empujado es el primer elemento reventado. Una clase de pila puede
ser implementado en al menos un par diferente

Operación Complejidad Uso Descripción


Creación pila O (1) s = Stack () llama al constructor
devuelve el último elemento empujó y lo elimina
popular O (1) a = s.pop () de s
empujar O (1) s.push (a) empuja el tema, un, en la pila, es
parte superior O (1) a = s.top () devuelve el elemento superior sin estallar s
esta vacio O (1) s.isEmpty () devuelve True si s no tiene elementos empujados
Fig. 4.13 Complejidad de operaciones de la pila
124 4 secuencias

maneras de alcanzar la complejidad de cálculo indicados en esta tabla. Una lista o una
lista enlazada será suficiente. El código en la Sección.4.11.2es una implementación de
una pila con una lista. La aplicación es bastante sencilla. El programa principal en la
Sección.4.11.2 comprueba el tipo de datos pila con un par de pruebas para asegurarse
de que el código funciona correctamente.

4.11.1 El Código de clase Pila


1 clase Pila:
2 def __en si mismo):
3 self.items = []
4
5 def pop (auto):
6 Si self.isEmpty ():
aumento RuntimeError ( "intento de hacer estallar
7 una pila vacía")
8
9 topIdx = len (self.items) -1
10 item = self.items [topIdx]
11 del self.items [topIdx]
12 regreso ít
13
14 def empujar (auto, artículo):
15 self.items.append (punto)
dieciséis
17 def la parte superior (self):
18 Si self.isEmpty ():
aumento RuntimeError ( "El intento por obtener la parte
19 superior de la pila vacía")
20
21 topIdx = len (self.items) -1
22 regreso self.items [topIdx]
23
24 def estaVacia (self):
25 regreso len (self.items) == 0
26
27 def principal():
28 s = Stack ()
29 lst = lista (rango de (10))
30 LST2 = []
31
32 para k en lst:
33 s.push (k)
34
35 Si s.top () == 9:
36 impresión( "Test 1 Aprobado")
37 más:
38 impresión( "Test 1 Failed")
39
40 mientras no s.isEmpty ():
41 lst2.append (s.pop ())
42
43 lst2.reverse ()
44
45 Si LST2 = lst:
46 impresión( "Test 2 Failed")
47 más:
48 impresión( "Test 2 Aprobado")
49
4.11Stacks y colas 125

50 tratar:
51 s.pop ()
52 impresión( "Test 3 Failed")
53
54 excepto Error de tiempo de ejecución:
55 impresión( "Test 3 Aprobado")
56 excepto:
57 impresión( "Test 3 Failed")
58
59 tratar:
60 detener()
61 impresión( "Test 4 Failed")
62
63 excepto Error de tiempo de ejecución:
64 impresión( "Test 4 Aprobado")
65 excepto:
66 impresión( "Test 4 Failed")
67
68 Si __name __ == "__ main__":
69 principal()

Este código, si se guarda en un archivo llamado stack.py se pueden importar en


otros módulos. Cuando este módulo se ejecuta por sí mismo, la función principal
de la prueba se ejecutará. Cuando este módulo se importa en otro programa, la
función principal no se ejecuta debido a que la variable __name__ no será igual a
“__main__”.
Una cola es como una pila de muchas maneras, excepto que en lugar de ser una
estructura de datos LIFO, las colas son estructuras de datos FIFO o First In / First
Out. El primer elemento empujado, es el primer elemento reventado. Cuando
estamos trabajando con una cola hablamos de encolamos un elemento, en vez de
empujarlo. Al quitar un elemento de la cola hablamos de desencolado el elemento
en lugar de aparecer como lo hicimos desde una pila. La tabla de la figura.4.14
proporciona detalles de las operaciones de la cola y sus complejidades.
La implementación de una cola con las complejidades de esta tabla es un poco
más complicado que la implementación de la pila. Para implementar una cola con
estas complejidades que necesitamos para ser capaz de añadir a un extremo de una
secuencia y eliminar desde el otro extremo de la secuencia en O (1) tiempo. Esto
sugiere el uso de una lista enlazada. Ciertamente, una lista enlazada trabajaría para
conseguir las complejidades deseados. Sin embargo, todavía puede utilizar una
lista, si estamos dispuestos a aceptar una complejidad amortizado de O (1) para la
operación de retirada de cola. Este código de clase Queue implementa una cola
con una lista y alcanza una complejidad amortizado de O (1) para la operación de
quitar de la cola.

Operación Complejidad Uso Descripción


Creación de
colas O (1) q = Queue () llama al constructor
a = q.dequeue
dequeue O (1) () devuelve el primer elemento en cola y lo elimina de q
enqueue O (1) q.enqueue (a) encola el tema, un, en la cola, q
devuelve el elemento frontal sin desencolado el
frente O (1) a = q.front () elemento
esta vacio O (1) q.isEmpty () devuelve True si q no ha enqueued artículos

Fig. 4.14 Complejidad de las operaciones de cola


126 4 secuencias

1 cola de clase:
2 def __en si mismo):
3 self.items = []
4 self.frontIdx = 0
5
6 def __compress (self):
7 newlst = []
8 para i en el rango (self.frontIdx, Len (self.items)):
9 newlst.append (self.items [i])
10
11 self.items = newlst
12 self.frontIdx = 0
13
14 def dequeue (self):
15 Si self.isEmpty ():
diec
aumento RuntimeError ( "intento de quitar de la cola
iséis una cola vacía")
17
18 # Cuando la cola está medio lleno, comprimirlo. Esta
19 # Logra una complejidad amortizado de O (1), mientras
20 # No dejar que la lista siga creciendo sin control.
21 Si self.frontIdx * 2> len (self.items):
self .__ compresa
22 ()
23
24 item = self.items [self.frontIdx]
25 self.frontIdx + = 1
26 regreso ít
27
28 def poner en cola (sí, artículo):
29 self.items.append (punto)
30
31 def delantera (self):
32 Si self.isEmpty ():
aumento RuntimeError ( "Intento de acceso frontal de cola
33 vacía")
34
35 regreso self.items [self.frontIdx]
36
37 def estaVacia (self):
38 regreso self.frontIdx == len (self.items)
39
40 def principal():
41 q = Queue ()
42 lst = lista (rango de (10))
43 LST2 = []
44
45 para k en lst:
46 q.enqueue (k)
47
48 Si q.front () == 0:
49 impresión( "Test 1 Aprobado")
50 más:
51 impresión( "Test 1 Failed")
52
53 mientras no q.isEmpty ():
54 lst2.append (q.dequeue ())
55
56 Si LST2 = lst:
57 impresión( "Test 2 Failed")
58 más:
59 impresión( "Test 2 Aprobado")
4.11Stacks y colas 127

60
61 para k en lst:
62 q.enqueue (k)
63
64 LST2 = []
sesenta y cinco
66 mientras no q.isEmpty ():
67 lst2.append (q.dequeue ())
68
69 Si LST2 = lst:
70 impresión( "Test 3 Failed")
71 más:
72 impresión( "Test 3 Aprobado")
73
74 tratar:
75 q.dequeue ()
76 impresión( "Test 4 Failed")
77
78 excepto Error de tiempo de ejecución:
79 impresión( "Test 4 Aprobado")
80 excepto:
81 impresión( "Test 4 Failed")
82
83 tratar:
84 q.front ()
85 impresión( "Test 5 Failed")
86
87 excepto Error de tiempo de ejecución:
88 impresión( "Test 5 Aprobado")
89 excepto:
90 impresión( "Test 5 Failed")
91
92 Si __name __ == "__ main__":
93 principal()

4.11.2 Infijo evaluación de expresiones

Una expresión es una expresión infija donde los operadores aparecen en entre sus
operandos. Por ejemplo, (6+ 5) * 4 es una expresión infija porque +aparece entre el 6 y
5 * y aparece entre sus operandos. Python, al ser un lenguaje de programación, se
puede evaluar expresiones infijos. Sin embargo, digamos que nos gustaría escribir un
programa que permita evaluar las expresiones introducidas por el usuario. Podemos
hacer esto en un programa en Python? Resulta que podamos, y muy fácilmente. Python
incluye una función que va a tratar una cadena como una expresión que se evalúa y se
devolverá el resultado de dicha evaluación. La función se llama eval. Aquí es un
programa que utiliza la función eval.
1 def principal():
2 expr = input ( "Por favor, introduzca una expresión infija:")
3 resultado = eval (expr)
4 impresión( "El resultado de", expr, "es", resultado)
5
6 Si __name__ == "__main__":
7 principal()
128 4 secuencias

Esta es sin duda una muy interesante, aunque corto, programa. La función eval
hace un montón de trabajo para nosotros. ¿Pero como funciona? Resulta que
podemos escribir nuestra propia función eval usando un par de pilas. En esta
sección describimos un evaluador de expresiones infija. Para hacer nuestro trabajo
un poco más fácil, vamos a insistir en que el usuario introduzca espacios entre
todos los operadores (incluyendo parens) y operandos. Así, por ejemplo, el usuario
puede interactuar como sigue.
Por favor, introduzca una expresión infija: (6 +
5) * 4 - 9 El resultado de (6 + 5) * 4 - 9 = 35,0

El algoritmo infija evaluador utiliza dos pilas, una pila de operador y una pila
de operandos. El registro del operador llevará a cabo los operadores y parens
izquierda. La pila de operandos contiene números.
El algoritmo procede mediante el escaneo de las fichas de la expresión infija de
izquierda a derecha. Usted puede hacer esto con bastante facilidad en Python
mediante la división de la cadena de entrada y luego iterar sobre la lista de cadenas
desde la entrada. Las fichas son operadores (incluyendo parens) y números.
Cada operador tiene una prioridad asociada a ella. operadores de multiplicación
y división tienen la prioridad más alta, mientras que la suma y la resta son
siguiente. Finalmente los paréntesis de la izquierda y la derecha paren prioridad
son los más bajos. Una función llamada de precedencia puede escribirse de
manera que un operador dado, la función devuelve el valor de precedencia
prioridad adecuada. Usted puede decidir sobre sus valores de precedencia dadas
las restricciones prescritas.
Para comenzar el stack y el registro del operador se inicializan con pilas vacías
y un paréntesis de la izquierda se empuja en el registro del operador.
Para cada ficha en la entrada que hacemos lo siguiente:

1. Si el token es un operador entonces necesitamos para funcionar en las dos


series con el operador dado. De funcionamiento se describe en la
Sección.4.11.2.1.
2. Si el token es un número entonces empujamos en la pila número.

Después de escanear toda la entrada y operativo cuando sea necesario operamos en


las pilas una vez más con un derecho adicional paren operador. Después de utilizar el
tiempo final del registro del operador debe estar vacío y el stack debe tener un número
en él, que es el resultado. Usted hace estallar el stack e imprimir el número como su
resultado.

4.11.2.1 El Operar Procedimiento

El procedimiento de operar debe ser una función independiente. El procedimiento


de operar se da un operador, la pila de operador, y pila de operandos como
argumentos. Para operar hacemos lo siguiente:
• Si el operador dado es un paréntesis de izquierda empujamos en la pila del
operador y de retorno.
• De lo contrario, mientras que la precedencia del operador dado es menor que o
igual a la precedencia del operador de la parte superior en la pila operador
proceder como sigue:
4.11Stacks y colas 129

1. Pop el operador de la parte superior de la pila del operador. Llamar a esto el


topOp.
2. si es un topOp +, -, *, o / y luego operar en la pila número haciendo estallar
los operandos, haciendo la operación, y empujando el resultado.
3. Si topOp es un paréntesis de la izquierda y luego el operador dado debe ser
un paréntesis de la derecha. Si es así, hemos terminado operativo y volvemos
inmediatamente.

Cuando la precedencia del operador dado es mayor que la precedencia de


topOp que terminar el bucle y empuje el operador dado en el registro del operador
antes de volver a operar.

4.11.2.2 Ejemplo

Para ver cómo funciona este algoritmo que le ayudará a ver un ejemplo. En las
figuras, el símbolo más a la derecha subrayada es la señal que está siendo
procesado y todas las fichas a la izquierda ya han ser procesada. La pila de
operandos y la pila de operador están etiquetados en los diagramas. El operador
topOp y el operador actual se dan también.
En la Fig. 4.15 las fichas (6 +5 han sido procesados y empujado sobre sus respectivas
pilas. Los paren correctas ahora se está procesando y operar fue llamado para procesar este
operador. En el procedimiento de operar el topOp es un “+”Y“+”Tiene precedencia mayor
que‘)’por lo que los paren derecho no puede ser empujado en la parte superior del operador
de suma. Por lo tanto, nos pop el registro del operador y operar. Desde que nos metimos un
“+”Entonces estallar los dos números de la pila de operandos, sumarlos, y empuje el
resultado como se muestra en la Fig. 4.16.
Después de pulsar el 11 en la pila, se encontró que el operador de la parte superior
era un paréntesis de la izquierda. En ese caso, nos fuimos los paren fue y regresó de
inmediato dejando sólo los paren individuales izquierda en el registro del operador. A
continuación, el evaluador procedió al encontrar el operador “*” y empujándolo y
luego encontrar el 4 y empujándolo en la pila de operandos. A continuación, el “-
”Operador no puede ser empujado sobre el registro del operador, ya que su prioridad
es menor que‘*’. Operar se llama y el topOp dicta que nos pop dos números,
multiplicamos ellos, y empuje el resultado como se muestra en la Fig.4.17. Los "-
”Operador tiene mayor prioridad que la‘(’lo que es sólo inserta en la pila del operador.
El 9 se procesa y se inserta en la pila de operando como se muestra en la Fig. 4.17.
Para finalizar la evaluación de la expresión infija, funcionan se llama una vez
más con “)” como el operador. Esto obliga al 9 que se debe restar del 44 dejando
35 en la pila operando y el registro del operador vacía. El resultado final es 35 y el
evaluador aparece el resultado de la pila de operandos y devuelve como el
resultado y así es como funciona la función eval Python. Resulta que la función
eval Python es un poco más sofisticado que este ejemplo. Sin embargo, el efecto
de la evaluación de expresiones simples infijos es el mismo.

4.11.3 Radix sort


El algoritmo radix sort ordena una secuencia de cadenas lexicográfico, tal y como
aparecerían en una agenda o diccionario. Para implementar este algoritmo son las
cuerdas
130 4 secuencias

Fig. 4.15 Evaluación Infijo Paso 1

Fig. 4.16 Evaluación Infijo Paso 2

Fig. 4.17 Evaluación Infijo Paso 3


4.11Stacks y colas 131

leídos desde la fuente (por ejemplo, un archivo o internet) y se colocan en una cola
llamada mainQueue. A medida que las cuerdas se leen y se colocan en la cola del
algoritmo realiza un seguimiento de la longitud de la cadena más larga que serán
ordenadas. Vamos a llamar a esta longitud más larga.
Para el algoritmo radix tipo funcione correctamente, todas las cadenas en el
mainQueue tienen que tener la misma longitud, que no es probable, por supuesto.
Si una cadena es más corta que la cadena más larga que puede ser rellenado con
espacios en blanco. Para asistir, es útil tener una función que devuelve un carácter
de una cadena como la función charAt en la Sección. 4.11.6.

4.11.4 La función charat


1 def charAt (s, i):
2 Si len (s) - 1 <i:
3 regreso ""
4
5 regreso si]

La función charAt devuelve el carácter i de la cadena s y un espacio en blanco


si i es mayor o igual a la longitud del s. Con el uso de esta función las cadenas en
el mainQueue pueden ser de diferentes longitudes y la función charAt hará que se
vean como que tienen la misma longitud.
Además de la mainQueue, hay 256 colas creados y colocados en una lista de
colas, llamado queueList. Hay 256 posibles valores ASCII diferentes y se crea una
cola para cada letra ASCII. Dado que la mayoría de los caracteres ASCII están en
el rango de 0-127 el algoritmo probablemente no va a utilizar las colas en los
índices 128-255.
El algoritmo de ordenación funciona mediante la eliminación de una cadena de
mainQueue. Se ve en el último carácter, a partir de las de más larga 1, en cada
cadena y colocar la cadena en una cola que corresponde al valor ASCII del
carácter. Por lo tanto, todas las cadenas que terminan en 'a' van en la 'a' de cola y
así sucesivamente. Para encontrar el índice en queueList se utiliza la función ord.
Por ejemplo, escribiendo ord ( “a”) devolvería un 65 que es el índice a utilizar en
el queueList para el carácter “a”.
Luego, a partir de la primera cola en el queueList, todas las cadenas están quitada de
cada cola y se colocan en la cola principal. Vaciamos cada cola primero antes de proceder-
ción a la siguiente cola en el queueList. Esto se repite hasta que todas las colas de letras
están vacíos.
Entonces, volvemos a la eliminación de todos los elementos de la cola principal
de nuevo, esta vez buscando en la segunda hasta la última letra de cada palabra.
Cada cadena se coloca en una cola en el queueList dependiendo de su segunda a la
última carta. El proceso se repite hasta llegar a la primera letra de cada cadena.
Cuando hayamos terminado, todas las cadenas están en la cola principal en el
orden establecido. Para completar el algoritmo de todas las cadenas se quitan de la
mainQueue una vez más en el orden establecido.

4.11.4.1 Radix Ordenar Ejemplo


Para ver cómo funciona Radix sort vamos a considerar un ejemplo donde el bate
de palabras, granja, granero, coche, sombrero, y el gato están ordenados
alfabéticamente. En las figuras de esta sección cada
132 4 secuencias

cola se extrae verticalmente con la parte delantera de la cola de estar en el fondo


de la caja y la parte trasera de la cola de estar en la cima de cada caja. Mientras
que hay 256 colas más un mainQueue creado por especie radix, el ejemplo se
muestran sólo las colas que se utilizan al ordenar estas palabras. La primera que
aparece en la lista es la cola para espacios en una cadena. Figura4.18 representa
las cuerdas de la mainQueue después de que hayan sido leídos de su fuente.
El primer paso del algoritmo procesa la mainQueue vaciando y la colocación de
cada cadena en la cola que corresponde a su cuarta carta (la longitud máxima de la
cadena). Esto resulta en la granja y el granero siendo colocados en la m y n colas.
Las otras cadenas se colocan en la cola de espacio como se muestra en la Fig.4.19.
Entonces, todas las cuerdas se desencolan de las tres colas no vacías y se colocan de
nuevo en el mainQueue en el orden en que fueron desencolan como se muestra en la
Fig. 4.20.
Una vez más, el proceso se repite para la tercera letra en cada cadena. Esto se
traduce en el uso de los R y T colas como se muestra en la Fig.4.21.
Una vez más, las cuerdas son llevados de vuelta a la mainQueue en el orden en
que fueron desencolan como se representa en la Fig. 4.22.
Y el proceso se repite de nuevo para la segunda letra en cada cadena. Todas las
cadenas tienen una A como su segundo personaje para que todos terminan en la
cola (Fig.4.23).

Fig. 4.18 Radix Ordenar Paso 1


Fig. 4.19 Radix Ordenar Paso carta 2-4th
4.11Stacks y colas 133

Fig. 4.20 Radix Ordenar Paso 3

Fig. 4.21 Radix Ordenar Paso carta 4-3rd

Fig. 4.22 Radix Ordenar Paso 5

Y todos ellos se remontan a la mainQueue como se muestra en la Fig. 4.24. Sin


cambios desde el paso 5 en este caso.
Por último, nos fijamos en la primera letra de cada cuerda y el tipo es casi
completa, como se muestra en la Fig. 4.25.
Con lo que todas las cuerdas de nuevo a los resultados mainQueue en el
mainQueue que contiene todas las cuerdas en el orden establecido. El mainQueue
se puede vaciar en este punto y las cuerdas puede ser procesada en orden
clasificado como se representa en la Fig.4.26.
134 4 secuencias

Fig. 4.23 Radix Ordenar Paso 6-2nd carta

Fig. 4.24 Radix Ordenar Paso 7

Fig. 4.25 Radix Ordenar Paso carta 8-1st

Radix sort es bastante simple. Se llama especie radix debido a una raíz es como
un punto decimal se mueve hacia atrás a través de la cadena. Movemos el punto
decimal un carácter a la vez hasta que lleguemos al primer carácter de cada
cadena.
Resumen 4.12Chapter 135

Fig. 4.26 Radix Ordenar Paso 9

4.12 Resumen del capítulo

En este capítulo se exploró el uso de secuencias lineales en la programación de


computadoras. Estas secuencias vienen en muchas formas incluyendo listas
accesibles aleatoriamente, matrices, listas enlazadas, pilas y colas. También vimos
que una matriz de dos dimensiones es sólo una lista de listas. El capítulo también
exploró las operaciones en relación con estos tipos de datos y la complejidad de
estas operaciones.
Algoritmos eran también una parte importante del capítulo cuatro. La
ordenación por selección, ordenamiento por mezcla, y los algoritmos de quicksort
se estudiaron junto con sus computacionales complejos-dades. Minimax se
presenta como un interesante caso de estudio sobre el uso de matrices
bidimensionales y recursividad en un programa. Los algoritmos evaluador infija y
Radix sort también fueron presentados como ejemplos del uso de pilas y colas.
Después de leer este capítulo usted debe tener una comprensión de los tipos
abstractos de datos como listas, pilas y colas. Debe comprender cómo se implementa
un tipo de datos abstracto, cómo la implementación puede afectar a la complejidad de
sus operaciones, y al menos un par de algoritmos que utilizan estos tipos de datos en
sus implementaciones.

4.13 Preguntas de revisión

Responder a estas respuesta corta, selección múltiple y preguntas de verdadero /


falso para probar su dominio del capítulo.

1. ¿Cuál es el mejor de los casos, peor de los casos, y la complejidad caso


promedio de ordenación por selección, ordenamiento por mezcla, y los
algoritmos de quicksort?
2. ¿Cómo puede la operación de anexión lograr O (1) la complejidad cuando a
veces se queda sin espacio para añadir otro elemento?
3. ¿Cuál es la complejidad del operador de concatenación, el operador +, para las
listas?
4. ¿Cuál es la complejidad del operador de eliminación de las listas?
136 4 secuencias

5. Al ordenar los elementos de una lista, ¿qué método debe estar definido para
esos elementos? ¿Por qué?
6. ¿Por qué la clasificación rápida funcionan mejor que fusionar tipo?
7. ¿En qué condiciones sería posible para el tipo de combinación obtenga
mejores resultados que la clasificación rápida?
8. Resumir lo que sucede cuando una lista se divide.
9. Resumir lo que sucede cuando dos listas se combinan en el algoritmo de
ordenamiento por mezcla.
10. ¿Cuál es el propósito del parámetro de inicio a la función de selección del
algoritmo de ordenación por selección?
11. ¿Cuáles son las ventajas de una lista enlazada más de un acceso al azar lista
imple-mentación de un tipo de lista de datos?
12. ¿Cuáles son las ventajas de una lista accesible al azar más de una lista
enlazada imple-mentación de un tipo de lista de datos?
13. ¿Cómo se diferencia una pila de una cola en la manera de acceder a él?
14. ¿Cuál es la complejidad del algoritmo radix tipo?

4.14 Los problemas de programación

1. Escribir un programa que los tiempos del quicksort, la fusión de ordenación, y


los algoritmos de ordenación integradas en Python para descubrir cuál es
mejor y para ver sus velocidades relativas. Para ello se debe implementar dos
clases de secuencias, un QSequence y una secuencia M. El QSequence puede
heredar de la clase PyList y debe implementar su propio algoritmo de
ordenación utilizando el código de clasificación rápida presentada en este
capítulo. La secuencia M debe ser una clase similar usando el algoritmo de
ordenamiento por mezcla.
Puede ordenar lo que quiera. Si elige ordenar objetos de alguna clase que defina,
recuerde que debe implementar el método __lt__ para esa clase. Asegúrese de
cambiar aleatoriamente los elementos de la secuencia antes de la clasificación
utilizando la clasificación rápida. Generar un archivo XML en el formato de una
parcela a tres secuencias. Trazar el tiempo que se tarda tanto en forma aleatoria y
una secuencia de ordenación rápida. Se representa el tiempo que se necesita para
fusionar una secuencia especie. Por último, trazar el tiempo que toma para ordenar
la misma secuencia utilizando el método de ordenación integrada. La complejidad
de la combinación de tipo y quicksort es O (n log n) de manera por la complejidad
computacional los tres algoritmos son equivalentes. ¿Qué los datos experimentales
revelan acerca de los dos algoritmos? Poner un comentario en la parte superior de
su programa de dar su respuesta.
Para probar efectivamente estos tres algoritmos de ordenación que debe
ordenar los elementos hasta un tamaño de la lista de por lo menos 1.000
elementos. Usted puede medir el tiempo de las clases en incrementos de 100.
Dependiendo de su ordenador, puede que tenga que jugar con los números
exactos en cierto grado para obtener una buena gráfica buscando.
2. El algoritmo de ordenamiento por mezcla no se utiliza tan comúnmente como el
algoritmo de ordenación rápida, porque la clasificación rápida es un tipo in-situ,
mientras que la fusión especie requiere al menos espacio para una copia adicional
de la lista. De hecho, ordenamiento por mezcla puede ser implementado con
exactamente una copia extra de la lista. En este ejercicio usted es volver a aplicar
el tipo de combinación
4.14 Los problemas de programación 137

algoritmo para utilizar una copia adicional de la lista en lugar de asignar una
nueva lista cada vez que dos listas se combinan.
La lista extra se utiliza antes de llamar a la parte recursiva del algoritmo de
ordenamiento por mezcla. Luego, con cada nivel de recursividad alterna el
algoritmo de combinación de ordenar copias a la otra lista, mover de un tirón
entre las dos listas. Para lograr esto, la función mergeSortRecursively se debe
dar una nueva lista de listas de llamada en lugar de las listas de la lista ss. En
las listas [0] es la lista siguientes y en las listas [1] es la copia extra de la lista.
Un parámetro adicional se da a la función mergeSortRecursively. El índice de
la lista de fusionar a partir también se proporciona. Este índice le dará la
vuelta de ida y vuelta entre 0 y 1 como se produce cada llamada recursiva a
mergeSortRecursively. Este mover de un tirón de 0 a 1 a 0 y así
sucesivamente se puede lograr utilizando la aritmética modular por escrito:
listToMergeFromIndex = (listToMergeFromIndex + 1)% 2

El signo de porcentaje es el resto después de dividir por 2. Adición 1 y encontrar


el resto después de dividir por dos significa que listToMergeFromIndex voltea
entre 0 y 1. Por lo tanto, la llamada se realiza como mergeSortRecursively
(listToMergeFromIndex, listas, iniciar, detener). La función mergeSortRecursively
debe devolver el nuevo índice de la lista desde la que fusionar.
Una parte de esta nueva función mergeSortRecursively es un poco difícil.
Puede haber un nivel más de la recursividad en el lado izquierdo o derecho
para las dos llamadas recursivas a mergeSortRecursively. Si este es el caso,
entonces el resultado ya sea de la mitad izquierda o derecha deben ser
copiados en la misma lista que la otra mitad antes de que las dos mitades
pueden ser fusionado con éxito.
Cuando mergeSortRecursively vuelve a la función mergesort el resultado de la
especie-ción puede estar en la secuencia original o puede ser en la copia. Si es en
la copia, entonces el resultado se debe copiar de nuevo a la secuencia original
antes de regresar.
Completar esta versión de dos lista de ordenamiento por mezcla como se describe
en este ejercicio y probarlo a fondo. Entonces esta versión de fusión clasificar y
comparar esos tiempos con la versión de la combinación de tipo presentado en el
capítulo y con la implementación clasificación rápida presentada en este capítulo.
Construir un archivo XML en el formato leído por el programa PlotData.py y
trazar sus correspondientes diagramas de tiempo para ver cómo este algoritmo
lleva a cabo cuando se compara con los otros dos.
3. Completar el programa de tres en raya se describe en la sección sobre matrices
de 2 dimensiones. Utilice el código de la secta.20.5como punto de partida. A
continuación, complete las secciones que dicen que se dejan como ejercicio
para el estudiante.
4. Completar el tipo de datos Lista enlazada mediante la implementación de las
operaciones de eliminación, la igualdad, ITERATE de longitud, y de miembros.
Asegúrese de que tengan la complejidad dada en la tabla complejidades
ListaEnlazada. A continuación, poner en práctica un programa de prueba en su
función principal para probar thorougly las operaciones que ha implementado.
Llame a la linkedlist.py módulo de manera que se puede importar esto en otros
programas que puedan necesitar.
5. Aplicar un tipo de datos de la cola usando una aplicación lista enlazada. Crear
un conjunto de casos de prueba para probar meticulosamente, su tipo de datos.
Coloque el tipo de datos en un archivo llamado queue.py y crear una función
principal que corre a los casos de prueba.
138 4 secuencias

6. Aplicar un tipo de datos de cola de prioridad utilizando una aplicación lista


enlazada. En una cola de prioridad, los elementos de la cola de cada uno
tienen una prioridad en el que el menor sea el número mayor será la prioridad.
Las prioridades son por lo general sólo números. La cola de prioridad tiene la
costumbre de puesta en cola, quitar de la cola, y los métodos vacíos. Cuando
se pone en cola un valor que se compara con la prioridad de otros artículos y
se coloca delante de todos los artículos que tienen prioridad más baja (es
decir, un número de prioridad más alta).
7. Aplicar un tipo de datos de la pila utilizando una aplicación lista enlazada.
Crear un conjunto de casos de prueba para probar meticulosamente, su tipo de
datos. Coloque el tipo de datos en un archivo llamado stack.py y crear una
función principal que corre a los casos de prueba.
8. Implementar el programa infija evaluador se describe en el capítulo. Usted
debe aceptar la entrada y producir una salida como se describe en esa sección
del texto. Los tokens de entrada deben todos estarán separados por espacios en
blanco para que la recuperación de las fichas fáciles. No se olvide de convertir
sus fichas de números a partir de cadenas de flotadores al escribir el programa.
9. Implementar el algoritmo radix tipo descrito en el capítulo. Utilizar el
algoritmo para ordenar una lista de palabras que encuentre en Internet o en
otros lugares. Escribir un programa principal que pone a prueba su algoritmo
radix tipo.
10. Búsqueda de una secuencia de elementos para un artículo en particular toma
tiempo O (n), en promedio, donde n es el número de elementos en la lista. Sin
embargo, si la lista está ordenada en primer lugar, a continuación, la búsqueda
de un elemento dentro de la lista se puede hacer en O (log n) tiempo mediante
el uso de un enfoque de divide y vencerás. Este tipo de búsqueda se llama
búsqueda binaria. El algoritmo de búsqueda binaria se observa, para el
elemento en el medio de la secuencia. Si no se encuentra ahí porque la lista se
ordena el algoritmo de búsqueda binaria sabe si mirar en el lado izquierdo o
derecho de la secuencia. La búsqueda binaria informa Verdadero o Falso
dependiendo de si se encuentra el elemento. A menudo se escribe de forma
recursiva está dando una secuencia y el comienzo y el final valores de índice
en el que buscar el elemento. Por ejemplo, para buscar una secuencia entera
llamada ss, búsqueda binaria puede ser llamado como busquedaBinaria (SEC,
0, len (SEC) -1). Escribir un programa que construye una PyList o
simplemente una lista de los valores de Python, los ordena, y luego mira hacia
arriba valores aleatorios dentro de la lista. Calcular los tiempos de búsqueda
de listas de varios tamaños y grabar los resultados en el formato PlotData.py
para que pueda visualizar sus resultados. Debería ver una curva de O (log n) si
se ha implementado correctamente búsqueda binaria.

Anda mungkin juga menyukai