Estruturas de Dados e Complexidade de Algoritmos Prof. Dr. Lucídio dos Anjos Formiga Cabral PPGI/UFPB Março/2009 Descrição do curso • Conteúdo – – – – – – – – Introdução Fundamentos de algoritmos Análise da eficiência de algoritmos Ordenação e estatísticas de ordem Estruturas de dados avançadas Técnicas avançadas de projeto e análise Tópicos selecionados Problemas NP-Completos Bibliografia • Básica: – CORMEN, T.; LEISERSON, C.; RIVEST, R.; STEIN, C.; Algoritmos: Teoria e Prática, Editora Campus, Rio de Janeiro, 2002. • Complementar – GOODRICH, M. T., TAMASSIA R., Projeto de Algoritmos, Editora Bookman, Porto Alegre, 2004. – ZIVIANI, N., Projeto de Algoritmos, Editora Pioneira Thomson, Belo Horizonte, 2004. – Knuth, D., The Art of Computer Programming, Volumes 1,2 e 3, Addison-Wesley,1997. – TOSCANI, L. V.; VELOSO. PAULO A. S. Complexidade de Algoritmos, Editora Sagra-Luzzatto, 2001. – SZWARCFITER, J.; Grafos e Algoritmos Computacionais, Editora Campus, Rio de Janeiro, 1984. – TERADA, R.; Desenvolvimento de Algoritmos e Estrutura de Dados. Editora Makron Books, 1991. Organização do Curso • Página do curso www.di.ufpb.br/~lucidio/complexest.htm • 3 provas escritas Introdução • O que é um algoritmo? – São as idéias implícitas nos programas de computadores. – Corresponde a um conjunto bem definido de regras que especificam uma seqüência de operações a serem aplicadas a um conjunto de dados, chamado entrada, produzindo após uma quantidade finita de tempo um conjunto de dados chamado saída. – Também chamado de processo, procedimento computacional, etc. – Pode ser implementado de diferentes formas. O que será estudado? • Objetivos principais: – Um conjunto de ferramentas práticas: uma coleção de algoritmos fundamentais para uso em outros cursos, ou em seus trabalhos futuros. – Estudo teórico: uma avaliação dos aspectos envolvidos no projeto, análise ou seleção de um algoritmo para um novo problema. Exemplos de algoritmos 1 - Ordenação Entrada: Uma seqüência de n números ‹a1, a2, ..., an›. Saída: Uma reordenação da seqûëncia de entrada ‹a'1, a'2, ...,a'n›, onde a'1 ≤ a'2 ≤ ... ≤ a'n 2 – Número Primo Entrada: Uma número natural q. Saída: sim ou não, dependendo se q é primo. NÓS BUSCAMOS ALGORITMOS QUE SEJAM CORRETOS E EFICIENTES !!! Problemas • Estudaremos os problemas computacionais, que consistem de uma descrição geral da questão a ser respondida, em geral, envolvendo algumas variáveis livres ou parâmetros. • Uma instância de um problema computacional é uma questão específica obtida por associar valores aos parâmetros do problema. Problema do Caixeiro Viajante • Instância: Um conjunto de cidades X juntamente com a informação de distância d(x,y) entre qualquer par x, y pertencentes a X. • Questão: Qual é a menor rota circular que inicia e termina em uma dada cidade e visita todas as cidades? Problema Computacional • Um instância de um problema computacional é um possível valor para a entrada. – ‹45, 7, 13, 23, 2› é uma instância para o problema da ordenação. – 29 é uma instância para o problema dos números primos. • Um algoritmo está correto se, para qualquer instância, ele termina e retorna como saída o valor esperado. Como expressar algoritmos? • Aspectos como precisão e facilidade de expressão são importantes. • Três formas – Linguagem natural – Pseudo-Código – Linguagem de Programação • Infelizmente, quanto maior a facilidade de expressão menor é a precisão. Corretude • Para qualquer algoritmo, nós devemos provar que ele sempre retorna a saída desejada para todas as instâncias válidas do problema. • Para a ordenação, isto deve ser válido ainda que a entrada já esteja ordenada, ou que contenha elementos repetidos Quão bom é um dado algoritmo? • Existem muitas considerações envolvendo esta questão? – Corretude • Corretude teórica • Estabilidade númerica – Eficiência • Complexidade • Velocidade • Uso de outros recursos Corretude não é óbvia! • • • O seguinte problema aparece em aplicações de manufatura e transporte. Suponha que você tenha um braço de robô equipado com um soldador. Para habilitar o braço do robô a soldar todos os pontos de contato, devemos construir uma ordem de visita aos pontos. Desde que robôs são caros, nós precisamos encontrar a ordem que minimiza o tempo (ou distância percorrida) que ele gasta para efetuar a solda nos pontos desejados. Imagine um algoritmo para encontrar o melhor percurso! Estratégia do vizinho mais próximo • • Uma solução muito popular inicia em algum ponto p0 e então caminha em direção ao vizinho mais próximo, digamos p1, e repete o procedimento. Algoritmo Visite o ponto inicial p(0) P = p(0) i = 0 Enquanto existir ponto não visistado i = i + 1 Seja p(i) o ponto não visitado, mais próximo de p(i-1) Visite p(i) Retorne para p(0) a partir de p(i) • Este algoritmo é simples de entender e implementar e muito eficiente. Entretanto ............. Estratégia do vizinho mais próximo • Entretanto, ele não é CORRETO!!! Adotar a estratégia de sempre começar pelo ponto mais à esquerda ou a partir de qualquer outro ponto não corrige o problema. Um algoritmo correto • • Nós podemos tentar todas as ordens possíveis dos pontos e então selecionar a ordem que minimiza o comprimento total. Algoritmo d = INF Para cada uma das n! Permutações Pi dos n pontos Se (custo(Pi) <= d) então d = custo(Pi) Pmin = Pi Retorne Pmin • • • Desde que todas as ordens possíveis são consideradas, tem-se a garantia de terminar com o percurso (ciclo) de menor custo possível. Para valores ainda modestos de n este algoritmo se torna inviável. Nenhum algoritmo correto eficiente existe para o problema do caixeiro viajante. O modelo RAM • Algoritmos são a única parte durável, importante e original da ciência da computação porque podem ser estudados de modo independente da linguagem e da máquina. • Assim sendo, faremos toda a nossa análise baseada no modelo de computação RAM (Máquina de Acesso Aleatório). – Cada operação simples (+, -,=,if, call) toma exatamente um passo. – Laços e chamadas de procedimentos não são operações simples, mas dependem sobre o tamanho da entrada e do conteúdo do procedimento. – Cada acesso a memória toma exatamente um passo • Nós medimos o tempo de execução de um algoritmo contando o número de passos. Complexidade de melhor, médio e pior caso • • • A complexidade de pior caso de um algoritmo é a função definida pelo número máximo de passos tomados sobre qualquer instância de tamanho n. A complexidade de melhor caso de um algoritmo é a função definida pelo número mínimo de passos tomados sobre qualquer instância de tamanho n. A complexidade de caso médio de um algoritmo é a função definida pelo número médio de passos tomados sobre qualquer instância de tamanho n. Ordenação por Inserção • Uma maneira de ordenar um vetor de n elementos é iniciar com uma lista vazia e sucessivamente inserir novos elementos na posição correta: • Em cada estágio, o elemento inserido forma uma lista ordenada e após n inserções tem-se a lista totalmente ordenada. Quão eficiente é este algoritmo? O tempo de execução muda para instâncias diferentes!!!! Como esse algoritmo se comporta para uma lista já ordenada na entrada? E para uma lista ordenada em ordem inversa? • • • • Análise exata da ordenação por inserção • Contaremos o número de vezes que cada linha do pseudo-código será executada. Análise exata da ordenação por inserção • Para calcular T(n) do algortimo de ordenação, faremos: Análise exata da ordenação por inserção • Melhor caso: lista ordenada de elementos • Avaliando o laço do enquanto podemos achar T[ j ] <= x quando x tem valor inicial (i-1). E observamos que t(i)=1 para i=2,...,n. Portanto, o tempo de execução, neste caso é: • O tempo de execução pode ser expresso então como: T(n) = an + b Análise exata da ordenação por inserção • Pior caso: lista ordenada na ordem inversa – Cada elemento T[ i ] deve ser comparado com todos os elementos da lista ordenada T[1...j –1] tal que t(i)=i para i=2,3,...,n. – Observe que: Análise exata da ordenação por inserção • Portanto: Análise exata da ordenação por inserção • Que pode ser expresso como: Análise do pior caso e do caso médio • Na análise do algoritmo de ordenação consideramos o melhor e o pior caso. Vamos nos concentrar apenas no tempo de execução do pior caso, pois: – Seu tempo de execução corresponde a um limite superior sobre o tempo de execução para qualquer instância. – Ocorre com freqüência em alguns algoritmos. • Exemplo: pesquisa em um banco de dados por informação não armazenada. – Muitas vezes, o caso médio é quase tão ruim quanto o pior caso. Ordem de Crescimento • A função obtida na análise do pior caso do algoritmo de ordenação foi a função n2+bn+c. Esta função pode ser representada pelo termo n2 que tem crescimento muito superior aos demais termos. • A ordem de crescimento é dada pelo termo mais significante da função. No algoritmo de ordenação nós dizemos que ele é de O(n2). • Um algoritmo é mais eficiente que outro, se seu tempo de execução no pior caso tem uma ordem de crescimento menor.