Técnicas de Desenho de Algoritmos Mudança de ênfase: da implementação de algoritmos para o desenho de algoritmos A ver: 5 tipos de algoritmos abordagem ao problema exemplos complexidade em tempo e espaço Referências: Mark Allen Weiss. Data Structures & Algorithm Analysis in Java. Addison- Wesley, 1999. Robert Sedgewick. Algorithms in C++. Addison-Wesley, 1992. Steven S. Skiena. The Algorithm Design Manual. Springer 1998.
Algoritmos Gananciosos Exemplos Anteriores: Dijkstra, Prim, Kruskal Cada fase do algoritmo: decisão baseada no ganho imediato consequências futuras não consideradas Algoritmo atinge óptimos locais se é óptimo global, é solução se não, pode servir para obter aproximação Exemplo de problema que resolve bem: fazer trocos, minimizando número de notas e moedas estratégia: dar repetidamente a maior unidade possível Exemplo de problema que não resolve bem: caminho mais rápido usando estratégia da melhor aresta adjacente
Problema de escalonamento Dados: tarefas e tempos Objectivo: minimizar tempo médio de terminação Tempo médio: 25 Tarefa Tempo j1 j2 j3 j4 j 1 15 j 2 8 j 3 3 Tempo médio: 17.75 15 23 26 36 j 4 10 j3 j2 j4 j1 3 11 21 36
2ª solução é óptima Escalonamento Porquê: tarefas mais curtas primeiro Tarefas: j i1, j i2,..., j in Terminações: ti1, ti1+ti2,... Custo total da solução: n n n (n k +1) t = ik (n+1) t ik k t ik k = 1 k =1 k = 1 Se existe x>y tal que t ix < t iy : troca de j ix e j iy diminui custo
Escalonamento multiprocessador Exemplo com 3 processadores Tarefa Tempo j1 j2 j3 j4 j5 j6 j7 j8 j9 3 5 6 10 11 14 15 18 20 Total de tempos: 165 Tempo médio: 18.33 j1 j2 j3 j4 j5 j6 j7 3 5 6 13 16 20 28 34 40 j8 j9
Solução óptima não é única Para cada i, o i < n/p as tarefas j ip+1 a j (i+1)p são alocadas a processadores diferentes j1 j5 j9 j2 j4 j7 j3 j6 j8 3 5 6 14 15 20 30 34 38
Minimizar tempo de completação Tempo a minimizar é o da última tarefa a terminar j2 j5 j8 j6 j9 j1 j3 j4 j7 3 5 9 14 16 19 34 Este problema é variante do empacotamento, logo NP-completo!
Divisão e conquista Divisão: resolver recursivamente problemas mais pequenos (até caso base) Conquista: solução do problema original é formada com as soluções dos subproblemas Há divisão quando o algoritmo tem pelo menos 2 chamadas recursivas no corpo Subproblemas devem ser disjuntos Algoritmos já vistos: Travessia de árvores em tempo linear: processar árvore esquerda, visitar nó, processar árvore direita Ordenações: mergesort: ordenar 2 subsequências e juntá-las quicksort: ordenar elementos menores e maiores que pivot, concatenar
Quicksort function quicksort(array) var list less, greater if length(array) 1 return array select and remove a pivot value pivot from array for each x in array if x pivot then append x to less else append x to greater return concatenate(quicksort(less), pivot, quicksort(greater))
Programação Dinâmica Divisão e conquista: problema é partido em subproblemas que se resolvem separadamente; solução obtida por combinação das soluções Programação dinâmica: resolvem-se os problemas de pequena dimensão e guardam-se as soluções; solução de um problema é obtida combinando as de problemas de menor dimensão Divisão e conquista é top-down Programação dinâmica é bottom-up Abordagem é usual na Investigação Operacional Programação é aqui usada com o sentido de formular restrições ao problema que tornam um método aplicável Quando é aplicável a programação dinâmica: estratégia óptima para resolver um problema continua a ser óptima quando este é subproblema de um problema de maior dimensão
Aplicação directa - Fibonacci Problemas expressos recursivamente que podem ser reescritos em formulação iterativa Exemplo: números de Fibonacci /** Números de Fibonacci * versão recursiva */ n >= 0 int fib( const unsigned int n ) { if( n <= 1 ) return 1; else return fib( n-1 ) + fib( n-2 ); } /** Números de Fibonacci * versão iterativa */ int fibonacci(int n ) { int last=1, nexttolast=1, answer=1; if( n <= 1 ) return 1; for( int i = 2; i<=n; i++ ) { answer = last + nexttolast; nexttolast = last; last = answer; } return answer; }
Fibonacci Expressão recursiva: algoritmo exponencial Expressão iterativa: algoritmo linear Problema na formulação recursiva: repetição de chamadas iguais F6 F5 F4 F4 F3 F3 F2 F3 F2 F2 F1 F2 F1 F1 F0 F2 F1 F1 F0 F1 F0 F1 F0 F1 F0
Exemplo: Equação de recorrência C(n) = 2 n n 1 C(i) + n Para resolver numericamente, i =0 expressão recursiva é directa double eval( int n ) { double sum = 0.0; if( n == 0 ) return 1.0; for( int i = 0; i < n; i++ ) Sum += eval( i ); return 2.0 * sum / n + n; } Algoritmo recursivo é exponencial! Problema: repetição de chamadas
Chamadas Repetidas C5 C4 C3 C2 C1 C0 C3 C2 C1 C0 C2 C1 C0 C1 C0 C0 C2 C1 C0 C1 C0 C0 C1 C0 C0 C0 C1 C0 C0 C0 C0 C0
Solução iterativa 1 double eval(int n ) { double [ ] c = new double [n+1]; c[0] = 1.0; for( int i = 1; i <= n; i++ ) { double sum = 0.0; for( int j = 0; j < i; j++ ) sum += c[j]; Algoritmo iterativo O(n 2 ) } } c[i] = 2.0 * sum / i + i; return c[n]; Evita chamadas recursivas guardando tabela de C(n)
Solução iterativa 2 double eval(int n ) { double sum = 0.0; double [ ] a = new double [n+1]; a[0] = 1.0; for( int i = 1; i <= n; i++ ) a[i] = a[i-1] + 2.0 * a[i-1] / i + i; Algoritmo iterativo O(n) } double answer = 2.0 * a[n] / n + n; return answer; Tabela de A(n) guarda valor dos somatórios; para cada entrada basta acrescentar 1 termo
Algoritmos de retrocesso Algoritmos em que se geram escolhas que vão sendo testadas e eventualmente refeitas Problemas para os quais não existem algoritmos eficientes: retrocesso é melhor que pesquisa exaustiva solução é gerada e avaliada parcialmente quando uma solução parcial não satisfaz objectivos, retrocesso apenas desfaz última escolha evita-se a pesquisa em ramos que garantidamente não levam a solução - poda da árvore de pesquisa Exemplo: arranjo da mobília numa casa grande número de possibilidades cada peça de mobília é colocada, solução é arranjo satisfatório chegando a ponto onde qualquer arranjo é inconveniente, desfaz-se o último passo e tentase alternativa muitos arranjos nunca são testados
Problema da portagem Dados: n pontos p1, p2,..., pn situados no eixo dos xx xi é a coordenada x de pi x1= 0 determinam n (n-1)/2 distâncias d1, d2,..., dm da forma xi - xj Distâncias podem ser geradas em tempo O(n 2 ) Problema inverso: coordenadas dos pontos a partir das distâncias: mais difícil Não há algoritmo garantido como polinomial para o problema D - conjunto das distâncias D = m = n (n-1) / 2 Algoritmo que se segue: O(n 2 log n) - é conjectura
Exemplo D= {1, 2, 2, 2, 3, 3, 3, 4, 5, 5, 5, 6, 7, 8, 10} D = 15 -> n = 6 x1 = 0, x6 = 10 x1 = 0 x6 = 10 D= {1, 2, 2, 2, 3, 3, 3, 4, 5, 5, 5, 6, 7, 8} maior distância: 8 então x2 = 2 ou x5 = 8 (escolha é indiferente) x1 = 0 x5 = 8 x6 = 10 D= {1, 2, 2, 3, 3, 3, 4, 5, 5, 5, 6, 7}
Exemplo 7 - maior valor em D -> x4 = 7 ou x2 = 3 x4 = 7 distâncias x6-7 = 3 e x5-7 = 1 estão em D x2 = 3 distâncias 3-x1 = 3 e x5-3 = 5 estão em D x1 = 0 x4 = 7 x5 = 8 x6 = 10 D= {2, 2, 3, 3, 4, 5, 5, 5, 6} 6 - maior valor em D -> x3 = 6 ou x2 = 4 x3 = 6 distância x4 - x3 = 1 impossível, já não existe 1 em D x2 = 4 distâncias x2-x1 = 4 e x5 - x2 = 4 impossível, só 1 vez 4 em D É preciso retroceder!
Exemplo x4 = 7 não conduziu a solução tenta-se agora x2 = 3 x1 = 0 x2 = 3 x5 = 8 x6 = 10 D= {1, 2, 2, 3, 3, 4, 5, 5, 6} 6 - maior valor em D -> x4 = 6 ou x3 = 4 x3 = 4 impossível, só 1 vez 4 em D x1 = 0 x2 = 3 x4 = 6 x5 = 8 x6 = 10 D= {1, 2, 3, 5, 5}
Exemplo x1 = 0 x2 = 3 x3 = 5 x4 = 6 x5 = 8 x6 = 10 D = { } x 1 =0, x 5 =10 x 5 =8 Árvore de decisão x ** 4 =7 x 2 =3 * * x 3 =6 x 2 =4 x 3 =4 x 4 =6 x 3 =5
Na ausência de retrocesso Anális e D pode ser mantido como árvore de pequisa equilibrada O(n 2 ) operações em D remoção: D tem O(n 2 ) elementos, não há reinserções, total é O(n 2 ) pesquisa: 1 tentativa de colocação faz no máximo 2n, total é O(n 2 ) Tempo total é O(n 2 log n) Com retrocesso: perde-se eficiência não existe limite polinomial para o retrocesso requerido não estão identificados exemplos patológicos com pontos de coordenadas inteiras e distribuídas uniformemente, conjectura é que retrocesso não ocorre mais que O(1)
Jogos Como jogar automaticamente um jogo estratégico? Exemplo: jogo do galo pode construir-se algoritmo que nunca perde e aproveita oportunidades para ganhar posições críticas armazenadas em tabela escolha de jogada baseada na posição corrente usando uma tabela... todo a análise do jogo feita pelo programador Em geral, em jogos não triviais não é possível dispor de decisões para todos os caminhos a partir de uma posição é preciso recomputar a cada jogada é impraticável explorar todas as hipóteses
Minimax Estratégia minimax função de avaliação da qualidade de uma posição 1 se posição de vitória 0 se é empate -1 se é para perder se se pode fazer avaliação por inspecção do tabuleiro: posição terminal posição não terminal: valor é determinado assumindo recursivamente jogadas óptimas de ambos os lados Um jogador tenta minimizar e o outro maximizar o valor da posição Para posição P: Se é a minha vez de jogar avalio recursivamente as posições sucessoras Ps, escolhendo o valor maior; ao avaliar Ps as suas sucessoras são avaliadas e o menor valor é escolhido (caso mais favorável para o oponente)
Pesquisa com limite de profundidade Em jogos complexos: inviável pesquisar todos os nós terminais para avaliar a posição parar a determinada profundidade nós onde pára a recursão tratados como nós terminais função de estimativa para avaliar nós terminais Ex: xadrez - avaliar peças e suas posições Para aumentar o factor de previsão - métodos que avaliam menos nós e não perdem informação sobre posições já avaliadas X O X X O X... X X O X O X... tabela de transposição
Árvore do jogo Estrutura da pesquisa de posições (nós) e valores das avaliações 44 Max 44 Min 44 68 Max 27 44 68 C Min 42 27 86 44 68 73 Max 42 25 27 7 72 86 9 44 50 68 73 A
44 Cortes α β Estrutura da pesquisa de posições (nós) e valores das avaliações Max 40 Min 40 D Max 40 27 Min 73 40 27 B Max 73 23 30 40 19 27
Corte α 44 Max 44 40 Min 40 D? Valor em D não pode aumentar resultado na raiz: o seu nó pai é min e tem valor garantidamente inferior ao conseguido na raiz até ao momento
Corte β 44 Min 44 68 Max 68 D? Valor em C não pode aumentar resultado na raiz: nó pai é max e tem valor garantidamente superior ao conseguido na raiz até ao momento