Relatório - Projeto e Análise de Algoritmos Alunos: Felipe Rodopoulos de Oliveira – 10/010060 Heitor Henrique de Paula – 10/0104011 Wallace Bruno – 10/0127380 1. Compilação O trabalho consiste em 5 programas: 1. gera_inteiros.c – programa gerador de sequências de números contidos em um dado intervalo em um dado tipo de ordenação, colocada em um dado arquivo. 2. quicksort-a1.c – recebe um arquivo com uma sequência de números como entrada, ordena-os utilizando o método do quicksort com o pivô sendo o primeiro elemento e escreve em um arquivo de saída com a sequencia de números ordenada. 3. Quicksort-a2.c – recebe um arquivo com uma sequência de números como entrada, ordena-os em um vetor pelo método do Quicksort randomizado com o pivô sendo um elemento aleatório do vetor e escreve em um arquivo de saída a sequência de números ordenada. 4. Quicksort-a3.c - recebe um arquivo com uma sequência de números como entrada, ordena-os em uma lista utilizando o método do Quicksort Randomizado com o pivô sendo um elemento aleatório da mesma e escreve em um arquivo de saída a sequência de números ordenada. 5. radixsort.c – recebe um arquivo com uma sequência de números como entrada, ordenaos em um vetor pelo método do Radixsort e escreve um arquivo de saída com a sequência de números ordenada. Todos os códigos podem ser compilados pela seguinte diretiva: gcc –ansi –Wall ARQUIVO_DESEJADO.c –o NOME_DO_EXECUTAVEL Para rodar o gerador de inteiros e os algoritmos de ordenação, respectivamente: ./gera M N ordenação arquivo-saida.txt ./EXECUTAVEL entrada.txt saída.txt Cada algoritmo de ordenação foi testado para uma diferente massa de dados N e uma diferente forma de ordenação da entrada dada. Aqui os dados serão divididos pelos três diferentes tipos de ordenação: ordenado, invertido e aleatório. As tabelas contém as médias de amostras de 10 execuções para cada massa de dados e tipo de ordenação. 2. Tabelas e Gráficos Aqui serão expostos as tabelas com os tempos de execução para cada algoritmo construído para uma determinada massa de dados N. Seguido, terão os gráficos com os pontos gerados a partir dos dados das tabelas, sendo que os gráficos conterão 4 linhas, uma para cada algoritmo. Importante notar que os gráficos terão a escala de log x log para que os pontos exponenciais correspondam a pontos sequenciais. Em todos os casos a escala do gráfico para os algoritmos de Quicksort Randomizado em lista ficou muito maior que a dos demais gráficos, necessitando que fossem postos em um gráfico a parte para melhor visualização. Importante também notar que em todos os casos, este mesmo algoritmo teve tempos que ultrapassavam 1h. O algoritmo continuou ser rodado mesmo assim, mas só até certo ponto. Para a entrada que isso ocorria, as demais entradas tinham os tempos considerados iguais. 2.1. Sequência Ordenada Quicksort 1–8 1 – 16 1 – 32 1 – 64 1 – 128 1 – 256 1 – 512 1 – 1024 1 – 2048 1 – 4096 1 – 8192 1 – 16384 1 – 32786 1 – 65536 1 – 131072 1 – 262144 1 – 524288 1 – 1048576 1 - 2097152 0.000009 0.000012 0.000011 0.000016 0.000033 0.000074 0.0000249 0.0002031 0.0004487 0.017730 0.062040 0.212823 0.839491 3.345426 13.187542 56.270340 93.505768 142.397339 226.522476 Quicksort Randomizado 0.000015 0.000021 0.000040 0.000063 0.000113 0.000287 0.000539 0.001581 0.003171 0.005879 0.012074 0.020808 0.043233 0.077316 0.147334 0.292949 0.626966 1.224888 2.356692 Radixsort 0.000015 0.000011 0.000015 0.000022 0.000028 0.000048 0.000095 0.000213 0.000494 0.001050 0.002068 0.005270 0.010492 0.021470 0.040495 0.078008 0.144312 0.308577 0.689914 Quicksort Randomizado em Lista 0.000034 0.000040 0.000089 0.000131 0.000394 0.001131 0.008122 0.034302 0.118908 0.480016 1.911062 8.840253 38.936981 177.950226 794.067627 3272.633545 4280.775391 4323.832520 4323.832520 2.2. Sequência Aleatória Quicksort 1–8 1 – 16 1 – 32 1 – 64 1 – 128 1 – 256 1 – 512 1 – 1024 1 – 2048 1 – 4096 1 – 8192 1 – 16384 1 – 32786 1 – 65536 1 – 131072 1 – 262144 1 – 524288 1 – 1048576 1 - 2097152 0.000008 0.000014 0.000012 0.000019 0.000030 0.000052 0.000154 0.000212 0.000588 0.001530 0.001970 0.007818 0.010767 0.023698 0.047460 0.089378 0.176611 0.379993 0.798123 Quicksort Randomizado 0.000022 0.000029 0.000042 0.000078 0.000155 0.000291 0.000545 0.002499 0.002490 0.006690 0.012685 0.025360 0.050920 0.093955 0.175846 0.351205 0.718770 1.425702 2.882137 Radixsort 0.000013 0.000017 0.000016 0.000020 0.000036 0.000067 0.000109 0.000217 0.000442 0.001112 0.002244 0.005505 0.008815 0.017786 0.037856 0.072612 0.138738 0.300189 0.614032 Quicksort Randomizado em Lista 0.000019 0.000046 0.000079 0.000122 0.000339 0.001383 0.008226 0.039823 0.140725 0.572997 2.494688 11.248690 43.130264 185.722610 821.222717 3486.802490 4304.854492 4304.854492 4304.854492 2.3. Sequência Invertida Quicksort 1–8 1 – 16 1 – 32 1 – 64 1 – 128 1 – 256 1 – 512 1 – 1024 1 – 2048 1 – 4096 1 – 8192 1 – 16384 1 – 32786 1 – 65536 1 – 131072 1 – 262144 1 – 524288 1 – 1048576 1 - 2097152 0.000009 0.000012 0.000014 0.000028 0.000078 0.000154 0.001005 0.004154 0.017626 0.060209 0.214840 0.842484 3.434234 13.693277 56.924576 92.959946 122.673454 227.895340 303.842041 Quicksort Randomizado 0.000021 0.000036 0.000050 0.000084 0.000143 0.000306 0.000656 0.001909 0.002935 0.007731 0.014771 0.026522 0.052145 0.106936 0.192669 0.373644 0.720024 1.453796 2.890021 Radixsort 0.000009 0.000010 0.000013 0.000019 0.000027 0.000043 0.000147 0.000175 0.000813 0.001017 0.002336 0.006497 0.008195 0.018423 0.036267 0.071743 0.147676 0.330471 0.653514 Quicksort Randomizado em Lista 0.000034 0.000002 0.000067 0.000158 0.000439 0.001413 0.007360 0.031599 0.138473 0.589626 2.507957 9.939127 44.239773 197.220871 797.749695 3296.634521 4210.873535 4304.855469 4304.855469 3. Questões da Especificação Questão 1 Aqui percebeu-se a seguinte configuração: nos casos onde o vetor estava ordenado ou invertido, o melhor algoritmo foi o quicksort randomizado em vetor. Para o vetor aleatório, o melhor foi o quicksort não-randomizado. Analisando os dados e conhecendo o funcionamento do quicksort pode-se explicar o ocorrido pelo seguinte: o quicksort não-randomizado irá sempre pegar a primeira posição dos seus subvetores e usar como pivô. Ou seja, pegando a primeira posição, o algoritmo estará varrendo o vetor analisando que, no caso do vetor ordenado, todos o elemento serão maiores que o pivô, logo, nada é alterado e teremos que pegar o elemento seguinte do pivô e rodar o vetor novamente. No caso do vetor desordenado, todos os elementos serão menores que o pivô, logo, este será trocado de posição com todos e o elemento seguinte será analisado. A relação de recorrência que estamos caindo aqui é: O que configura uma alta complexidade. No quicksort randomizado, o pivô é escolhido aleatoriamente, logo a cada partição do vetor, estaremos em um novo lugar. Este lugar pivô pode ser um lugar de pior, médio ou melhor caso, mas nunca será sempre o de pior. Logo a relação de recorrência que ocorre para o randomizado é: Caindo numa recorrência média e bem melhor do que a do quicksort não-randomizado. Para explicar o caso do quicksort ser mais eficiente no randomizado, basta averiguar a distribuição de elementos no vetor a qual está em ordem aleatória. Logo, o fator de aleatoriedade já está sendo aplicado ao vetor e por fim, o quicksort irá ter um comportamento de um quicksort, porém, mais comportado, uma vez que está pegando sempre o primeiro elemento do subvetor. Desta forma, mostra-se mais eficiente Questão 2 Em todos os casos, a versão de manipulação em vetores foi bem mais rápida. Analisando as tabelas percebe-se uma grande discrepância entre os tempos de execução, o que mostra que forma de armazenar os inteiros com certeza influi na complexidade do algoritmo. Em listas, para realizar trocas e achar elementos era necessário percorrer a lista, uma tarefa bem custosa para algoritmos que necessitam de uma grande liberdade para percorrer memória. Questão 3 Conhecendo-se o tamanho da palavra (b) e definindo o r, para entradas de dados n, temos a seguinte complexidade para o radix: Esta relação mostra que a quantidade de passos necessárias para a execução do Radixsort depende de r e o trabalho feito em cada passo também depende de r. Visto isso, fez-se o pedido na especificação e obteve-se os seguintes dados que permitiram a construção do Gráfico 4. Obs: como não foi especificado, foi feito apenas para o caso de entradas aleatórias. R 131072 262144 524288 1048576 2097152 1 0.04592300 0.07730900 0.14849500 0.34529001 0.78763103 2 0.03746800 0.08226800 0.16816001 0.32857099 0.74224997 4 0.03937000 0.08151603 0.16309901 0.35172600 0.72794300 8 0.04685300 0.08681601 0.17136200 0.33312398 0.68809998 16 0.04580599 0.07052100 0.15192100 0.30043599 0.62585700 32 0.04253500 0.07697600 0.15448201 0.30538303 0.86256099 Analisando o obtido, foi possível perceber que o R como 16 foi o mais eficiente seguido do 8, 4, 2, 1 e finalmente o R = 32 como o menos eficiente. A explicação para tal é simples: Observando a relação de recorrência do Radix, observamos que cuidado que devemos ter para que a constante 2r não ultrapasse o valor de N, pois caso isso ocorra, N não será mais a variável determinante da relação de recorrência e sim a constante dependente de R. Logo, quando R é 32 e nenhuma das massas de teste é maior que 232, então a relação de recorrência cresce em função desta constante. Desta forma, tem-se o pior caso. Para evitar tal, é necessário garantir que R ≤ lg(n). O melhor caso, que ocorre para R = 16, vem-se do caso limite da relação anterior representada. Como nossa massa de teste vai até o caso de 2097152 = 221 e o maior R que não ultrapassa 21 é o R = 16, temos o caso onde há a maior divisão da palavra de 32 bits por R nos nossos casos de R. Assim, verificase que quando R = 16, o algoritmo executa sobre 2 passos e com o trabalho de (n + 216) em cada um deles, sendo que esta potência de 2 é uma constante que será ignorada por não chegar ao valor de N, fazendo com que tenhamos o melhor caso por ter menos passos dentre de todos os R’s testados. Logo, os resultados obtidos estão dentro do esperado. Questão 4 Analisando o obtido de todos os resultados, foi constatado que o Radixsort foi o mais rápido em todos os casos. Este resultado é compreensível, de modo que o Radixsort é um algoritmo que trabalha livre de comparações e com base em outro algoritmo, o Counting Sort, que tem uma eficiência de θ(n + k). Nos casos testados aqui, tem-se que n = k, então, a complexidade se reduzia a θ(n + n) = θ(2n) = θ(n). Vemos que o Radix trabalhava sob passos, sendo que estes tinham complexidade θ(n + 2r), pois executavam usando o Couting Sort como base. Como 2r foi tratado como constante, então os passos rodavam com complexidade θ(n) e tendo D passos, temos a complexidade de θ(D.n). Comparativamente, o quicksort pode ter no mínimo uma complexidade de θ(n . log(n)), por ser um algoritmo de comparação e por essa razão, acabou por perder em todos os casos. Assim, os resultados aqui no experimento seguiram o esperado.