Capítulo 2 - Algoritmos elementares de ordenação ISEL/LEIC - 2007/2008 2º ano, 1º Semestre * Estes acetatos foram parcialmente adaptados dos acetatos de AED da LEEC do Instituto Superior Técnico Prof. Rui Gustavo Crespo Nuno Leite AED: Algoritmos e Estruturas de Dados Semestre de Inverno 2007/08 http://www.deetc.isel.ipl.pt/programacao/aed/
Algoritmos elementares de ordenação Começaremos o nosso estudo com algoritmos elementares de ordenação: Selection sort, Insertion sort, Bubble sort e Shell sort Porquê estudar algoritmos elementares de ordenação? Razões de ordem prática Fáceis de codificar e por vezes suficientes Rápidos/Eficientes para problemas de dimensão média e por vezes os melhores em certas circunstâncias Razões pedagógicas Bom exemplo para aprender terminologia, compreender contexto dos problemas e bom princípio para desenvolvimento de algoritmos mais sofisticados Alguns são fáceis de generalizar para métodos mais eficientes Importante para compreensão das regras de "funcionamento" LEIC AED Inverno 2007/08 Algoritmos de ordenação 2
Contexto e regras básicas (1) Objectivo estudar métodos de ordenação de ficheiros de dados em que cada elemento (item) é caracterizado por uma chave (key) chaves são usadas para controlar a ordenação objectivo é rearranjar os dados de forma a que as chaves estejam ordenadas de forma pré-definida (numérica ou alfabética, por exemplo) Metodologia características específicas de cada item ou chave podem ser diferentes mas conceito abstracto é o mais importante utilizaremos operações abstractas nos dados: comparação, troca LEIC AED Inverno 2007/08 Algoritmos de ordenação 3
Contexto e regras básicas (2) Metodologia começaremos por estudar ordenação em arrays consideraremos primeiro ordenação de sequências de inteiros apresentaremos posteriormente uma implementação genérica Tempo de execução usualmente proporcional ao número de comparações número de movimentações/trocas (ou ambos) LEIC AED Inverno 2007/08 Algoritmos de ordenação 4
Programa de teste de algoritmos de ordenação public class IntArraySort { private static boolean less(int x, int y) { return x < y; } private static void exch(int[] a, int i, int j) { int aux = a[i]; a[i] = a[j]; a[j] = aux; } private static void lessexch(int[] a, int i, int j) { if (less(a[i], a[j])) exch (a, i, j); } // Selection Sort private static void selectionsort(int[] a, int l, int r) {... } // Insertion Sort private static void insertionsort(int[] a, int l, int r) {... } // Bubble Sort private static void bubblesort(int[] a, int l, int r) {... }... LEIC AED Inverno 2007/08 Algoritmos de ordenação 5
Programa de teste de algoritmos de ordenação... public static void sort(int[] a, int l, int r) { selectionsort(a, l, r); } public static void main(string[] args) { int n = 50; int a[] = new int[n]; for (int i = 0; i < n; ++i) { // random gera números no intervalo [0.0; 1.0[ a[i] = (int) (Math.random()*100); } sort(a, 0, n-1); // Ordenar array for (int i = 0; i < n; ++i) // Imprimir array System.out.print(a[i] + " "); } } // classe IntArraySort LEIC AED Inverno 2007/08 Algoritmos de ordenação 6
Nomenclatura (1) Tipos de Algoritmos de Ordenação não adaptativos: sequência de operações independente da ordenação original dos dados interessantes para implementação em hardware adaptativos: sequência de operações dependente do resultado de comparações (operação "less") a maioria dos que estudaremos Parâmetro de interesse é o desempenho, i.e. tempo de execução algoritmos básicos: N 2 para ordenar N itens mas por vezes os melhores para N pequeno algoritmos avançados: N log N para ordenar N itens p.ex., Quicksort, Mergesort, Heapsort LEIC AED Inverno 2007/08 Algoritmos de ordenação 7
Nomenclatura (2) Olharemos também para os recursos de memória necessários ordenação "in-place" ou utilizando memória adicional Definição: um algoritmo de ordenação é dito estável se preserva a ordem relativa dos itens com chaves repetidas ex: ordenar lista de alunos por ano de graduação quando esta já está ordenada alfabeticamente por nome é usualmente possível estabilizar um algoritmo alterando a sua chave (tem custo adicional) algoritmos básicos são quase todos estáveis, mas poucos algoritmos avançados são estáveis LEIC AED Inverno 2007/08 Algoritmos de ordenação 8
Exemplo: Algoritmo estável vs não estável Ordenado pela 1ª chave (nome) Afonso 1 Beatriz 2 Bruno 4 João 2 José 4 Mariana 2 Mário 3 Matilde 3 Sílvia 1 Tânia 4 Após ordenação pela segunda chave (número) algoritmo não estável Afonso 1 Sílvia 1 Mariana 2 João 2 Beatriz 2 Matilde 3 Mário 3 Tânia 4 Bruno 4 José 4 Após ordenação pela segunda chave (número) algoritmo estável Afonso 1 Sílvia 1 Beatriz 2 João 2 Mariana 2 Mário 3 Matilde 3 Bruno 4 José 4 Tânia 4 LEIC AED Inverno 2007/08 Algoritmos de ordenação 9
Nomenclatura (3) Definição: um algoritmo de ordenação é dito interno, se o conjunto de todos os dados a ordenar couber na memória; caso contrário é dito externo ex: ordenar dados lidos de disco é ordenação externa Distinção muito importante: ordenação interna pode aceder a qualquer dado facilmente ordenação externa tem de aceder a dados de forma sequencial (ou em blocos) Vamos estudar fundamentalmente ordenação interna LEIC AED Inverno 2007/08 Algoritmos de ordenação 10
Nomenclatura (4) Definição: um algoritmo de ordenação é dito directo se os dados são acedidos directamente nas operações de comparação e troca; caso contrário é dito indirecto Exemplo de algoritmo indirecto: se a chave for pequena e cada dado for "grande (por exemplo o nome completo de um aluno, mais morada, número de BI, etc.) nestes casos não convém estar a trocar os elementos é dispendioso basta trocar a informação correspondente aos seus índices array de índices/ponteiros em que o primeiro indica o menor elemento, etc. LEIC AED Inverno 2007/08 Algoritmos de ordenação 11
Exemplo: Ordenação indirecta Em Java, a ordenação de elementos de tipo básico é feita de forma directa, e a ordenação de objectos é realizada de forma indirecta, dado que estes são manipulados por referência A figura ao lado ilustra o conteúdo dum array a ser ordenado, contendo referências para objectos com chaves E X A M P L E, nesta ordem. Após a ordenação das referências, o array passa a referir os objectos na ordem A E E L M P X LEIC AED Inverno 2007/08 Algoritmos de ordenação 12
Ordenação por selecção - Selection Sort Algoritmo: procurar menor elemento e trocar com o elemento situado na 1.ª posição procurar 2.º menor elemento e trocar com o elemento situado na 2.ª posição proceder assim até ordenação estar completa a sombreado, indicam-se os elementos já ordenados LEIC AED Inverno 2007/08 Algoritmos de ordenação 13
Implementação private static void selectionsort(int[] a, int l, int r) { int min; for (int i = l; i < r; ++i) { min = i; for (int j = i+1; j <= r; ++j) if (less(a[j], a[min])) min = j; exch(a, i, min); } } LEIC AED Inverno 2007/08 Algoritmos de ordenação 14
Selection Sort -Análise Ciclo interno apenas faz comparações troca de elementos é feita fora do ciclo interno cada troca coloca um elemento na sua posição final Número de trocas é N-1 (porque não N?) tempo de execução dominado pelo número de comparações! Propriedade: Selection sort usa aproximadamente N trocas e N 2 /2 comparações Demonstração: para cada item i de 1 a N-1 há uma troca e N-i comparações (ver sombreados no exemplo) Logo há N-1 trocas e (N-1) + (N-2) +... + 2 + 1 = N(N-1)/2 comparações LEIC AED Inverno 2007/08 Algoritmos de ordenação 15
Selection Sort -Análise Factos: desempenho é independente da ordenação inicial dos dados; a única coisa que depende desta ordenação é o número de vezes que min éactualizado quadrático no pior caso (dados ordenados de forma inversa) N log N em média (dados ordenados aleatoriamente) Algoritmo não adaptativo Ordenar um ficheiro já ordenado demora tanto tempo como ordenar um ficheiro ordenado aleatoriamente! LEIC AED Inverno 2007/08 Algoritmos de ordenação 16
Ordenação Síntese da Aula 1 Introdução ao problema da ordenação de dados Simplicidade do problema facilita a compreensão de conceitos chave em algoritmos Problema de grande aplicação Definição das regras base e interface de utilização operações elementares relevantes Definições e propriedades gerais Ordenação por Selecção Selection sort Descrição Exemplo de aplicação Análise de eficiência LEIC AED Inverno 2007/08 Algoritmos de ordenação 17
Ordenação por inserção - Insertion Sort Ideia: considerar os elementos um a um e inseri-los no seu lugar entre os elementos já tratados (mantendo essa ordenação) ex: ordenar cartas de jogar inserção implica arranjar novo espaço ou seja mover um número elevado de elementos uma posição para a direita inserir os elementos um a um começando pelo da 1ª posição LEIC AED Inverno 2007/08 Algoritmos de ordenação 18
Insertion Sort - Funcionamento Durante a primeira passagem de Insertion sort, o S na segunda posição é maior que A, não sendo necessário mover-se. Na segunda passagem, quando é encontrado o O na terceira posição, este é trocado com o S colocando A O S em ordem, e assim por diante. Elementos não sombreados e não assinalados com um círculo, são aqueles que são movidos uma posição para a direita LEIC AED Inverno 2007/08 Algoritmos de ordenação 19
Implementação private static void insertionsort1(int[] a, int l, int r) { for (int i = l+1; i <= r; ++i) { for (int j = i; j > l; --j) lessexch(a, j, j-1); } } LEIC AED Inverno 2007/08 Algoritmos de ordenação 20
Insertion Sort - Comentários Elementos à esquerda do índice corrente estão ordenados mas não necessariamente na sua posição final podem ainda ter de ser deslocados para a direita para dar lugar a elementos menores encontrados posteriormente Implementação da ordenação por inserção na pág. anterior é ineficiente código é simples, claro, mas pouco eficiente; pode ser melhorado ilustra bom raciocínio: encontrar solução simples estudar o seu funcionamento melhorar desempenho através de pequenas transformações LEIC AED Inverno 2007/08 Algoritmos de ordenação 21
Insertion Sort - Melhorar desempenho Demasiadas operações de comparação/troca (lessexch) podemos parar se encontramos uma chave que não é maior que a do item a ser inserido (array está ordenado à esquerda) podemos sair do ciclo interno se less(a[j-1], a[j]) éverdadeira modificação torna o algoritmo adaptativo aumenta desempenho aproximadamente por um factor de 2 Passa a haver duas condições para sair do ciclo mudar para um ciclo while remover instruções irrelevantes lessexch não é o melhor processo de mover vários dados uma posição para a direita LEIC AED Inverno 2007/08 Algoritmos de ordenação 22
Insertion Sort - Versão adaptativa private static void insertionsort2(int[] a, int l, int r) { for (int i = l+1; i <= r; ++i) { int v = a[i]; int j = i; while (j >= l+1 && less(v, a[j-1])) { a[j] = a[j-1]; --j; } a[j] = v; } } LEIC AED Inverno 2007/08 Algoritmos de ordenação 23
Insertion Sort -Análise Propriedade: Insertion sort usa aproximadamente N 2 /4 comparações e N 2 /4 pseudo-trocas (translações ou movimentos) no caso médio e o dobro destes valores no pior caso Demonstração: Fácil de ver graficamente; elementos abaixo da diagonal são contabilizados (todos no pior caso) Para dados aleatórios é expectável que cada elemento seja colocado aproximadamente a meio para trás pelo que apenas metade dos elementos abaixo da diagonal devem ser contabilizados Factos: Quando a sequência a ordenar se encontra parcialmente ordenada, o Insertion sort tem um desempenho aproximadamente linear. LEIC AED Inverno 2007/08 Algoritmos de ordenação 24
Bubble Sort Talvez o algoritmo mais utilizado e o que muitas pessoas aprendem primeiro Ideia: fazer múltiplas passagens pelos dados trocando de cada vez dois elementos adjacentes que estejam fora de ordem, até não haver mais trocas supostamente muito fácil de implementar usualmente mais lento que os dois métodos elementares estudados LEIC AED Inverno 2007/08 Algoritmos de ordenação 25
Implementação private static void bubblesort(int[] a, int l, int r) { for (int i = l; i < r; ++i) { for (int j = r; j > i; --j) { lessexch(a, j, j-1); } } } LEIC AED Inverno 2007/08 Algoritmos de ordenação 26
Bubble Sort - Comentários (1) Movendo da direita para a esquerda no ficheiro de dados quando o elemento mais pequeno é encontrado na primeira passagem é sucessivamente trocado com todos à sua esquerda acaba por ficar na primeira posição na segunda passagem o 2.º elemento mais pequeno é colocado na sua posição e por diante N passagens pelos dados são suficientes! É semelhante ao método de selecção tem mais trabalho para colocar cada elemento na sua posição final todas as trocas sucessivas até chegar à posição certa LEIC AED Inverno 2007/08 Algoritmos de ordenação 27
Bubble Sort - Comentários (2) Algoritmo pode ser melhorado, tal como o algoritmo de inserção código é muito semelhante mas não igual ciclo interno de Insertion Sort percorre a parte esquerda (ordenada) do array ciclo interno de Bubble Sort percorre a parte direita (não ordenada) do array no final de cada passagem podemos testar se houve mudanças se o array estiver ordenado, pára LEIC AED Inverno 2007/08 Algoritmos de ordenação 28
Bubble Sort -Análise Propriedade: Bubble sort usa aproximadamente N 2 /2 comparações e N 2 /2 trocas no pior caso; N 2 /4 trocas no caso médio Demonstração: A i-ésima passagem de Bubble Sort requer N-i operações de comparação/troca, logo a demonstração é semelhante a Selection sort Factos: Algoritmo pode depender criticamente dos dados se for modificado para terminar quando não houver mais trocas se o ficheiro estiver ordenado, apenas um passo é necessário se estiver em ordenação inversa então na i-ésima passagem são precisas N-i comparações e trocas caso médio é semelhante (análise mais complexa) LEIC AED Inverno 2007/08 Algoritmos de ordenação 29
Comparação dos algoritmos elementares de ordenação (1) Tempos de execução quadráticos Selection Insertion Bubble Comparações N 2 /2 N 2 /4 N 2 /2 Trocas N N 2 /4 N 2 /2 LEIC AED Inverno 2007/08 Algoritmos de ordenação 30
Comparação dos algoritmos elementares de ordenação (2) Ficheiros com elementos grandes e pequenas chaves Selection Sort é linear no número de dados N dados com tamanho M (palavras/words) considerando dados manipulados por valor tem-se: comparação -1 unidade; troca - M unidades N 2 /2 comparações e NM custo de trocas termo NM domina custo proporcional ao tempo necessário para mover os dados Ficheiros quase ordenados Insertion sort e Bubble sort são quase lineares os melhores algoritmos de ordenação podem ser quadráticos neste caso! LEIC AED Inverno 2007/08 Algoritmos de ordenação 31
Comparação dos algoritmos elementares de ordenação (3) int items Integer keys String keys N S I* I B S I B S I B 1000 14 54 8 54 100 55 130 129 65 170 2000 54 218 33 221 436 229 569 563 295 725 4000 212 848 129 871 1757 986 2314 2389 1328 3210 Legenda: S Selection sort I* Insertion sort, exchange-based I Insertion sort B Bubble sort LEIC AED Inverno 2007/08 Algoritmos de ordenação 32
Ordenação Síntese da Aula 2 Ordenação por inserção Insertion sort Versão elementar Versão adaptativa Exemplo de aplicação Análise de Eficiência Bubble sort Breve referência Descrição do algoritmo e análise funcionamento Exemplo de aplicação Análise de eficiência Comparação dos três primeiros algoritmos elementares Em número de comparações e trocas Na evolução da tabela durante a execução LEIC AED Inverno 2007/08 Algoritmos de ordenação 33
Shell sort (1) Shell sort (proposto por Donald Shell em 1959) é um algoritmo que requer no pior caso menos de O(n 2 ) comparações e trocas embora seja simples perceber intuitivamente o seu funcionamento, é extremamente difícil analisar o seu desempenho estimam-se desempenhos de ordem entre O(n 3/2 ) e O(n log 2 n) (melhor alcançado actualmente), dependendo dos detalhes de implementação Shell sort éuma generalização de Insertion sort que tem em conta duas observações: Insertion sort é eficiente se a entrada estiver ordenada ou quase ordenada Insertion sort é ineficiente, em média, porque apenas move os elementos de uma posição de cada vez LEIC AED Inverno 2007/08 Algoritmos de ordenação 34
Shell sort (2) Insertion sort: se o menor item está no final da tabela, serão precisos N passos para o colocar na posição correcta Shell sort: acelerar o algoritmo permitindo trocas entre elementos que estão afastados como? O Shell sort compara os elementos separados de uma distância (gap) de várias posições (p.ex. gap=5) são realizadas múltiplas passagens com passos sucessivamente menores, terminando com gap=1, que não é mais do que realizar Insertion sort mas nesta altura, os dados já estão praticamente ordenados Insertion sort éeficiente! LEIC AED Inverno 2007/08 Algoritmos de ordenação 35
Shell sort Exemplo (1) Suponha que se pretende ordenar o array 66, 95, 93, 82, 54, 40, 35, 19, 75, 24, 32, 43, 16, 68 Ordenar os elementos separados de uma distância h éo mesmo que particionar o array em linhas de h colunas e aplicar Insertion sort ao longo das colunas Execução de Shell sort com h=5: Sequência original h=5 Sequência ordenada por colunas usando Insertion sort 66 95 93 82 54 32 35 16 68 24 40 35 19 75 24 40 43 19 75 54 32 43 16 68 66 95 93 82 LEIC AED Inverno 2007/08 Algoritmos de ordenação 36
Shell sort Exemplo (2) Execução de Shell sort com h=3: Sequência original h=3 Sequência ordenada por colunas usando Insertion sort 32 35 16 32 19 16 68 24 40 43 24 40 43 19 75 54 35 75 54 66 95 68 66 95 93 82 93 82 LEIC AED Inverno 2007/08 Algoritmos de ordenação 37
Shell sort Exemplo (3) Execução de Shell sort com h=1 (Insertion sort): Sequência original (coluna única) h=1 32 19 16 43 24 40 54 35 75 68 66 95 93 82 Sequência ordenada usando Insertion sort 16 19 24 32 35 40 43 54 66 68 75 82 93 95 LEIC AED Inverno 2007/08 Algoritmos de ordenação 38
Shell sort Exemplo (4) Em termos de implementação, a ordenação das colunas com Insertion sort não é realizada sequencialmente, ordenar 1ª col., 2ª col., mas sim de forma entrelaçada como ilustrado neste exemplo. 66 40 32 Sequência original h=5 95 35 43 93 19 16 82 75 68 54 24 k= l+h+k k= l+h+k 0 1 2 3 4 40 66 35 95 19 93 75 82 24 54 32 43 16 68 5 6 7 8 32 35 16 68 24 40 43 19 75 54 66 95 93 82 LEIC AED Inverno 2007/08 Algoritmos de ordenação 39
Shell sort Implementação para o exemplo dado private static void shellsorttest(int[] a, int l, int r) { int incs[] = { 5, 3, 1 }; for (int k = 0; k < incs.length; ++k) { int h = incs[k]; // Insertion sort com passo (gap) h for (int i = l+h; i <= r; ++i) { int v = a[i], j = i; while (j >= l+h && less(v, a[j-h])) { a[j] = a[j-h]; j -= h; } a[j] = v; } } } LEIC AED Inverno 2007/08 Algoritmos de ordenação 40
Sequência de Ordenação (1) Difícil de escolher propriedades de muitas sequências foram já estudadas possível provar que umas são melhores que outras ex: 1, 4, 13, 40, 121, 364, 1093, 3280,... (Knuth, h=3*h ant +1) melhor que 1, 2, 4, 8, 16, 32, 64, 128, 256, 512,... (Shell, h=2 i ) -> O(n 2 ) Porquê? mas pior (20%) que 1, 8, 23, 77, 281, 1073, 4193, (Sedgewick, 4 i + 1 + 3(2 i ) + 1 ) na prática utilizam-se sequências que decrescem geometricamente para que o número de incrementos seja logarítmico LEIC AED Inverno 2007/08 Algoritmos de ordenação 41
Sequência de Ordenação (2) a sequência óptima não foi ainda descoberta (se é que existe) análise do algoritmo é desconhecida ninguém encontrou a fórmula que define a complexidade complexidade depende da sequência LEIC AED Inverno 2007/08 Algoritmos de ordenação 42
Shell sort Implementação genérica private static void shellsort(int[] a, int l, int r) { int h, length = r-l+1; for (h = 1; h < length; h = 3*h+1); // empty body for (h /= 3; h > 0; h /= 3) { // Insertion sort com passo (gap) h for (int i = l+h; i <= r; ++i) { int v = a[i], j = i; while (j >= l+h && less(v, a[j-h])) { a[j] = a[j-h]; j -= h; } a[j] = v; } } } LEIC AED Inverno 2007/08 Algoritmos de ordenação 43
Análise de Shell sort Propriedades: o resultado de h-ordenar um ficheiro que está k-ordenado é um ficheiro que está simultaneamente h- e k-ordenado Propriedade muito importante: indica a ordenação actual não estraga a ordenação realizada anteriormente Shell sort faz menos do que O(N 3/2 ) comparações para os incrementos (Knuth) 1, 4, 13, 40, 121, 364, 1093,... Shell sort faz menos do que O(N 4/3 ) comparações para os incrementos (Sedgewick) 1, 8, 23, 77, 281, 1073, 4193, 16577,... Nota: sequências até agora usam incrementos que são primos entre si Shell sort faz menos do que O(N lg 2 N) comparações para os incrementos (Pratt) 1, 2, 3, 4, 6, 8, 9, 12, 16,..., 2 p 3 q,... LEIC AED Inverno 2007/08 Algoritmos de ordenação 44
Vantagens de Shell sort rápido/eficiente pouco código melhor método para ficheiros pequenos e médios aceitável para elevados volumes de dados Muito utilizado na prática, embora difícil de compreender! LEIC AED Inverno 2007/08 Algoritmos de ordenação 45
Implementação genérica de algoritmos de ordenação (1) public class StaticGenericArraySort { private static boolean less(object x, Object y, Comparator cmp) { if (cmp == null) return ((Comparable)x).compareTo(y) < 0; else return cmp.compare(x, y) < 0; } private static void exch(object[] a, int i, int j) { Object aux = a[i]; a[i] = a[j]; a[j] = aux; } private static void lessexch(object[] a, int i, int j, Comparator cmp) { if (less(a[i], a[j], cmp)) exch (a, i, j); } private static void selectionsort(object[] a, int l, int r, Comparator cmp) { for (int i = l; i < r; ++i) { int min = i; for (int j = i+1; j <= r; ++j) if (less(a[j], a[min], cmp)) min = j; exch(a, i, min); } } LEIC AED Inverno 2007/08 Algoritmos de ordenação 46
Implementação genérica de algoritmos de ordenação (2) } // Métodos públicos public static void sort(object[] a, int l, int r, Comparator cmp) { selectionsort(a, l, r, cmp); } public static void main(string[] args) { Student[] students = {new Student("Nuno", 30), new Student("Luis", 10), new Student("Afonso", 40), new Student("Ana", 20) }; // Ordem natural, neste caso por nome de forma ascendente // (definido pelo método compareto) sort(students, 0, students.length-1, null); // ordenação por nome de forma descendente sort(students, 0, students.length-1, Student.BY_NAME); // ordenação por número de forma ascendente sort(students, 0, students.length-1, Student.BY_NUMBER); } LEIC AED Inverno 2007/08 Algoritmos de ordenação 47
Ordenar por diferentes critérios class Student implements Comparable<Student> { private String name; private int number; private static class ByName implements Comparator<Student> { public int compare(student a, Student b) { return -a.name.compareto(b.name); } // ordem inversa } private static class ByNumber implements Comparator<Student> { public int compare(student a, Student b) { return a.number - b.number; } } public static final Comparator<Student> BY_NAME = new ByName(); public static final Comparator<Student> BY_NUMBER = new ByNumber(); public int compareto(student s) { return this.name.compareto(s.name); } } LEIC AED Inverno 2007/08 Algoritmos de ordenação 48
Ordenação Síntese da Aula 3 Shell sort Apresentado como uma variante de aceleração do Insertion sort Descrição Implementação Sequências de ordenação Exemplo de aplicação Discussão da eficiência do algoritmo Condicionada pela sequência de ordenação utilizada Vantagens relativamente a outros algoritmos Implementação genérica de algoritmos de ordenação Ordenação por diferentes critérios LEIC AED Inverno 2007/08 Algoritmos de ordenação 49
Capítulo 2 - Algoritmos avançados de ordenação - Quicksort e Mergesort ISEL/LEIC - 2007/2008 2º ano, 1º Semestre * Estes acetatos foram parcialmente adaptados dos acetatos de AED da LEEC do Instituto Superior Técnico Prof. Rui Gustavo Crespo Nuno Leite AED: Algoritmos e Estruturas de Dados Semestre de Inverno 2007/08 http://www.deetc.isel.ipl.pt/programacao/aed/
Algoritmos avançados - Quicksort Provavelmente o algoritmo mais usado inventado em 1960 por Charles Hoare muito estudado e analisado desempenho bem conhecido popular devido à facilidade de implementação e eficiência complexidade N log 2 N, em média, para ordenar N objectos ciclo interno muito simples e conciso LEIC AED Inverno 2007/08 Algoritmos de ordenação 51
Quicksort Quicksort realiza 39% mais comparações do que o Mergesort Contudo, é mais rápido que o Mergesort na prática devido ao menor custo de outras instruções de alta frequência Mas: não é estável quadrático (N 2 ) no pior caso! frágil : qualquer pequeno erro de implementação pode não ser detectado mas levar a ineficiência LEIC AED Inverno 2007/08 Algoritmos de ordenação 52
Quicksort Algoritmo do tipo dividir para conquistar Algoritmo Quicksort: (Opcional): Baralhar (Shuffle) o array Particionar (Partition) o array da seguinte forma: elemento a[i] fica na sua posição final para um determinado i não existem elementos maiores do que a[i] à esquerda de i não existem elementos menores do que a[i] à direita de i Ordenar cada partição recorrentemente LEIC AED Inverno 2007/08 Algoritmos de ordenação 53
Quicksort -Partição LEIC AED Inverno 2007/08 Algoritmos de ordenação 54
Quicksort -Exemplo LEIC AED Inverno 2007/08 Algoritmos de ordenação 55
Estratégia para a partição Escolher a[r] para ser o elemento de partição o que é colocado na posição final Percorrer o array a partir da esquerda até encontrar um elemento maior que ou igual ao elemento de partição (a[r]) Percorrer o array a partir da direita até encontrar um elemento menor que ou igual ao elemento de partição (a[r]) estes dois elementos estão deslocados; trocamos as suas posições! Procedimento continua até nenhum elemento à esquerda de a[r] ser maior que ele, e nenhum elemento à direita de a[r] ser menor que ele termina quando os índices se cruzam completa-se trocando a[r] com o elemento referenciado pelo índice i (que varre da esquerda para a direita) LEIC AED Inverno 2007/08 Algoritmos de ordenação 56
Quicksort - Implementação public class IntArraySort { public static void quicksort(int[] a, int l, int r) { shuffle(a, l, r); // Opcional qsort(a, 0, a.length - 1); } private static void qsort(int[] a, int l, int r) { if (r <= l) return; int m = partition(a, l, r); qsort(a, l, m-1); qsort(a, m+1, r); } LEIC AED Inverno 2007/08 Algoritmos de ordenação 57
Quicksort - Implementação private static int partition(int[] a, int l, int r) { int i = l-1, j = r; int v = a[r]; for (;;) { // Encontra índice de elemento >= pivot à esquerda while (less(a[++i], v)) ; // Encontra índice elemento<=pivot à direita while (less(v, a[--j])) if (j == l) break; if (i >= j) break; exch(a, i, j); } exch(a, i, r); // Troca com pivot de partição return i; // Retorna índice do pivot } LEIC AED Inverno 2007/08 Algoritmos de ordenação 58
Quicksort -Partição Eficiência do processo de ordenação depende de quão bem a partição divide os dados depende por seu turno do elemento de partição será tanto mais equilibrada quanto mais perto este elemento estiver do meio da tabela na sua posição final Processo de partição não é estável qualquer chave pode ser movida para trás de várias outras chaves iguais a si (que ainda não foram examinadas) não é conhecida nenhuma forma simples de implementar uma versão estável de Quicksort baseada em arrays LEIC AED Inverno 2007/08 Algoritmos de ordenação 59
Quicksort Características de desempenho Se não se usar o shuffling, o Quicksort pode ser muito ineficiente em casos patológicos Propriedade: Quicksort usa cerca de N 2 /2 comparações no pior caso No pior caso, o número de comparações usadas por Quicksort satisfaz a recorrência de dividir para conquistar C(n) = C(n-1) + O(n) 1º termo cobre o custo de ordenar um sub-ficheiro (partição degenerada) 2º termo refere-se a examinar cada elemento C(n) = O(n 2 ) LEIC AED Inverno 2007/08 Algoritmos de ordenação 60
Quicksort Análise do pior caso Se o ficheiro já estiver ordenado, todas as partições degeneram e o programa chama-se a si próprio N vezes; o número de comparações é de N + (N-1) + (N-2) + + 2 + 1 = (N + 1)N / 2 (mesma situação se o ficheiro estiver ordenado por ordem inversa) Não apenas o tempo necessário para a execução do algoritmo cresce quadraticamente como o espaço necessário para o processo recorrente é de cerca de N o que é inaceitável para ficheiros grandes LEIC AED Inverno 2007/08 Algoritmos de ordenação 61
Quicksort Características de desempenho Melhor caso: quando cada partição divide o ficheiro de entrada exactamente em metade número de comparações usadas por Quicksort satisfaz a recorrência de dividir para conquistar C(n) = 2C(n/2) + O(n) 1º termo cobre o custo de ordenar os dois sub-ficheiros 2º termo refere-se a examinar cada elemento solução é C(n) = O(n log 2 n) (vimos numa aula anterior) Espaço usado nas chamadas recorrentes é log 2 (n) Demonstração... LEIC AED Inverno 2007/08 Algoritmos de ordenação 62
Quicksort Características de desempenho Propriedade: Quicksort usa cerca de O(2N log 2 N) comparações em média Demonstração: A fórmula de recorrência exacta para o número de comparações utilizado por Quicksort para ordenar N números distintos aleatoriamente posicionados é C(n) = n + 1 + 1 n n k= 1 (C(k 1) + C(n k)) n 2,C(0) = C(1) = termo n+1 cobre o custo de comparar o elemento de partição com os restantes (2 comparações extra: ponteiros cruzam-se) resto vem do facto de que cada elemento tem probabilidade 1/n de ser o elemento de partição após o que ficamos com dois sub-arrays de tamanhos k-1 e n-k 0 LEIC AED Inverno 2007/08 Algoritmos de ordenação 63
Quicksort Características de desempenho Demonstração (cont.) C(n) = n + 1 + 1 n n k= 1 (C(k 1) + C(n k)) = n + 1 + 2 n n k= 1 C(k 1) Multiplicar ambos os lados da equação por n e subtrair a mesma fórmula por n-1 nc(n) -(n -1)C(n -1) = n(n + 1) -(n -1)n + Simplificar para: 2C(n -1) nc(n) = (n + 1)C(n 1) + 2n LEIC AED Inverno 2007/08 Algoritmos de ordenação 64
Quicksort Características de desempenho Dividir ambos os lados da equação por n(n-1) de forma a obter a soma: C(n) = n + 1 = = = = C(n -1) 2 + n n + 1 C(n - 2) 2 2 + + n -1 n n + 1 C(n - 3) 2 2 + + + n - 2 n -1 n M C(2) 3 + n k= 3 2 k+ 1 2 n + 1 Aproximar a resposta exacta por um integral: C(n) = n + 1 k= 1 2 n 2 = n k k= 1 k 2ln Finalmente, obtém-se a solução: n C(n) = 2(n + 1) ln n 1.39n log2 n. LEIC AED Inverno 2007/08 Algoritmos de ordenação 65
Quicksort Características de desempenho Análise assume que os dados estão aleatoriamente ordenados e têm chaves diferentes pode ser lento em situações em que as chaves não são distintas ou que os dados não estão aleatoriamente ordenados (como vimos) Algoritmo pode ser melhorado para reduzir a probabilidade que estes casos sucedam! necessário em ficheiros de grandes dimensões ou se o algoritmo for usado como função genérica numa biblioteca LEIC AED Inverno 2007/08 Algoritmos de ordenação 66
Ordenação Síntese da Aula 4 Algoritmo Quicksort Ideia chave + Motivação Algoritmo que recorre à divisão em instâncias menores divide and conquer Código Exemplo de aplicação Descrição detalhada do mecanismo de partição Análise de eficiência Pior caso Melhor caso Caso médio LEIC AED Inverno 2007/08 Algoritmos de ordenação 67
Quicksort Questões mais relevantes Possível redução de desempenho devido ao uso de recorrência Tempo de execução dependente dos dados de entrada Tempo de execução quadrático no pior caso um problema Espaço/memória necessário no pior caso é linear um problema sério (para ficheiros de grandes dimensões) Problema do espaço está associado ao uso de recorrência: recorrência implica chamada a função e logo a carregar dados na pilha/stack do computador no pior caso todas as partições degeneram e há O(N) níveis de recorrência (em vez de O(log 2 (N)) no melhor caso) pilha cresce até ordem N LEIC AED Inverno 2007/08 Algoritmos de ordenação 68
Quicksort - Espaço necessário (1) Para resolver este problema usamos uma pilha (stack) explícita pilha contém trabalho a ser processado, na forma de sub-arrays a ordenar quando precisamos de um sub-array para processar tiramo-lo da pilha (i.e. fazemos um pop() do stack) por cada partição criamos dois sub-arrays e metemos ambos na pilha (i.e. fazemos dois push() para o stack) substitui a pilha do computador que é usada na implementação recorrente LEIC AED Inverno 2007/08 Algoritmos de ordenação 69
Quicksort - Espaço necessário (2) Conduz a uma versão não recorrente de Quicksort verifica os tamanhos dos dois subarrays e põe o maior deles primeiro na pilha (e o menor depois; logo o menor é retirado e tratado primeiro) ordem de processamento dos subarrays não afecta a correcta operação da função ou o tempo de processamento mas afecta o tamanho da pilha No pior caso espaço extra para a ordenação é logarítmico em N garante que dimensão máxima do stack é O(log 2 N) LEIC AED Inverno 2007/08 Algoritmos de ordenação 70
Quicksort - Versão não-recursiva (1) static void nonrecursivequicksort(int[] a, int l, int r) { shuffle(a, l, r); // Shuffle IntStack s = new IntStackArray(50); s.push(l); s.push(r); while (!s.isempty()) { r = s.pop(); l = s.pop(); if (r <= l) continue; int i = partition(a, l, r); if (i-l > r-i) { s.push(l); s.push(i-1); } s.push(i+1); s.push(r); if (r-i >= i-l) { s.push(l); s.push(i-1); } } } LEIC AED Inverno 2007/08 Algoritmos de ordenação 71
Quicksort - Versão não-recursiva (2) Política de colocar o maior dos subarrays primeiro na pilha garante que cada entrada na pilha não é maior do que metade da que estiver antes dela na pilha pilha apenas ocupa log 2 N no pior caso que ocorre agora quando a partição ocorre sempre no meio da tabela em ficheiros aleatórios o tamanho máximo da pilha é bastante menor Propriedade: se o menor dos dois subarrays é ordenado primeiro a pilha nunca necessita mais do que log 2 N entradas quando Quicksort é usado para ordenar N elementos LEIC AED Inverno 2007/08 Algoritmos de ordenação 72
Quicksort - Versão não-recursiva (3) Demonstração: no pior caso o tamanho da pilha é inferior a T(n) em que T(n) satisfaz a recorrência T(n) = T n/2 = + 1 com T(0) = T(1) 0 que foi já estudada anteriormente LEIC AED Inverno 2007/08 Algoritmos de ordenação 73
Quicksort - Melhoramentos (1) Algoritmo pode ainda ser melhorado com alterações triviais porquê colocar ambos os subarrays na pilha se um deles é de imediato retirado? Teste para r <= l é feito assim que os subarrays saem da pilha seria melhor nunca os lá ter colocado! ordenação de ficheiros/subarrays de pequenas dimensões pode ser efectuada de forma mais eficiente como escolher correctamente o elemento de partição? Como melhorar o desempenho se os dados tiverem um grande número de chaves repetidas? LEIC AED Inverno 2007/08 Algoritmos de ordenação 74
Quicksort - Melhoramentos (2) Pequenos ficheiros/sub-arrays Até mesmo o QuickSort apresenta um overhead excessivo quando é usado com ficheiros de pequena dimensão conveniente utilizar o melhor método possível quando encontra tais ficheiros forma óbvia de obter este comportamento é mudar o teste no início da função recorrente para uma chamada a Insertion sort if (r-l <= M) insertionsort(a, l, r) em que M é um parâmetro a definir na implementação LEIC AED Inverno 2007/08 Algoritmos de ordenação 75
Quicksort - Melhoramentos (3) Pequenos ficheiros/subarrays outra solução é a de simplesmente ignorar ficheiros pequenos (tamanho menor que M) durante a partição: if (r-l <= M) return; neste caso no final teremos um ficheiro que está praticamente todo ordenado Boa solução neste caso é usar insertion sort algoritmo híbrido: bom método em geral! LEIC AED Inverno 2007/08 Algoritmos de ordenação 76
Quicksort - Melhoramentos (4) 1º Método: Utilizar um elemento de partição que com alta probabilidade divida o ficheiro pela metade pode usar-se um elemento aleatoriamente escolhido evita o pior caso (i.e. pior caso tem baixa probabilidade de acontecer) é um exemplo de um algoritmo probabilístico um que usa aleatoriedade para obter bom desempenho com alta probabilidade independentemente dos dados de entrada pode ser demasiado pesado incluir gerador aleatório no algoritmo; existem outros métodos igualmente simples LEIC AED Inverno 2007/08 Algoritmos de ordenação 77
Quicksort - Melhoramentos (5) 2º Método: pode escolher-se alguns (ex: três) elementos do ficheiro e usar a mediana dos três como elemento de partição escolhendo os três elementos da esquerda, meio e direita da tabela podemos incorporar sentinelas na ordenação ordenamos os três elementos, depois trocamos o do meio com a[r-1] e executamos o algoritmo de partição em a[l+1] a[r-1] Este melhoramento chama-se o método da mediana de três median - of - three LEIC AED Inverno 2007/08 Algoritmos de ordenação 78
Mediana de três - Implementação private final static int M = 10; static void quicksort(int[] a, int l, int r) { if (r-l <= M) return; exch(a, (l+r)/2, r-1); lessexch(a, r-1, l); lessexch(a, r, l); lessexch(a, r, r-1); int i = partition(a, l+1, r-1); quicksort(a, l, i-1); quicksort(a, i+1, r); } static void hybridsort(int a[], int l, int r) { quicksort(a, l, r); insertionsort(a, l, r); } LEIC AED Inverno 2007/08 Algoritmos de ordenação 79
Quicksort - Melhoramentos (6) Método da mediana de três melhora Quicksort por duas razões o pior caso é mais improvável de acontecer na prática dois dos três elementos teriam de ser dos maiores ou menores do ficheiro e isto teria de acontecer constantemente a todos os níveis de partição reduz o tempo médio de execução do algoritmo embora apenas por cerca de 5% junto com o método de tratar de pequenos ficheiros pode resultar em ganhos de 20 a 25% É possível pensar em outros melhoramentos mas o acréscimo de eficiência é marginal (ex: porque não fazer a mediana de cinco?) LEIC AED Inverno 2007/08 Algoritmos de ordenação 80
Quicksort - Chaves duplicadas (1) Ficheiros com um grande número de chaves duplicadas são frequentes na prática ex: ordenar população por idade; remover duplicados de uma lista desempenho de Quicksort pode ser substancialmente melhorado se todas as chaves forem iguais Quicksort mesmo assim faz O(N log 2 N) comparações LEIC AED Inverno 2007/08 Algoritmos de ordenação 81
Quicksort - Chaves duplicadas (2) Uma possibilidade é dividir o ficheiro em três partes cada uma para chaves menores, iguais e maiores que o elemento de partição não é trivial de implementar, sobretudo se se impuser que a ordenação deverá ser feita com apenas uma passagem pelos dados Solução simples para este problema é fazer uma partição em três partes manter chaves iguais ao elemento de partição que são encontradas no sub-ficheiro da esquerda do lado esquerdo do ficheiro manter chaves iguais ao elemento de partição que são encontradas no sub-ficheiro da direita do lado direito do ficheiro LEIC AED Inverno 2007/08 Algoritmos de ordenação 82
Quicksort - Chaves duplicadas (3) Quando os ponteiros/índices de pesquisa se cruzam sabemos onde estão os elementos iguais ao de partição e é fácil colocá-los em posição não faz exactamente tudo num só passo mas quase... trabalho extra para chaves duplicadas é proporcional ao número de chaves duplicadas: funciona bem se não houver chaves duplicadas linear quando há um número constante de chaves!! LEIC AED Inverno 2007/08 Algoritmos de ordenação 83
Quicksort - Partição em três static void quicksort(int a[], int l, int r) { if (r <= l) return; int v = a[r], i = l, j = r-1, p = l-1, q = r, k; for (;;) { while (less(a[i], v)) ++i; while (j >= l && less(v, a[j])) ++j; if (i >= j) break; exch(a, i, j); if (equal(a[i], v)) { p++; exch(a, p, i); } if (equal(v, a[j])) { q--; exch(a, q, j); } } exch(a, i, r); j = i-1; i = i+1; for (k = l ; k <= p; k++,j--) exch(a, k, j); for (k = r-1; k >= q; k--,i++) exch(a, k, i); quicksort(a, l, j); quicksort(a, i, r); } LEIC AED Inverno 2007/08 Algoritmos de ordenação 84
Junção versus partição Quicksort é baseado na operação de selecção do elemento de partição (pivot) a partição divide um ficheiro em duas partes quando as duas metades do ficheiro estão ordenadas, o ficheiro está ordenado Operação complementar é de junção (merge) dividir o ficheiro em duas partes para serem ordenados e depois combinar as partes de forma a que o ficheiro total fique ordenado Mergesort Mergesort tem uma propriedade muito interessante: ordenação de um ficheiro de N elementos é feito em tempo proporcional a N log N, independentemente dos dados! Outros algoritmos, p.ex. HeapSort, também têm desempenho desta ordem LEIC AED Inverno 2007/08 Algoritmos de ordenação 85
Ordenação Síntese da Aula 5 Análise do algoritmo Quicksort Discussão relativa à memória utilizada na versão recorrente Alternativa de implementação por pilha e suas vantagens na perspectiva da memória utilizada Código para Quicksort em versão não recorrente Melhoramentos na versão não recorrente Mecanismos de partição alternativos Aleatórios Mediana de três Estratégia de melhoramento em presença de chaves duplicadas Ideia base Código LEIC AED Inverno 2007/08 Algoritmos de ordenação 86