Análise de complexidade de tempo em estruturas de dados lineares Análise de Algoritmos Análise de operações em struturas de dados lineares Profa. Sheila Morais de Almeida DAINF-UTFPR-PG março - 2016 Listas ligadas Análise de complexidade de tempo em estruturas de dados lineares Listas ligadas Pilhas e Filas Pilhas e filas são estruturas onde os elementos a serem removidos são pré-especificados. Na pilha: o último que entra é o primeiro a ser removido. Na fila: o primeiro que entra é o primeiro a ser removido. Análise de complexidade de tempo em estruturas de dados lineares Pilha A operação de inserção em uma pilha é chamada de PUSH. A operação de remoção em uma pilha é chamada de POP. Pode-se implementar uma pilha de no máximo n elementos utilizando-se um vetor com n posições. Listas ligadas Análise de complexidade de tempo em estruturas de dados lineares Pilha Considere que: • S[1..n] é o vetor que contém a pilha; • top(S) guarda a posição do topo da pilha no vetor; • a pilha contém os elementos de S[1] a S[top(S)]; • S[1] é o elemento no fundo da pilha; • S[top(S)] é o elemento no topo da pilha. Exemplo... Listas ligadas Análise de complexidade de tempo em estruturas de dados lineares Pilha Quando top(S) = 0, a pilha está vazia. stack-empty(S) if(top(S) == 0) return true; else return false; Listas ligadas Análise de complexidade de tempo em estruturas de dados lineares Listas ligadas Pilha Se uma pilha vazia sofre operação de POP, dizemos que ocorre o erro de stack underflows. Se uma pilha cheia sofre operação de PUSH, dizemos que ocorre o erro de stack overflows. PUSH(S,x) //insere o elemento x no topo da pilha if(top(S) < n) { top(S) = top(S) + 1; S[top(S)] = x; } else printf(“erro: stack overflows \n”); Análise de complexidade de tempo em estruturas de dados lineares Pilha POP(S) //insere o elemento x no topo da pilha if(stack-empty(S)) printf(“erro: stack underflows\n”); else { top(S) = top(S) − 1; return S[top(S) + 1]; } Listas ligadas Análise de complexidade de tempo em estruturas de dados lineares Listas ligadas Pilha Todas as operações em uma pilha têm complexidade de tempo O(1). Análise de complexidade de tempo em estruturas de dados lineares Listas ligadas Fila A operação de inserção em uma fila é chamada de ENQUEUE. A operação de remoção de uma fila é chamada de DEQUEUE. Em uma fila, o primeiro elemento inserido é o primeiro elemento removido. Filas têm cauda e cabeça. Quando um elemento é inserido, entra na cauda. Um elemento na cabeça é o próximo a ser removido. Exemplo... Análise de complexidade de tempo em estruturas de dados lineares Listas ligadas Fila • head(Q): posição da cabeça da fila; • tail(Q): posição em que um próximo elemento será inserido. • Todos os elementos da fila estão entre as posições head(Q), head(Q) + 1, head(Q) + 2, . . . , tail(Q) − 1, considerando que se a fila atingir a posição n, então ela continua na posição 1, em ordem circular. • A fila está vazia quando head(Q) == tail(Q). • Inicialmente, head(Q) = tail(Q) = 1. • Quando a fila está cheia, head(Q) = tail(Q) + 1. Análise de complexidade de tempo em estruturas de dados lineares Fila ENQUEUE(Q,x) if(head(Q) == tail(Q) + 1) printf(“erro: queue overflows\n”); else { Q[tail(Q)] = x; if(tail(Q) == length(Q)) tail(Q) = 1; else tail(Q) = tail(Q) + 1; } Listas ligadas Análise de complexidade de tempo em estruturas de dados lineares Fila DEQUEUE(Q) if(head(Q) == tail(Q)) printf(“erro: queue underflows\n”); else { x = Q[head(Q)]; if(head(Q) == length(Q)) head(Q) = 1; else head(Q) = head(Q) + 1; } return x; Listas ligadas Análise de complexidade de tempo em estruturas de dados lineares Listas ligadas Fila Cada operação na fila tem complexidade de tempo O(1) na fila. Análise de complexidade de tempo em estruturas de dados lineares Listas ligadas Listas ligadas Suponha uma lista ligada não-ordenada. Busca na lista ligada A função list-search(L,k) encontra o primeiro elemento com chave k na lista L, realizando uma busca linear simples e retornando um apontador para esse elemento. Se não houver elemento com a chave k na lista, a função retorna Nil. Análise de complexidade de tempo em estruturas de dados lineares Listas ligadas list-search(L,k) x = head(L); while(x 6= Nil && key (x) 6= k) x = next(x); return x; Pior caso da função list-search: Θ(n). Listas ligadas Análise de complexidade de tempo em estruturas de dados lineares Listas ligadas Inserção na lista ligada list-insert(L,x) next(x) = head(L); if(head(L) 6= Nil) prev [head(L)] = x; head(L) = x; prev (x) = Nil; O tempo de execução da função list-insert(L,x) é O(1). Listas ligadas Análise de complexidade de tempo em estruturas de dados lineares Listas ligadas Listas ligadas Remoção da lista ligada O procedimento de remoção da lista retira o elemento da lista L. Primeiro executa-se a função list-search(L,x) que procura pelo elemento x, na lista L; depois atualizam-se os apontadores: list-delete(L,x) if(prev (x) 6= Nil) next(prev (x)) = next(x); else head(L) = next(x); if(next(x) 6= Nil) prev (next(x)) = prev (x); Análise de complexidade de tempo em estruturas de dados lineares Listas ligadas Listas ligadas list-delete executa em tempo O(1), mas Θ(n) é necessário no pior caso para procurar o elemento na lista L. Análise de complexidade de tempo em estruturas de dados lineares Listas ligadas Listas ligadas - Sentinelas Sentinelas O uso de sentinelas pode simplificar o código da função list-delete. Podemos ignorar os limites da cabeça e da cauda, criando uma lista duplamente ligada circurlar. Exemplo... Análise de complexidade de tempo em estruturas de dados lineares Listas ligadas Listas ligadas - Sentinelas Cria-se um objeto nil(L) que representa Nil, mas tem todos os campos da estrutura nó. Essa sentinela e colocada entre a a cabeça e a cauda da lista. O campo next(nil(L)) aponta para a cabeça da lista. O campo prev (nil(L)) aponta para a cauda da lista. O campo next da cauda aponta para nil(L). O campo prev da cabeça aponta nil(L). Assim, pode-se eliminar head(L). Exemplo... Análise de complexidade de tempo em estruturas de dados lineares Listas ligadas - Sentinelas list-delet’(L,x) next(prev (x)) = next(x); prev (next(x)) = prev (x); list-searc’(L,x) x = next(nil(L)); while(x 6= nil(L) && key (x) 6= k) x = next(x); return x; Listas ligadas Análise de complexidade de tempo em estruturas de dados lineares Listas ligadas - Sentinelas list-inser’(L,x) next(x) = next(nil(L)); prev (next(nil(L))) = x; next(nil(L)) = x; prev (x) = nil(L); Listas ligadas Análise de complexidade de tempo em estruturas de dados lineares Listas ligadas Listas ligadas - Sentinelas Sentinelas raramente reduzem a complexidade assintótica das estruturas de dados, mas podem reduzir as constantes. O ganho de se usar sentinelas é mais relacionado à clareza do código que ao tempo de execução. No caso das listas, o tempo melhorou em O(1). Existem casos em que o uso de sentinelas reduz o tempo de execução de um laço, melhorando o tempo em O(n) ou O(n2 ). Sentinelas não devem ser usadas indiscriminadamente. Se houverem muitas listas pequenas, o uso de sentinelas pode ser um desperdı́cio de memória significativo.