# Taller 2: Agoritmos de ordenamiento y estructuras de datos.

Juan David Uchuvo Gonz´lez, C´d: 257895 a o Jos´ David Bermeo L´pez, C´d: 257911 e o o May 4, 2011
1. Desarrolle los siguientes ejercicios del [Cormen09] (a) Exerc. 6.5-9 pag 166. Give an O(nlgk)-time algorithm to merge k sorted lists into one sorted list, where n is the total number of elements in all inputs list. # ! / usr / bin / python def left ( i ): return 2*( i +1) def right ( i ): return 2*( i +1)+1 def parent ( i ): return ( i +1)/2 def swap (A ,i , j ): A[i], A[j] = A[j], A[i] class heap : def __init__ ( self ,A , max ): self . heapsize = len ( A ) -1 self . A = A if max : self . build_max_heap () else : self . build_min_heap () def min_heapify ( self , i ): l = left ( i ) -1 r = right ( i ) -1 if l <= self . heapsize and self . A [ l ] < self . A [ i ]: smaller = l else : smaller = i if r <= self . heapsize and self . A [ r ] < self . A [ smaller ]: smaller = r if smaller != i : swap ( self .A ,i , smaller ) self . min_heapify ( smaller ) def max_heapify ( self , i ): l = left ( i ) -1 r = right ( i ) -1 if l <= self . heapsize and self . A [ l ] > self . A [ i ]: largest = l else : largest = i 1

def

def

def def

def

if r <= self . heapsize and self . A [ r ] > self . A [ largest ]: largest = r if largest != i : swap ( self .A ,i , largest ) self . max_heapify ( largest ) build_min_heap ( self ): self . heapsize = len ( self . A ) -1 for i in range (( len ( A ) -1)/2 , -1 , -1): self . min_heapify ( i ) build_max_heap ( self ): self . heapsize = len ( self . A ) -1 for i in range (( len ( A ) -1)/2 , -1 , -1): self . max_heapify ( i ) heap_min ( self ): # Only for minheaps return self . A [0] heap_extract_min ( self ): # Only for minheaps if self . heapsize < 1: return " Overflow " min = self . A [0] self . A [0] = self . A [ self . heapsize ] self . heapsize = self . heapsize -1 self . min_heapify (0) return min h e a p _ e x t r a c t _ m i n _ o f _ m i n ( self ): if self . heapsize < 1: return " Overflow " number = self . A [0]. pop (0) if len ( self . A [0]) < 1: self . A . pop (0) self . heapsize = self . heapsize -1 self . min_heapify (0) return number

def merge_sort (A , n ): # A is list of lists such that A [ k ] is a sorted list . result = [] C = heap (A , False ) # True is a maxheap , and False a minheap for i in range ( n ): result . append ( C . h e a p _ e x t r a c t _ m i n _ o f _ m i n ()) return result if __name__ == " __main__ " : A = [[1 ,3 ,5 ,7] ,[2 ,4 ,6 ,9] ,[8 ,10 ,11]] print A print merge_sort (A ,11) Usamos un minheap de listas ordenadas [A1 , A2 , · · · , Ak ] que mantiene su propiedad de heap usando como llave la primera posici´n de la lista Ai . La funci´n merge sort toma tiempo o o O(nlgk) ya que la llamada para construir un minheap con k elementos como m´ximo toma a tiempo O(k) (cuando a´n ninguna lista esta vac´ Y las n llamadas a heap extract min of min u ıa). toman tiempo O(nlg(k)). (b) Exerc. 7.2-3 pag 178. Show that the running time of quicksort is Θ(n2 ) when the array A cotains distinct elements and is sorted in decreasing order. El tiempo de ejecuci´n de partition(A, p, r) es Θ(n), y los arreglos resultantes para hacer o las nuevas lamadas recursivas a quicksort son A[p, · · · , n − 1] y A[], con tama˜o n − 1 y 0 n

2

respectivamente, por ende el tiempo de ejecuci´n T de quicksort por ´rbol de recursi´n es: o a o T (n) = = = =
i=1 n

T (n − 1) + T (0) + Θ(n) T (n − 1) + Θ(1) + Θ(n) T (n − 1) + Θ(n)
n

Θ(i) c
i=1

=

i = Θ(n2 )

Por m´todo de sustituci´n con T (n) = O(n2 ): e o Caso base: Suponemos, T (0) = c0 caso base con n = 1. Si T (n) = O(n2 ) entonces T (1) = O(1) → T (1) ≤ c T (n) = Θ(n) + T (n − 1) + T (0) T (1) = Θ(0) + T (0) + T (0) = 2c0
c Entonces 2 ≥ c0 que se cumple para c suﬁcientemente grande. Paso inductivo: Suponemos que se cumple para n − 1 para concluir que tambi´n se cumple e para n. T (n) ≤ c(n − 1)2 + dn + c0 = cn2 − 2cn + c + dn + c0 = cn2 + (d − 2c)n + c + c0 ≤ cn2 ⇔ (d − 2c)n + c + c0 ≥ 0 1 ⇔ d + c0 ≤ (2 − n )c

SEARCH(L, k) IN SERT (L, x) DELET E(L, x) SU CCESSOR(L, x) P REDECESSOR(L, x) M IN U M U M (L) M AXIM U M (L)

Unsorted, singly linked O(n) O(1) O(1) O(n) O(n) O(n) O(n)

Sorted, singly linked O(n) O(n) O(n) O(1) O(n) O(1) O(1)

Unsorted, doubly linked O(n) O(1) O(1) O(n) O(n) O(n) O(n)

Sorted, doubly linked O(n) O(n) O(1) O(1) O(1) O(1) O(1)

(e) Exerc. 11.4-1 pag 277. Consider inserting the keys 10, 20, 22, 31, 4, 15, 28, 17, 88, 59 into hash table of length m = 11 using open addresing with the auxiliary hash function h (k) = k. Illustrate the result of inserting these keys using linear probing, using quadratic probing with c1 = 1 and c2 = 3, and using double hashing with h1 (k) = k and h2 (k) = 1 + (kmod(m − 1)). Linear hash: 22 88 Quadratic hash: 22 59 Double hashing: 22 / 59 17 4 15 28 88 / 31 10 88 17 4 / 28 / 15 31 10 / / 4 15 28 17 59 31 10

(f) Exerc. 12.2-8 pag 294. Prove that no matter what node we start at in a height-h binary search tree, k successive calls to tree-successor take O(k + h) time. Ya que el tiempo de ejecuci´n de tree-successor en un ´rbol de altura h es O(h), entonces el o a tiempo de k llamadas sucesivas al ´rbol desde cualquier nodo x est´ dado por la recurrencia a a T (n) = O(h)+T1 (1)+T2 (1)+· · ·+Tk (1), es decir el costo de ejecutar la llamada a tree-successor y el costo de llamarlo en general k veces para un nodo x. (g) Exerc. 13.3-2 pag 322. Show the red-blacks trees that result after succesively inserting the keys 41, 38, 31, 12, 19, 8 into a initially empty red-black tree. 38 19 12 8 N il N il N il N il 31 N il N il 41 N il

2. Desarrolle el ejercicio 7.4-5 pag 185 del [Cormen09]. We can improve the running time of quicksort in practice by taking advantage of the fast running time of insertion sort when its input is “nearly” sorted. Upon calling quicksort on a subarray with fewer than k elements, let it simply return without sorting the array. After top-level call to quicksort returns, run insertion sort on the entire array ﬁnish the sorting process. Argue that this sorting algorithm runs in O(nk + nlg(n/k)) expected time. How should we pick k, both in theory and in practice? (a) Muestre que el tiempo esperado es de O(nk+nlog( n )) (Utilice el hecho que el tiempo esperado k de quicksort es O(nlog(n)). El tiempo de ejecuci´n de este algoritmo depende del tiempo que tome ejecutando el quicko sort mas el tiempo que tome ejecutanto el insertion sort. Primero trataremos el tiempo del quicksort que est´ dado por el n´mero total de comparaciones X que se hace durante toda a u la ejecuci´n del algoritmo, teniendo en cuenta que en subarreglos de tama˜o menor a k el o n

4

algoritmo de quicksort no se ejecutar´. a
n−1 n

X

=
i=1 j=k+i+1 n−1 n−i−1

2 j−i+1

=
i=1 r=k n−1 n

2 r+2
n−1 n

<
i=1 r=k n−1

2 = r

i=1

r=1

2 2 − r r=1 r

k

=
i=1 n−1

O(lg(n)) − O(lg(k))

=
i=1 n−1

O(lg(n)) + O(lg(1/k)) n n O(lg( )) = O(nlg( )) = T Q(n, k) k k i=1

=

Ahora el arreglo en general est´ ordenado en subarreglos de tama˜o m´ximo k que pueden a n a estar no ordenados internamente. Asi que el tiempo de ejecuci´n del insertion sort es la suma o del tiempo de aplicaci´n de este algoritmo sobre cada uno de los subarreglos. El n´mero o u m´ximo de estos subarreglos es de n y por cada uno el tiempo promedio y en el peor caso es a k de O(k 2 ). T I(n, k) T (n, k) = = n O(k 2 ) = O(nk) k

n T Q(n, k) + T I(n, k) = O(nlg( )) + O(nk) = O(nk + nlg(n/k)) k

(b) De manera te´rica, encuentre un buen valor para k. o T (n, k) = O nk + nlg(n/k)

= dnk + dnlg(n/k) ∂ T (n, k) ∂k = dnk + d(lg(e))nln(n) − d(lg(e))nln(k) ∂ = dnk + d(lg(e))nln(n) − d(lg(e))nln(k) ∂k d(lg(e))n = dn − =0 k = d(lg(e))n =⇒ k = lg(e) d(lg(e))n dn = = >0 k2 lg(e)

dnk ∂ d(lg(e))n dn − ∂k k

Por medio del m´todo de la segunda derivada se demostro que con k = lg(e) la funci´n T (n, k) e o toma sus menores valores en funci´n de n teoricamente. o (c) Implemente el algoritmo en Python. def insertionsort ( A ): for j in range (1 , len ( A )): key = A [ j ] i =j -1 while (i >=0 and A [ i ] > key ): A [ i +1]= A [ i ] i =i -1 5

return self . Nodekey < otro . Nodekey def __ne__ ( self , otro ): return self . Nodekey != otro . Nodekey class TREENODE : def __init__ ( self , left , key , right ,p , color ): self . key = key self . left = left self . right = right self . p = p self . color = color class RBTREE : def __init__ ( self ): self . NIL = TREENODE ( None , Node ( " NIL " , None ,0) , None , None ,1) self . root = self . NIL def LEFTROTATE ( self , x ): y = x . right x . right = y . left if y . left != self . NIL : y . left . p = x y.p=x.p if x . p == self . NIL : self . root = y elif x == x . p . left : x . p . left = y else : x . p . right = y y . left = x x.p= y def RIGHTROTATE ( self , x ): y = x . left x . left = y . right if y . right != self . NIL : y . right . p = x y.p=x.p if x . p == self . NIL : self . root = y elif x == x . p . left : x . p . left = y else : x . p . right = y y . right = x x.p= y def RBINSERTFIXUP ( self , z ): while z . p . color ==0: if z . p == z . p . p . left : y = z . p . p . right if y . color == 0: z . p . color =1 y . color =1 z . p . p . color =0 z=z.p.p 7

else : if z == z . p . right : z=z.p self . LEFTROTATE ( z ) z . p . color =1 z . p . p . color =0 self . RIGHTROTATE ( z . p . p ) else : y = z . p . p . left if y . color == 0: z . p . color =1 y . color =1 z . p . p . color =0 z=z.p.p else : if z == z . p . left : z=z.p self . RIGHTROTATE ( z ) z . p . color =1 z . p . p . color =0 self . LEFTROTATE ( z . p . p ) self . root . color =1 def INSERT ( self , z ): y = self . NIL x = self . root while x != self . NIL : y=x if z . key < ( x . key ): x = x . left else : x = x . right z.p=y if y == self . NIL : self . root = z elif z . key < ( y . key ): y . left = z else : y . right = z z . left = self . NIL z . right = self . NIL z . color = 0 self . RBINSERTFIXUP ( z ) def SEARCH ( self , key ): if self . root == self . NIL : return None node = self . root while ( node != self . NIL and node . key != key ): if ( node . key <( key )): node = node . right else : node = node . left if ( node == self . NIL ): return None return node 8

def INORDER ( self ): if self . root == self . NIL : return [] else : return inorder ( self . root , self . NIL ) def SUCCESSOR ( self , node ): if ( node == self . NIL ): return None if ( node . right != self . NIL ): node = node . right while ( node . left != self . NIL ): node = node . left return node p = node . p while ( p != self . NIL and node == p . right ): node = p p = node . p return p

def inorder ( node , NIL ): if node == NIL : return [] else : result = inorder ( node . left , NIL ) result += [ node . key ] result += inorder ( node . right , NIL ) return result def BUILD_TREE ( file , tree ): for line in file : Atree = tree for i in range (1 , len ( line ) -2): if len ( line ) -3== i : Nodetemp = Node ( line [ i ] , RBTREE () ,1) else : Nodetemp = Node ( line [ i ] , RBTREE () ,0) temp = Atree . SEARCH ( Nodetemp ) if temp == None : temp2 = TREENODE ( None , Nodetemp , None , None ,0) Atree . INSERT ( temp2 ) Atree = Nodetemp . NEWTREE else : Atree = temp . key . NEWTREE if ( len ( line ) -3== i ): temp . key . term =1 def SEARCH_WORD ( word , tree ): Atree = tree for letter in range ( len ( word )): Nodetemp = Node ( word [ letter ] , RBTREE () ,0) temp = Atree . SEARCH ( Nodetemp ) if temp == None : return -1 9

Atree = temp . key . NEWTREE if ( letter == len ( word ) -1 and temp . key . term ==1): return 1 return 0 def getalphabet (): alphabet = [] for i in range (26): alphabet +=( chr ( i +97)) return alphabet def addletter ( word , tree ): S = [] alphabet = getalphabet () for i in range ( len ( word )): for char in alphabet : similar = word [: i ]+ char + word [ i :] if ( SEARCH_WORD ( similar , tree )==1): S . append ( similar ) return S def removeletter ( word , tree ): S = [] for i in range ( len ( word )): similar = word [: i ]+ word [ i +1:] if ( SEARCH_WORD ( similar , tree )==1): S . append ( similar ) return S def transposeletters ( word , tree ): S = [] for i in range ( len ( word ) -1): similar = word [: i -1]+ word [ i ]+ word [i -1]+ word [ i +1:] if ( SEARCH_WORD ( similar , tree )==1): S . append ( similar ) return S def replaceletter ( word , tree ): S = [] alphabet = getalphabet () for i in range ( len ( word )): for char in alphabet : similar = word [: i ]+ char + word [ i +1:] if ( SEARCH_WORD ( similar , tree )==1): S . append ( similar ) return S def s e a r c h _ s i m i l a r _ w o r d s ( word , tree ): S = [] S += addletter ( word , tree ) S += removeletter ( word , tree ) S += transposeletters ( word , tree ) S += replaceletter ( word , tree ) return S if __name__ == ’ __main__ ’: try : file = open ( ’ spanish . lex ’ , ’r + ’) except IOError : print " NO SE PUDO CARGAR EL ARCHIVO " else : tree = RBTREE () 10

BUILD_TREE ( file , tree ) print " YA CARGO EL DICCIONARIO " i =20 while ( i ): word = raw_input () case = SEARCH_WORD ( word , tree ) if case == 1: print " Encontrado . " if case == -1: print " No encontrado , pero una lista de palabras similares es : print s e a r c h _ s i m i l a r _ w o r d s ( word , tree ) if case == 0: print " La palabra es una subcadena de otra palabra si encontra i = i -1 El diccionario debe ser llenado inicialmente con el conjunto de las palabras dado en la siguiente p´gina: http://www.umich.edu/~archive/linguistics/texts/lexica/(span-lex.zip. a 4. Resuelva el problema del juez en l´ ınea con nombre Christmas Play http://www.spoj.pl/problems/ AMR10G/. # ! / usr / bin / python def CHRISTMAS ( T ): for i in range ( T ): N , K = map ( int , raw_input (). split ()) A = map ( int , raw_input (). split ()) diferencia = 1000000000 if K == N : print max ( A ) - min ( A ) else : if ( K == 1): diferencia = 0 A . sort () for j in range (N - K +1): if (( A [ j +K -1] - A [ j ]) < diferencia ): diferencia = A [ j +K -1] - A [ j ] print diferencia while (1): try : CHRISTMAS ( int ( raw_input ())) except : break

11

Quick-Insertion-Sort

0.6

0.5

K=1 K=10 K=20 K=30 K=40 K=50 K=60 K=70

0.4

1

Tiempo de ejcucion

0.3

0.2

0.1

0 20000 30000 Tamano del array 40000 50000 60000

0

10000

Quick-Insertion-Sort

0.9 K=8 K=10 K=15 K=20 K=22

0.8

0.7

0.6

2

0.5

Tiempo de ejcucion

0.4

0.3

0.2

0.1

0 30000 40000 50000 60000 Tamano del array 70000 80000 90000 100000

0

10000

20000

Quick-Insertion-Sort K=10 K=12 K=14 K=16 K=18

1.2

1

0.8

3

Tiempo de ejcucion

0.6

0.4

0.2

0 40000 60000 Tamano del array 80000 100000 120000

0

20000

Quick-Insertion-Sort

1.2

1

K=10 K=11 K=12 K=13 K=14 K=15

0.8

4

Tiempo de ejcucion

0.6

0.4

0.2

0 40000 60000 Tamano del array 80000 100000 120000

0

20000