Programação de Computadores Ordenação de Arranjos
|
|
|
- Washington Duarte da Mota
- 8 Há anos
- Visualizações:
Transcrição
1 Programação de Computadores Ordenação de Arranjos Notação O Alan de Freitas Classes de algoritmos Busca em arranjo Busca sequencial Busca binária On) Olog n)
2 Ordenação de Arranjos Ordenação de Arranjos Como sabemos, buscas binárias em arranjos são mais eficientes que buscas sequenciais. Porém, estas buscas só podem ser feitas em arranjos ordenados É comum em programação a necessidade de se ordenar um arranjo Um motivo razoável para isto pode ser inclusive ordenação dos dados para facilitar sua visualização Pedro João Maria Roberto Manuel José Barbosa Joaquim Alberto Alberto Barbosa Joaquim João José Manuel Maria Pedro Roberto Ordenação de arranjos Conceitos de Ordenação Imagine um catálogo telefônico onde os nomes das pessoas não estão em ordem As estratégias para se ordenar um arranjo são diversas Neste curso, estudaremos algumas destas estratégias O processo de ordenação pode ser para colocar os itens em ordem crescente ou decrescente
3 Conceitos de Ordenação Como no caso da busca binária, os algoritmos são estendidos em situações práticas para ordenar structs por suas chaves Porém, para fins didáticos, os algoritmos de ordenação serão apresentados para ordenação de arranjos de tipos de dados fundamentais Ordenação Estável Uma ordenação estável é aquela que mantém a ordem relativa dos elementos antes da ordenação Ou seja, se dois elementos são iguais, o elemento que aparece antes no arranjo desordenado deve ainda aparecer antes no arranjo ordenado Suponha um arranjo a com os seguintes elementos. Suponha um arranjo a com os seguintes elementos. a a a Este é o mesmo arranjo após a aplicação de um algoritmo de ordenação.
4 Se o resultado final ocorreu da seguinte maneira Note a procedência dos elementos 3 e 4. a a a nosso algoritmo de ordenação teve comportamento estável. a Perceba como mantiveram sua ordem relativa. Já este, poderia ser o resultado de um algoritmo instável Mas então qual a desvantagem de algoritmos instáveis? a a pois, apesar do resultado ser o mesmo, eles não se preocupam em manter a ordem relativa dos elementos. Imagine que queremos ordenar um arranjo com as cartas acima.
5 Estas cartas poderiam ser facilmente representadas pela seguinte estrutura: Utilizando uma ordenação qualquer, podemos ter um resultado como o abaixo: struct Carta int valor; int naipe; Nesta estrutura, valor seria um número de 1 a 13 e naipe seria um número de 1 a 4. As cartas estão ordenadas agora pelos números mas a ordem do naipes é arbitrária. Suponha que agora queremos que as cartas estejam ordenadas de acordo com seu naipe. Para esta segunda ordenação, podemos utilizar um algoritmo estável ou instável. Para isto precisamos de uma segunda ordenação das cartas.
6 Veja o que acontece quando ordenamos as cartas com um algoritmo instável. Veja o que aconteceria se houvéssemos ordenado as cartas com um algoritmo estável. Perdemos a ordem de valor das cartas ao ordenar pelos naipes. Claramente, isto não é o que queríamos. Agora temos as cartas ordenadas por seus naipes mas também mantivemos a ordenação anterior por seus valores. Ordenação Estável Para forçar a estabilidade, adicionamos um índice a cada elemento do arranjo. Alguns métodos mais eficientes não são estáveis Porém, podemos forçar um método não estável a se comportar de forma estável
7 Para forçar a estabilidade, adicionamos um índice a cada elemento do arranjo < 2 True Agora, podemos utilizar o índice como fator de desempate caso os elementos tenham chave igual ou mesmo naipe em nosso exemplo) Ordenação Estável Apesar de possível, forçar um método não estável a se tornar estável diminui a eficiência dos algoritmos e faz com que o algoritmo gaste memória extra para armazenar todos os índices Como há um índice para cada elemento, este custo extra de memória é On) Complexidade de tempo Outro fator a se considerar é a complexidade de tempo dos algoritmos de ordenação A medida mais relevante de tempo é o número de comparações Cn) feitas por um algoritmo para ordenar o vetor Uma medida secundária é o número Mn) de movimentações, ou operações de atribuição Complexidade de tempo Em geral os métodos de ordenação simples: Requerem On 2 ) comparações São apropriados para arranjos pequenos Os métodos eficientes Requerem On log n) comparações Adequados para arranjos grandes
8 Complexidade de memória Ordenação por Seleção Mais um fator relevante é a memória extra que estes algoritmos utilizam para conseguir ordenar o arranjo Os métodos de ordenação que não precisam de memória extra são chamados de métodos in place ou in situ) Este é um dos algoritmos mais simples de ordenação É um algoritmo onde a cada passo de repetição: Se escolhe o menor elemento do arranjo Troca-o com o elemento da primeira posição Repete este procedimento para os outros n-1 elementos Considere um arranjo com os seguintes elementos A partir do primeiro elemento, procuramos o menor elemento do arranjo.
9 Este elemento é trocado com o elemento da primeira posição. Sabemos agora que o arranjo está ordenado até o primeiro elemento A partir do segundo elemento, procuramos o menor elemento do arranjo. Este é colocado na segunda posição e sabemos que os dois primeiros elementos do arranjo já estão ordenados.
10 A partir do terceiro elemento, procuramos o menor elemento e o colocamos na terceira posição. A partir do quarto elemento, procuramos o menor elemento e o colocamos na quarta posição. A partir do quinto elemento, procuramos o menor elemento e o colocamos na quinta posição. Como há apenas um elemento no arranjo não ordenado, já sabemos que ele está em sua posição correta então podemos encerrar o método.
11 Ordenação por Seleção Arranjo Passo Passo Passo Passo Passo Arranjo Ordenação por seleção para um arranjo de inteiros void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; Troca Desordenado Ordenado A função ordena o arranjo a, que contém n elementos void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; Como sabemos que arranjos são passados por referência em C++, não precisamos retornar nada desta função. void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; a a
12 Os índices i, j e min são utilizados para percorrermos o arranjo em um for aninhado e procurar seu menor elemento a cada passo. void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; A variável x deve ser do mesmo tipo dos elementos do arranjos pois será uma variável temporária para fazer as trocas entre elementos. void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; a min i j a min x i j Entramos em um for que, para cada posição do arranjo, colocará em seu lugar o elemento mínimo entre os elementos posteriores. void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; Por exemplo, quando i é 0, supomos que o menor elemento entre as posições 0 e n está na posição 0 void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; a min x i j a a[min] min 0 x i 0 j
13 Percorremos então os elementos entre i+1 e n-1 atualizando a posição do menor elemento. Descobrimos que o menor elemento está em a[6]. void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; Trocamos então os elementos da posição a[0] e a[6]. Precisamos da variável auxiliar x para fazer a troca. void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; a min 6 i 0 a min 6 i 0 a[min] x j 7 a[min] x 1 j 7 Ao fim do primeiro passo, procuramos então o menor elemento entre a[0] e a[n-1] e colocamos no lugar de a[0]. void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; Atualizamos i para 1 e fazemos mais um passo. Sabemos que o vetor até já está ordenado. void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; a min 6 i 0 a min 6 i 1 a[min] x 1 j 7 a[min] x 1 j 7
14 Esta iteração procura o menor elemento entre e a[n-1]. Este elemento a[min] é trocado com. void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; a min 5 i 1 a min 5 i 1 a[min] x 1 j 7 a[min] x 2 j 7 Atualizamos novamente i para 2 e sabemos agora que o arranjo até já está ordenado. Achamos o menor entre e a[n-1]. void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; a min 5 i 2 a min 5 i 2 a[min] x 2 j 7 a[min] x 2 j 7
15 Trocamos com a[min]. Incrementamos i. Sabemos que o arranjo está ordenado até a posição. void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; a min 5 i 2 a min 5 i 3 a[min] x 3 j 7 a[min] x 3 j 7 Encontramos o menor elemento entre e a[n-1]. Trocamos o elemento com o elemento a[min]. void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; a min 3 i 3 a min 3 i 3 a[min] x 3 j 7 a[min] x 5 j 7
16 Na próxima iteração, colocamos o menor em a[4]. Na última iteração, colocamos o menor em a[5]. void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; a min 6 i 4 a min 5 i 5 a[min] x 6 j 7 a[min] x 8 j 7 Veja que não precisamos de uma iteração para o último elemento. Se há apenas um elemento, sabemos que ele seria o menor. void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; Assim, terminamos a função com todos os elementos do arranjo a ordenados void selecaoint a[], int n) int i, j, min; // índices // para cada posição for i = 0; i < n - 1; i++) // procuramos o menor entre i+1 e n e colocamos em i min = i; //mínimo é o i for j = i + 1; j < n; j++) if < a[min]) min = j; //mínimo é o j // troca com a[min] x = a[min]; a[min] = ; = x; a min 5 i 5 a min 5 i 5 a[min] x 8 j 7 a[min] x 8 j 7
17 Análise No laço interno, sempre que procuramos o menor elemento entre i e n, fazemos n-i comparações No laço mais externo, estas n-i comparações são feitas n-1 vezes Somatório: nx 1 n i=1 i = n2 2 = On2 ) Assim, no total, o algoritmo faz On 2 ) comparações Análise Em relação ao número de atribuições, cada troca envolve 3 atribuições. Como são feitas n-1 trocas, temos 3n-1) = On) atribuições de movimentação. Além destas, temos a atualização da posição do menor Esta ocorre em média n log n vezes Com estas, o custo total de atribuições é On log n) Análise - Vantagens Em relação ao custo de movimentação, temos um custo linear On) É então um bom algoritmo onde estas movimentações por algum motivo custem muito O algoritmo é interessante para arranjos pequenos Análise - Desvantagens Se o arranjo já estiver ordenado, isto não ajuda o algoritmo, pois o custo de comparações continua On 2 ) Dizemos que o método não é adaptável. O algoritmo não é estável pois a troca entre elementos pode destruir a ordem relativa destes
18 de perda de estabilidade Suponha um passo onde trocaremos o elemento 6) com o elemento 1). de perda de estabilidade Ao fazer esta troca, o elemento 6) perde sua ordem relativa entre qualquer elemento entre a[i+1] e a[j-1]. a a Neste caso, o elemento perdeu sua ordem relativa com o elemento a[3], que tem o mesmo valor. Ordenação por Inserção Este é o algoritmo preferido dos jogadores de cartas É um algoritmo onde a cada passo de repetição: Temos um arranjo ordenado até um certo ponto Pegamos o próximo elemento Colocamos este elemento na posição correta entre o primeiro e ele mesmo Considere um arranjo com os seguintes elementos
19 Ordenado Desordenado Sabemos que o arranjo até o segundo elemento já está ordenado pois um arranjo de apenas um elemento está sempre ordenado. Colocamos então o próximo elemento na posição correta do arranjo ordenado. Sabemos agora então que os dois primeiros elementos estão ordenados. O próximo elemento deverá ser colocado em sua posição correta.
20 Sabemos agora que os três primeiros elementos estão ordenados. O próximo elemento deverá ser colocado em sua posição correta. Sabemos agora que os quatro primeiros elementos estão ordenados. O próximo elemento deverá ser colocado em sua posição correta.
21 Sabemos agora que os cinco primeiros elementos estão ordenados. O próximo elemento deverá ser colocado em sua posição correta. Ordenação por Inserção Arranjo Passo Passo Passo Passo Passo Arranjo Sabemos agora que todos os elementos estão ordenados. Movimentação Desordenado Ordenado
22 Ordenação por inserção para um arranjo de inteiros A função ordena o arranjo a, que contém n elementos void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a Como sabemos que arranjos são passados por referência em C++, não precisamos retornar nada desta função. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; Os índices i e j são utilizados para percorrermos o arranjo em um for aninhado e colocar cada elemento em sua posição no arranjo ordenado. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a a i j
23 A variável x deve ser do mesmo tipo dos elementos do arranjo pois será uma variável temporária para fazer as trocas entre elementos. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; Entramos em um for que, para cada posição i do arranjo, colocará o elemento em seu lugar correto do arranjo ordenado de a[0] até a[i-1] void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a x i a x i j j Inicialmente, quando i é 1, sabemos que o arranjo até a[0] já está ordenado. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; A variável x recebe um cópia do elemento. Esta cópia será colocada em sua posição correta no arranjo ordenado. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a a x i 1 x 3 i 1 j j
24 O índice j, recebe i-1. É a partir de a[i-1] que procuraremos a posição correta de. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; No while aninhado, deslocamos os elementos anteriores a. Isso até encontrarmos a posição de x ou até que todos os elementos tenham sido deslocados. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a a x 3 i 1 x 3 i 1 j 0 j 0 Na primeira iteração temos que a[0] é deslocado. O valor de ainda está salvo em x. Saímos deste loop pois todos os elementos foram deslocados. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a a x 3 i 1 x 3 i 1 j -1 j -1
25 O elemento x, que saiu de, é então inserido em sua posição correta a[j+1]. Incrementamos i para 2, e sabemos agora que o arranjo até a[1] já está ordenado. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a a x 3 i 1 x 3 i 2 j -1 j -1 Repetindo o processo, x guarda uma cópia do elemento em questão A partir de a[i-1] agora ) procuraremos a posição correta para o elemento. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a a x 8 i 2 x 8 i 2 j -1 j 1
26 Como o elemento guardado x não é menor que o elemento, já achamos a posição correta para ele. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; O elemento x é colocado na posição a[j+1], que já era sua posição original. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a a x 8 i 2 x 8 i 2 j 1 j 1 Sabemos que o vetor até a[2] está ordenado e incrementamos i para 3 A variável x recebe uma cópia de void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a a x 8 i 3 x 5 i 3 j 1 j 1
27 O índice j marca que procuraremos a posição a partir de a[i-1] Enquanto não achamos a posição do elemento ou não deslocamos todos os elementos void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a a x 5 i 3 x 5 i 3 j 2 j 2 Deslocamos o elemento em uma posição Decrementamos j para testar o próximo elemento void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a a x 5 i 3 x 5 i 3 j 2 j 1
28 Ainda não achamos a posição correta de x nem deslocamos todos os elementos. Deslocamos o elemento em uma posição. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a a x 5 i 3 x 5 i 3 j 1 j 1 Decrementamos j para testar a próxima posição. Como o elemento x não é menor que, encontramos sua posição correta. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a a x 5 i 3 x 5 i 3 j 0 j 0
29 O elemento guardado em x é então colocado em sua posição correta. Sabemos que o arranjo está ordenado até a[3] e incrementamos i para 4. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a a x 5 i 3 x 5 i 4 j 0 j 0 Como vimos neste trecho de código, elementos maiores que serão deslocados no arranjo ordenado e será inserido em sua posição correta. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; Sabemos que o arranjo está ordenado até a[4] e incrementamos i para 5. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a a x 9 i 4 x 9 i 5 j 3 j 3
30 Elementos maiores que serão deslocados no arranjo ordenado e será inserido em sua posição correta. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; Sabemos que o arranjo está ordenado até a[5] e incrementamos i para 6. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a a x 2 i 5 x 2 i 6 j -1 j -1 Elementos maiores que serão deslocados no arranjo ordenado e será inserido em sua posição correta. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; Sabemos agora que todo o arranjo está ordenado e o critério de parada é então atingido quando i chega a 7, encerrando a função. void insercaoint a[], int n) int i, j; // Índices // para cada posição a partir de i = 1 for i = 1; i < n; i++) // coloca o elemento na posição correta entre 0 e i - 1 x = ; j = i - 1; while x < && j >= 0) a[j+1] = ; j--; a[j+1] = x; a a x 1 i 6 x 1 i 7 j -1 j -1
31 Análise Há duas condições que podem terminar o laço mais interno do algoritmo: A posição do elemento já ter sido encontrada Todas as posições já terem sido testadas Análise Em relação aos casos possíveis para o laço interno, temos: Melhor caso: quando é feita apenas uma comparação pois o elemento já está sempre na posição correta O1) Pior caso: i comparações são feitas até testarmos todas as posições i = Oi) Caso médio - probabilidade 1/i em cada caso: 1 ix i i) =1 j = i +1 = Oi) i 2 j=1 Análise Como temos n-1 iterações do laço mais externo, temos: Melhor caso = Pior caso n = Caso médio n+1 2 = nx i=2 nx i=2 nx 1=n 1=On) i=2 i = n + n2 2 2 i +1 2 = 3n + n2 4 4 = On 2 ) = On 2 ) Análise No algoritmo de ordenação por inserção, o número de movimentações é proporcional ao número de comparações. Isso porque os elementos são movimentados até que uma comparação indique que a posição correta foi encontrada
32 Análise Assim, em relação ao número de movimentações, temos também: Melhor caso: On) Pior caso: On 2 ) Caso médio: On 2 ) Análise - Vantagens O algoritmo tem um melhor caso que ocorre quando os elementos já estão ordenados Uma ordenação prévia é aproveitada pelo algoritmo. Dizemos que o método é adaptável. É um bom método para se adicionar alguns poucos elementos a um arranjo ordenado, pois terá um custo baixo em um arranjo "quase ordenado" Este algoritmo de ordenação é estável, pois a ordem relativa dos elementos é mantida no deslocamento dos elementos. Análise - Desvantagens Apesar de pouco provável na maior parte das aplicações práticas, o custo máximo do algoritmo ocorre quando os elementos estão em ordem reversa Se o número de movimentações é o fator mais relevante, ele se torna ineficiente em relação à ordenação por seleção Seleção X Inserção Algoritmo Seleção Inserção Pior caso On 2 ) On 2 ) Melhor caso On 2 ) On) Caso médio On 2 ) On 2 ) Estabilidade Não Sim Adaptabilidade Não Sim Movimentações On) Mesmo que comparações
33 Testes reais Como os dois algoritmos são On 2 ) no caso médio, podemos fazer comparações reais de tempo para ver a performance em segundos dos algoritmos. Para as comparações apresentadas a seguir, foram utilizadas versões mais eficientes dos algoritmos. Tempo segundos) 2 1 x 10 4 Seleção Inserção Testes reais Arranjos em ordem inicial aleatória Tamanho do arranjo Tempo segundos) Seleção Inserção Tamanho do arranjo x 10 4 Testes reais Em proporção, a seleção chega a ser quase 18 vezes mais lenta que a inserção. Testes reais Arranjos em ordem inicial decrescente pior caso da inserção) 6 Seleção Inserção Seleção Inserção x 10 4 Seleção Inserção Seleção Inserção Tempo em proporção à ordenação por seleção) Tempo em proporção à ordenação por seleção) Tempo segundos) 2 1 Tempo segundos) Tamanho do arranjo Tamanho do arranjo x Tamanho do arranjo Tamanho do arranjo x 10 4
34 Testes reais Em proporção, a seleção chega a ser quase 9 vezes mais lenta que a inserção, mesmo em seu pior caso. Testes reais Arranjos em ordem inicial crescente melhor caso da inserção) 8 7 Seleção Inserção 9 8 Seleção Inserção x 10 4 Seleção Inserção Seleção Inserção Tempo em proporção à ordenação por seleção) Tempo em proporção à ordenação por seleção) Tempo segundos) 2 1 Tempo segundos) Tamanho do arranjo Tamanho do arranjo x Tamanho do arranjo Tamanho do arranjo x 10 4 Testes reais Em proporção, a seleção chega a ser quase 2500 vezes mais lenta que a inserção, em seu melhor caso. Testes reais Como o melhor caso da inserção é On), em comparação a On 2 ) seleção, isto era esperado. 30 Seleção Inserção 2500 Seleção Inserção 30 Seleção Inserção 2500 Seleção Inserção Tempo em proporção à ordenação por seleção) Tempo em proporção à ordenação por seleção) Tempo em proporção à ordenação por seleção) Tempo em proporção à ordenação por seleção) Tamanho do arranjo Tamanho do arranjo x Tamanho do arranjo Tamanho do arranjo x 10 4
35 Exercício Criar um arranjo com 10 elementos aleatórios entre 1 e 50 Desenhar o arranjo várias vezes demonstrando os passos de uma ordenação por: Seleção Inserção Colocar um círculo nos elementos movimentados. Colocar um traço entre os elementos ordenados e os desordenados. Ordenação por Seleção Arranjo Passo Passo Passo Passo Passo Arranjo Troca Desordenado Ordenado Ordenação por Inserção Arranjo Passo Passo Passo Passo Passo Arranjo Movimentação Desordenado Ordenado Merge sort O merge sort ordenação por intercalação) é um método que utiliza uma técnica de dividir para conquistar É um algoritmo onde a cada passo: Divide o arranjo em 2 arranjos menores até que tenhamos vários arranjos de tamanho 1 Então, iniciamos uma repetição onde: 2 arranjos são fundidos em um arranjo maior ordenado até que tenhamos o arranjo original ordenado
36 Considere um arranjo com os seguintes elementos Já inicialmente, temos um arranjo de 8 elementos Dividimos este arranjo em dois arranjos com a metade do tamanho Redividimos cada arranjo na metade
37 Este processo continua até que tenhamos arranjos de 1 elemento cada Sabemos que cada um destes arranjos de 1 elemento é um arranjo ordenado. Iniciaremos agora a fase de intercalação. Os dois primeiros arranjos ordenados são intercalados em um novo arranjo ordenado. Fazemos isto com todos os arranjos menores.
38 Fazemos isto com todos os arranjos menores. Fazemos isto com todos os arranjos menores. O interessante é que o custo de se intercalar dois arranjos ordenados em um arranjo ordenado é menor que o custo de se colocar dois arranjos desordenados em um arranjo ordenado Fazemos a intercalação para os arranjos de tamanho 2
39 Fazemos a intercalação para os arranjos de tamanho 2 Na última intercalação, temos todo o arranjo original ordenado Esta é a etapa de conquista. Temos como resultado o arranjo original ordenado Esta é a etapa de divisão. Temos como resultado n arranjos ordenados de tamanho Desordenado Ordenado Desordenado Ordenado
40 Divisão Etapa de intercalação Intercalação Desordenado Ordenado Para a implementação de maneira mais usual, a etapa de intercalação de dois arranjos requer um arranjo auxiliar, onde serão colocados os itens. Etapa de intercalação Etapa de intercalação Os elementos são intercalados em um arranjo auxiliar e então são transferidos de volta para o arranjo de onde vieram: o arranjo sendo ordenado. Assim, para intercalar n elementos, precisamos de uma memória extra On)
41 O algoritmo de intercalação é organizado em algumas etapas Primeiramente, um arranjo temporário é criado e os índices são inicializados void intercalaint a[], int n) int *tmp = new int[n]; // Arranjo temporário para intercalação int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else // os outros elementos do primeiro arranjo vão para o arranjo temporário void intercalaint a[], int n) int *tmp = new int[n]; // Arranjo temporário para intercalação int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else // os outros elementos do primeiro arranjo vão para o arranjo temporário void intercalaint a[], int n) int *tmp = new int[n]; // Arranjo temporário para intercalação int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice arranjo i marca itens intercalados temporário do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else // os outros elementos do primeiro arranjo vão para o arranjo temporário while i < meio) tmp[k] = ; // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] Logo após, os elementos de a são intercalados neste // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else // os outros elementos do primeiro arranjo vão para o arranjo temporário while i < meio) tmp[k] = ; // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) = tmp[i]; // o arranjo temporário pode então ser desalocado da memória delete [] tmp; Então, os elementos intercalados no arranjo temporário são transferidos de volta para o arranjo a
42 void intercalaint a[], int n) int *tmp = new int[n]; // Arranjo temporário para intercalação int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; A função intercalará os elementos de a[] // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else // os outros elementos do primeiro arranjo vão para o arranjo temporário a while i < meio) tmp[k] = ; // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] n 8 void intercalaint a[], int n) int *tmp = new int[n]; // Arranjo temporário para intercalação int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; Os elementos de a[0] até a[3] serão intercalados com os elementos de a[4] a a[7] // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else // os outros elementos do primeiro arranjo vão para o arranjo temporário a while i < meio) tmp[k] = ; // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] n 8 void intercalaint a[], int n) int *tmp = new int[n]; // Arranjo temporário para intercalação int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; Um arranjo temporário é criado para guardar os elementos intercalados // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; a // se foi o índice j que chegou ao fim de seu arranjo primeiro else // os outros elementos do primeiro arranjo vão para o arranjo temporário while tmpi < meio) tmp[k] = ; // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] n 8 void intercalaint a[], int n) int *tmp = new int[n]; // Arranjo temporário para intercalação int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; Um índice meio marca onde está a metade do arranjo. Ou onde começa o segundo arranjo a ser intercalado. // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; a // se foi o índice j que chegou ao fim de seu arranjo primeiro else a[meio] // os outros elementos do primeiro arranjo vão para o arranjo temporário while tmpi < meio) tmp[k] = ; // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] n 8 meio 4
43 void intercalaint a[], int n) int *tmp = new int[n]; // Arranjo temporário para intercalação int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; Índices i, j e k marcam onde intercalaremos no primeiro, segundo arranjo e no arranjo temporário // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; a i 0 // se foi o índice j que chegou ao fim de seu arranjo primeiro else a[meio] j 4 // os outros elementos do primeiro arranjo vão para o arranjo temporário while tmpi < meio) tmp[k] = ; k 0 tmp[k] // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] n 8 meio 4 int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; Se i é menor que meio e j é menor que n, não atingimos o fim de nenhum arranjo sendo intercalados // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else a // os outros 3 elementos 4 5 do 7primeiro 2 arranjo 6 8 vão 9para o arranjo i temporário 0 while i < meio) tmp[k] 0 = ; n 8 a[meio] a[n] j 4 tmp // neste ponto, o arranjo temporário tem todos os elementos intercalados meio 4 // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) k 0 = tmp[k] tmp[i]; // o arranjo temporário pode então ser desalocado da memória int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; Como não atingimos o final de um dos arranjos, comparamos com // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else a // os outros 3 elementos 4 5 do 7primeiro 2 arranjo 6 8 vão 9para o arranjo i temporário 0 while i < meio) tmp[k] 0 = ; n 8 a[meio] a[n] j 4 tmp // neste ponto, o arranjo temporário tem todos os elementos intercalados meio 4 // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) k 0 = tmp[k] tmp[i]; // o arranjo temporário pode então ser desalocado da memória int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; O menor é colocado no arranjo temporário // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else a // os outros 3 elementos 4 5 do 7primeiro 2 arranjo 6 8 vão 9para o arranjo i temporário 0 while i < meio) tmp[k] 0 = ; a[meio] a[n] j 4 tmp 2 // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) k 0 = tmp[k] tmp[i]; // o arranjo temporário pode então ser desalocado da memória n 8 meio 4
44 int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; Com o elemento copiado, incrementamos o índice j, sendo usado no segundo arranjo // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else a // os outros 3 elementos 4 5 do 7primeiro 2 arranjo 6 8 vão 9para o arranjo i temporário 0 while i < meio) tmp[k] 0 = ; n 8 a[meio] a[n] j 5 tmp 2 // neste ponto, o arranjo temporário tem todos os elementos intercalados meio 4 // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) k 0 = tmp[k] tmp[i]; // o arranjo temporário pode então ser desalocado da memória int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; Sempre incrementamos também o índice k, usado no arranjo temporário // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else a // os outros 3 elementos 4 5 do 7primeiro 2 arranjo 6 8 vão 9para o arranjo i temporário 0 while i < meio) tmp[k] 0 = ; n 8 a[meio] a[n] j 5 tmp 2 // neste ponto, o arranjo temporário tem todos os elementos intercalados meio 4 // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) k 1 = tmp[i]; tmp[k] // o arranjo temporário pode então ser desalocado da memória int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; A estrutura de repetição continua até que i atinja meio ou j atinja n // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else a // os outros 3 elementos 4 5 do 7primeiro 2 arranjo 6 8 vão 9para o arranjo i temporário 0 while i < meio) tmp[k] 0 = ; a[meio] a[n] j 5 tmp 2 // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) k 1 = tmp[i]; tmp[k] // o arranjo temporário pode então ser desalocado da memória n 8 meio 4 int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; < é true Então é copiado para tmp[k] // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else a // os outros 3 elementos 4 5 do 7primeiro 2 arranjo 6 8 vão 9para o arranjo i temporário 1 while i < meio) tmp[k] 0 = ; a[meio] a[n] j 5 tmp 2 3 // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) k 2 = tmp[i]; tmp[k] // o arranjo temporário pode então ser desalocado da memória n 8 meio 4
45 int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; < é true Então é copiado para tmp[k] // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else a // os outros 3 elementos 4 5 do 7primeiro 2 arranjo 6 8 vão 9para o arranjo i temporário 2 while i < meio) tmp[k] 0 = ; a[meio] a[n] j 5 tmp // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) k 3 = tmp[i]; tmp[k] // o arranjo temporário pode então ser desalocado da memória n 8 meio 4 int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; < é true Então é copiado para tmp[k] // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else a // os outros 3 elementos 4 5 do 7primeiro 2 arranjo 6 8 vão 9para o arranjo i temporário 3 while i < meio) tmp[k] 0 = ; a[meio] a[n] j 5 tmp // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) k 4 = tmp[i]; tmp[k] // o arranjo temporário pode então ser desalocado da memória n 8 meio 4 int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; < é false Então é copiado para tmp[k] // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else a // os outros 3 elementos 4 5 do 7primeiro 2 arranjo 6 8 vão 9para o arranjo i temporário 3 while i < meio) tmp[k] 0 = ; a[meio] a[n] j 6 tmp // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) k 5 = tmp[i]; tmp[k] // o arranjo temporário pode então ser desalocado da memória n 8 meio 4 int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; < é true Então é copiado para tmp[k] // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else a // os outros 3 elementos 4 5 do 7primeiro 2 arranjo 6 8 vão 9para o arranjo i temporário 4 while i < meio) tmp[k] 0 = ; a[meio] a[n] j 6 tmp // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) k 6 = tmp[i]; tmp[k] // o arranjo temporário pode então ser desalocado da memória n 8 meio 4
46 int meio = n / 2; // Índice que marca o meio do arranjo int i, j, k; // Índices para a estrutura de repetição i = 0; // Índice i marca itens intercalados do primeiro arranjo j = meio; // Índice j marca itens intercalados do segundo arranjo k = 0; // Índice k marca itens já intercalados no arranjo temporário // Enquanto os índices i e j não tenham chegado ao fim de seus arranjos while i < meio && j < n) // colocamos o menor item entre e no arranjo temporário if < ) tmp[k] = ; else tmp[k] = ; Como i tem os mesmo valor de meio, o primeiro arranjo está intercalado e a repetição termina. // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else a // os outros 3 elementos 4 5 do 7primeiro 2 arranjo 6 8 vão 9para o arranjo i temporário 4 while i < meio) tmp[k] 0 = ; n 8 a[meio] a[n] j 6 tmp // neste ponto, o arranjo temporário tem todos os elementos intercalados meio 4 // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) k 6 = tmp[i]; tmp[k] // o arranjo temporário pode então ser desalocado da memória tmp[k] = ; // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else // os outros elementos do primeiro arranjo vão para o arranjo temporário while i < meio) tmp[k] = ; Queremos agora saber se // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) = tmp[i]; a i 4 // o arranjo temporário pode então ser desalocado da memória delete [] tmp; a[meio] tmp A repetição anterior terminou porque i == meio ou porque j == n? tmp[k] a[n] j 6 k 6 n 8 meio 4 tmp[k] = ; // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else // os outros elementos do primeiro arranjo vão para o arranjo temporário while i < meio) tmp[k] = ; // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) = tmp[i]; a i 4 // o arranjo temporário pode então ser desalocado da memória delete [] tmp; a[meio] Neste caso, i==meio. a[n] j 6 true n 8 tmp[k] = ; // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else // os outros elementos do primeiro arranjo vão para o arranjo temporário while i < meio) tmp[k] = ; true Neste caso, i==meio. Isto quer dizer que os outros elementos do segundo arranjo ainda precisam ser copiados. // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) = tmp[i]; a i 4 // o arranjo temporário pode então ser desalocado da memória delete [] tmp; a[meio] a[n] j 6 n 8 tmp meio 4 tmp meio k k 6 tmp[k] tmp[k]
47 tmp[k] = ; // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else // os outros elementos do primeiro arranjo vão para o arranjo temporário while i < meio) tmp[k] = ; Enquanto j for menor que n // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) = tmp[i]; a i 4 // o arranjo temporário pode então ser desalocado da memória delete [] tmp; a[meio] Copiamos para a[k] e incrementamos j e k a[n] j 7 n 8 tmp[k] = ; // se o índice i chegou ao fim de seu arranjo primeiro if i == meio) // os outros elementos do segundo arranjo vão para o arranjo temporário while j < n) tmp[k] = ; // se foi o índice j que chegou ao fim de seu arranjo primeiro else // os outros elementos do primeiro arranjo vão para o arranjo temporário while i < meio) tmp[k] = ; Enquanto j for menor que n // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) = tmp[i]; a i 4 // o arranjo temporário pode então ser desalocado da memória delete [] tmp; a[meio] Copiamos para a[k] e incrementamos j e k a[n] j 8 n 8 tmp meio 4 tmp meio k k 8 tmp[k] tmp[k] // se foi o índice j que chegou ao fim de seu arranjo primeiro else // os outros elementos do primeiro arranjo vão para o arranjo temporário while i < meio) tmp[k] = ; // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) = tmp[i]; // o arranjo temporário pode então ser desalocado da memória delete [] tmp; Ao chegar neste ponto, o vetor temp tem todos os elementos intercalados // se foi o índice j que chegou ao fim de seu arranjo primeiro else // os outros elementos do primeiro arranjo vão para o arranjo temporário while i < meio) tmp[k] = ; // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) = tmp[i]; // o arranjo temporário pode então ser desalocado da memória delete [] tmp; Copiamos então todos os elementos de volta para o arranjo a a i 4 a i j 8 n j 8 n 8 tmp meio 4 tmp meio k k 8 tmp[i] tmp[i] tmp[i] tmp[i] tmp[i] tmp[i] tmp[i] tmp[i]
48 // se foi o índice j que chegou ao fim de seu arranjo primeiro else // os outros elementos do primeiro arranjo vão para o arranjo temporário while i < meio) tmp[k] = ; // neste ponto, o arranjo temporário tem todos os elementos intercalados // estes elementos são copiados de volta para o arranjo int a[] for i = 0; i < n; ++i) = tmp[i]; // o arranjo temporário pode então ser desalocado da memória delete [] tmp; Agora, com os elementos já intercalados em a[], podemos deslocar o arranjo temporário e finalizar a função. a Arranjo ordenado i 8 j 8 k 8 n 8 meio 4 Algoritmo de Ordenação A utilidade de funções é justamente quebrar problemas em problemas menores Usando a função apresentada de intercalação como uma função auxiliar, podemos definir a função de ordenação A função de ordenação pode ser facilmente expressa em termos recursivos Utilizando a função de intercalação como uma função auxiliar, esta função recursiva ordena um arranjo com o método Merge Sort 1) Dividimos o arranjo ao meio void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n);
49 2) Usamos o próprio método recursivamente para ordenar o arranjo até a metade 3) Usamos o próprio método recursivamente para ordenar o arranjo da metade até o final void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); 4) Intercalamos os dois arranjos ordenados com a função já declarada. O primeiro de a[0] até a[meio-1] e o outro de a[meio] até a[n-1] Como vimos, funções recursivas precisam de uma caso base para funcionar. Caso contrário o algoritmo entraria em uma recursão infinita. void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); Neste algoritmo o caso base é quando o arranjo tem apenas um elemento, pois sabemos que um arranjo de apenas um elemento já está ordenado.
50 A função ordena o arranjo a, que contém n elementos. A função recebe o endereço de a ou de a[0]), e ordena, 8 elementos a partir dele A variável meio, dividirá o arranjo em 2 void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 8) mergesort&a[0], 8) a n 8 a n 8 meio Como o tamanho do arranjo for maior que 1, prosseguiremos com o processo de divisão. Atualizamos a posição do meio void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 8) Caso contrário, se o tamanho do arranjo fosse 1, sabemos que este já seria, por definição, um arranjo ordenado. void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 8) n 8 n 8 a a meio meio
51 Atualizamos a posição do meio void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 8) Neste passo, pedimos o algoritmo para ordenar o arranjo que se inicia na mesma posição de memória de a[] e tem tamanho meio, ou seja, 4 void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 8) Lembre-se que um arranjo nada mais é que um endereço onde se inicia uma série de elementos alocados na memória. a n 8 a n 8 meio 4 meio 4 A função mergesort anterior é guardada na pilha de chamadas de função, e a nova chamada de mergesort é executada. Como n é igual a 4, pedimos a esta chamada da função para considerar apenas 4 elementos a partir do endereço de a void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 4) mergesort&a[0], 8) meio 4 n 8 mergesort&a[0], 4) mergesort&a[0], 8) meio 4 n 8 n 4 n 4 a a
52 Criamos a variável que representará o meio e damos o seu valor A chamada atual da função vai para a pilha e fica em espera enquanto a nova função mergesorta, 2) é executada void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 4) mergesort&a[0], 8) meio 4 n 8 mergesort&a[0], 4) mergesort&a[0], 8) meio 4 n 8 n 4 n 4 a a meio 2 meio 2 O arranjo de tamanho 2 será dividido em dois arranjos de tamanho 1 Esta função também entrará na pilha e chamaremos a função para um arranjo de apenas um 1 elemento void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 4) meio 2 n 4 void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 4) meio 2 n 4 mergesort&a[0], 2) mergesort&a[0], 8) meio 4 n 8 mergesort&a[0], 2) mergesort&a[0], 8) meio 4 n 8 n 2 n 2 a a meio 1 meio 1
53 Como estamos considerando um arranjo de apenas um elemento, porém, o caso base foi atingido e a função retorna sem fazer nada. A função retornará sem fazer nada e a função chamadora sai da pilha e volta a ser executada. void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 2) meio 1 n 2 mergesort&a[0], 4) meio 2 n 4 void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 2) meio 1 n 2 mergesort&a[0], 4) meio 2 n 4 mergesort&a[0], 1) mergesort&a[0], 8) meio 4 n 8 mergesort&a[0], 1) mergesort&a[0], 8) meio 4 n 8 n 1 n 1 a a meio meio Ao retornar, esta função mergesort volta de onde parou. Neste ponto, ele chamará a função para 1 elemento a partir de &a[1]. Como apenas um elemento está sendo considerado, esta função entrará no caso base e retornará. void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 4) meio 2 n 4 void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 2) meio 1 n 2 mergesort&a[0], 4) meio 2 n 4 mergesort&a[0], 2) mergesort&a[0], 8) meio 4 n 8 mergesort&a[1],1) mergesort&a[0], 8) meio 4 n 8 n 2 n 2 a a meio 1 meio 1
54 A função mergesort do topo da pilha volta então a ser executada e seu próximo passo é de intercalação. Como vimos, a função de intercalação intercala duas metades ordenadas de um arranjo. void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 4) meio 2 n 4 void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 4) meio 2 n 4 mergesort&a[0], 2) mergesort&a[0], 8) meio 4 n 8 mergesort&a[0], 2) mergesort&a[0], 8) meio 4 n 8 n 2 n 2 a a meio 1 meio 1 Com as duas metades intercaladas. Esta execução da função mergesort termina e a execução do topo da pilha volta a ser executada. O próximo passo desta função é usar o próprio mergesort para ordenar o arranjo que começa em &a[2] e tem 2 elementos void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 4) meio 2 n 4 void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 2) mergesort&a[0], 8) meio 4 n 8 mergesort&a[0], 4) mergesort&a[0], 8) meio 4 n 8 n 2 n 4 a a meio 1 meio 2
55 Esta função ordenará precisará dividir o arranjo em problemas menores novamente. Esta chamada considerará apenas um arranjo iniciando em &a[2] com um elemento e retornará pelo caso base void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 4) meio 2 n 4 void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 4) meio 2 n 4 mergesort&a[2], 2) mergesort&a[0], 8) meio 4 n 8 mergesort&a[2], 2) mergesort&a[0], 8) meio 4 n 8 n 2 n 2 a a meio 1 meio 1 Esta chamada considerará apenas um arranjo iniciando em &a[3] com um elemento e retornará pelo caso base Esta chamada intercala os elementos do arranjo que se inicia em &a[2] e tem 2 elementos. void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 4) meio 2 n 4 void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 4) meio 2 n 4 mergesort&a[2], 2) mergesort&a[0], 8) meio 4 n 8 mergesort&a[2], 2) mergesort&a[0], 8) meio 4 n 8 n 2 n 2 a a meio 1 meio 1
56 Com o fim da intercalação, a função do topo da pilha volta a ser executada. A função mergesort&a[0], 4) também está na fase de intercalação. Como já sabemos, a função de intercalação é aplicado nas duas metades ordenadas do arranjo. void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 4) mergesort&a[0], 8) meio 4 n 8 mergesort&a[0], 4) mergesort&a[0], 8) meio 4 n 8 n 4 n 4 a a meio 2 meio 2 Estas duas metades são intercaladas em um arranjo com todos os elementos ordenados. Com o fim desta intercalação, a primeira metade do arranjo é agora um arranjo ordenado e esta função é retornada, retirando a primeira função da pilha. void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 4) mergesort&a[0], 8) meio 4 n 8 mergesort&a[0], 4) mergesort&a[0], 8) meio 4 n 8 n 4 n 4 a a meio 2 meio 2
57 A função original era a que considerava todos os elementos do arranjo. Como vimos, esta função já conseguiu criar um arranjo ordenado dos primeiros elementos do arranjo chamando o método mergesort recursivamente uma linha acima. void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 8) mergesort&a[0], 8) a n 8 a n 8 meio 4 meio 4 Nesta chamada do mergesort, a mesma coisa será feita. Porém, consideraremos o arranjo que se inicia na posição &a[4] e tem 4 elementos. Como já vimos na linha de código acima, a função mergesort tem a capacidade de ordenar este arranjo recursivamente. void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 8) mergesort&a[0], 8) a n 8 a n 8 meio 4 meio 4
58 Já o passo de intercalação desta chamada de função considerará o arranjo que começa em &a[0] e tem 8 elementos A função intercala então as duas metades ordenadas deste arranjo. void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 8) mergesort&a[0], 8) a n 8 a n 8 meio 4 meio 4 Com isto, temos nosso arranjo inicial ordenado e podemos retornar da função. Análise void mergesortint a[], int n) int meio; if n > 1) meio = n / 2; mergesorta, meio); mergesorta + meio, n - meio); intercalaa, n); mergesort&a[0], 8) a n 8 meio 4 Uma peça fundamental do algoritmo é a intercalação de dois arranjos Esta intercalação tem um custo On) pois passamos 1 vez por cada elemento o colocando no arranjo temporário
59 O custo do algoritmo depende então do número de intercalações feitas. Para isto, analise a seguinte intercalação: Neste exemplo foram feitos 3 passos de intercalação A cada um destes passos, 8 elementos foram intercalados. Se temos n elementos, cada intercalação com custo On) e k passos, o custo total do algoritmo é Onk). Nos resta saber o valor de k. n = k =
60 A cada passo, dobramos o tamanho de cada subarranjo, ou seja o tamanho dos subarranjos depois de k passos é 2 k. O algoritmo termina quando o tamanho do arranjo intercalado seja 2 k = n n = 2 3 = 2 k k = n k = lg n Deste modo, temos que o número de passos k para encerrar o algoritmo é lg n lg n = n = 2 3 = 2 k n = Sendo k = lg n, o custo total do algoritmo é então Onk) = On log n)
61 Como vimos anteriormente, algoritmos típicos da classe On log n) são os que quebram o problema em problemas menores, fazem uma operação em cada um dos elementos e depois combinam as soluções Análise Assim, a complexidade do método é On log n) Uma vantagem do método é sua complexidade constante para o pior caso e melhor caso A maior desvantagem é a necessidade de memória extra na fase de intercalação e nas chamadas recursivas da função Análise Quicksort Para uma ampla variedade de situações, é o método mais rápido que se conhece Assim, este é o algoritmo a ser usado quando queremos uma ordem de complexidade baixa, constante, mas memória não seja uma problema. Provavelmente, o mais utilizado, junto ao Merge sort A cada passo do Quicksort, o problema de ordenação é dividido em dois problemas menores que são ordenados de maneira independente
62 Quicksort A parte complicada do método é a partição do problema em problemas menores O processo de partição é feito a partir da escolha de um pivô x Escolhido o pivô, um arranjo é dividido em duas partes: A parte esquerda com elementos menores ou iguais a x A parte direita com elementos maiores ou iguais a x Considere um arranjo com os seguintes elementos Escolheremos um elemento como pivô Particionamos o arranjo de modo que elementos menores que o pivô fiquem à esquerda e elementos maiores fiquem à direita
63 Repetimos o processo para cada subproblema. Particionamos novamente o arranjo com os maiores à direita e menores fiquem à esquerda Repetiremos o processo para cada subproblema. Porém, os arranjos de tamanho 1 já são considerados como ordenados. Este é o resultado do novo processo de partição.
64 Todos os subarranjos de tamanho 1 podem ser considerados ordenados. Com todos os subarranjos ordenados, sabemos que o arranjo original está ordenado. Particionamento Para fazer o particionamento: Escolhemos um pivô x Percorremos o arranjo a partir da esquerda até que x Percorremos o arranjo a partir da direita até que < x Trocamos com Continuamos até que i e j se cruzem Considere um arranjo com os seguintes elementos
65 x x Escolhemos um elemento como pivô Definimos índices i e j que marcam o ínicio e o fim do arranjo x x Movimentamos i para a direita até encontrar um elemento maior ou igual a x. Neste caso, o elemento é o próprio 7. Movimentamos j para a esquerda até encontrar um elemento menor ou igual a x. O primeiro elemento nesta condição é o 2.
66 x x Trocamos com. Deslocamos i e j e continuaremos o processo. x x Deslocamos i até encontrar um elemento maior ou igual a x. Este elemento já é o 5. Deslocamos j até encontrar um elemento menor ou igual a x. Este elemento é o 4.
67 x x Trocamos com. Deslocamos i e j e continuaremos o processo. x x Deslocamos i até encontrar um elemento maior ou igual a x. Deslocamos j até encontrar um elemento menor ou igual a x. Este elemento já é o 3.
68 x subarranjo 1 Desta vez, porém, os valores de i e j se cruzaram e por isto não faremos a troca e encerramos a função. Os itens da esquerda até o elemento formam um subarranjo com elementos menores ou iguais ao pivô. de partição: subarranjo 1 subarranjo 2 Passo Os itens da direita a partir do elemento formam um subarranjo com elementos maiores ou iguais ao pivô. Movimentação Desordenado Ordenado Pivô
69 Partição de aplicação do Quicksort: Passo Nos exemplos anteriores, o pivô foi escolhido como o elemento na posição i+j)/ Subarranjo Desordenado Ordenado Pivô Este é o algoritmo que particiona o arranjo a[esq] a[dir] nos subarranjos: a[esq] e a[dir] void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); O laço interno do algoritmo de partição é muito simples. Por isto, o algoritmo quicksort é tão rápido.
70 void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); A função de partição recebe vários parâmetros. void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); Os parâmetros esq e dir indicam qual subarranjo de a[] queremos particionar. Neste exemplo, particionaremos todo o arranjo: de a[0] a a[7]. i j i j a esq dir a esq 0 dir 7 void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); Os parâmetros i e j são passados por referência. Eles pertencem originalmente à função chamadora do quicksort. ordenar0, 7, a) void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); Ao fim do algoritmo, estas referências i e j dirão à função chamadora quais são os subarranjos particionados. ordenar0, 7, a) i j i j particionar0, 7, &i, &j, a) particionar0, 7, &i, &j, a) i j i j a esq 0 dir 7 a esq 0 dir 7
71 void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); Todo o arranjo a[] é também enviado à função. Como sabemos, arranjos são endereços na memória e por isto são apenas enviados por referência. ordenar0, 7, a) void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); Criamos então a variável x, para guardar o elemento pivô, e uma variável temporária temp para fazer trocas. ordenar0, 7, a) i j i j particionar0, 7, &i, &j, a) particionar0, 7, &i, &j, a) i j i j a esq 0 dir 7 a esq 0 dir 7 x temp void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); Os índices i e j são inicializados nos extremos do arranjo a ser particionado. ordenar0, 7, a) void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); O elemento do meio é então escolhido como pivô. De acordo com a estratégia, outro pivô poderia ter sido escolhido. ordenar0, 7, a) i 0 j 7 i 0 j 7 particionar0, 7, &i, &j, a) particionar0, 7, &i, &j, a) a i j a i j esq 0 dir 7 esq 0 dir 7 x temp a[i+j)/2] x 4 temp
72 void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); Neste grande laço, vamos fazer as trocas enquanto os índices i e j não tenham se cruzado. ordenar0, 7, a) void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); Deslocamos o índice i até encontrar o primeiro elemento maior ou igual a x. ordenar0, 7, a) i 0 j 7 i 0 j 7 particionar0, 7, &i, &j, a) particionar0, 7, &i, &j, a) a i j a i j esq 0 dir 7 esq 0 dir 7 x 4 temp x 4 temp void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); Deslocamos o índice j até encontrar o primeiro elemento menor ou igual a x. ordenar0, 7, a) void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); Se neste deslocamento, os índices não cruzaram ordenar0, 7, a) i 0 j 5 i 0 j 5 particionar0, 7, &i, &j, a) particionar0, 7, &i, &j, a) a i j a i j esq 0 dir 7 esq 0 dir 7 x 4 temp x 4 temp
73 void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; ordenar0, 7, a) while i <= j); Trocaremos com void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); Deslocamos i e j em mais uma posição ordenar0, 7, a) i 0 j 5 i 1 j 4 particionar0, 7, &i, &j, a) particionar0, 7, &i, &j, a) a i j a i j esq 0 dir 7 esq 0 dir 7 x 4 temp 7 x 4 temp 7 void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); Todo o laço externo se repete enquanto os índices não se cruzarem. ordenar0, 7, a) void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; ordenar0, 7, a) while i <= j); Deslocamos os índices i e j. i 1 j 4 i 1 j 3 particionar0, 7, &i, &j, a) particionar0, 7, &i, &j, a) a i j a i j esq 0 dir 7 esq 0 dir 7 x 4 temp 7 x 4 temp 7
74 void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); Como os índices não estão cruzados, trocamos com e deslocamos i e j em uma posição. ordenar0, 7, a) void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); Os índices i e j estão na mesma posição mas ainda não se cruzaram. ordenar0, 7, a) i 2 j 2 i 2 j 2 particionar0, 7, &i, &j, a) particionar0, 7, &i, &j, a) a i j a i j esq 0 dir 7 esq 0 dir 7 x 4 temp 5 x 4 temp 5 void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); O primeiro maior ou igual a x é a[3]. ordenar0, 7, a) void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); O elemento já é menor ou igual a x. ordenar0, 7, a) i 3 j 2 i 3 j 2 particionar0, 7, &i, &j, a) particionar0, 7, &i, &j, a) a i j a i j esq 0 dir 7 esq 0 dir 7 x 4 temp 5 x 4 temp 5
75 void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); Como os índices já se cruzaram, a troca não é feita. ordenar0, 7, a) void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); A segunda constatação de que os índices já se cruzaram encerra a função. ordenar0, 7, a) i 3 j 2 i 3 j 2 particionar0, 7, &i, &j, a) particionar0, 7, &i, &j, a) a i j a i j esq 0 dir 7 esq 0 dir 7 x 4 temp 5 x 4 temp 5 void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); Como resultado, temos dois subarranjos. Um de a[0] até a[2] e outros de a[3] até a[7]. ordenar0, 7, a) void particionarint esq, int dir, int &i, int &j, int a[]) int x, temp; i = esq; j = dir; x = a[i + j) / 2]; do while x > ) while x < ) if i <= j) temp = ; = ; = temp; while i <= j); Como i e j foram passados por referência, a função chamadora sabe que os subarranjos são de a[esq] até e de até a[dir]. ordenar0, 7, a) i 3 j 2 i 3 j 2 particionar0, 7, &i, &j, a) particionar0, 7, &i, &j, a) a i j a i j esq 0 dir 7 esq 0 dir 7 a[esq] a[dir] x 4 temp 5 a[esq] a[dir] x 4 temp 5
76 Esta função ordena o arranjo a[] entre as posições esq e dir void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); Tendo pronta a função para o processo de partição, o algoritmo de ordenação se torna conceitualmente muito simples. a esq dir Para ordenar todo arranjo, chamamos: ordenar0, 7, a) Criamos então os índices i e j, que indicarão quais são os subarranjos após a partição. void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); ordenar0, 7, a) ordenar0, 7, a) a a i j esq 0 dir 7 esq 0 dir 7
77 Particionamos o arranjo entre as posições a[esq] e a[dir]. Os índices i e j indicam onde termina o primeiro subarranjo e onde começa o segundo. Se o valor de j atingir o valor de esq, o subarranjo tem apenas um elemento e não precisa ser ordenado. void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); ordenar0, 7, a) ordenar0, 7, a) a i 3 j 2 a i 3 j 2 esq 0 dir 7 esq 0 dir 7 Como o subarranjo de a[0] a a[2] tem mais de 1 elemento, usamos a própria função para ordená-lo. A função chamadora vai para a pilha e executamos a função que ordenará a[] das posições 0 a 2 void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); i 3 j 2 ordenar0, 7, a) ordenar0, 2, a) ordenar0, 7, a) esq 0 dir 7 a i 3 j 2 a esq 0 dir 7 esq 0 dir 2
78 Os índices são criados e o subarranjo é particionado. Esta função é jogada na pilha e chamamos a função de ordenação para o subarranjo de a[esq] até void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); i 3 j 2 i 3 j 2 ordenar0, 2, a) ordenar0, 7, a) esq 0 dir 7 ordenar0, 2, a) ordenar0, 7, a) esq 0 dir 7 a i 2 j 1 a i 2 j 1 esq 0 dir 2 esq 0 dir 2 Esta função é jogada na pilha e chamamos a função de ordenação para o subarranjo de a[esq] até Os índices são criados e o subarranjo particionado void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); i 2 j 1 void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); i 2 j 1 ordenar0, 2, a) esq 0 dir 2 ordenar0, 2, a) esq 0 dir 2 i 3 j 2 i 3 j 2 ordenar0, 1, a) ordenar0, 7, a) esq 0 dir 7 ordenar0, 1, a) ordenar0, 7, a) esq 0 dir 7 a a i 1 j -1 esq 0 dir 1 esq 0 dir 1
79 Como o subarranjo da esquerda não tem nenhum elemento, não executamos esta ordenação. Como o subarranjo da direita tem apenas 1 elemento, damos este subarranjo como ordenado. void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); i 2 j 1 void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); i 2 j 1 ordenar0, 2, a) esq 0 dir 2 ordenar0, 2, a) esq 0 dir 2 i 3 j 2 i 3 j 2 ordenar0, 1, a) ordenar0, 7, a) esq 0 dir 7 ordenar0, 1, a) ordenar0, 7, a) esq 0 dir 7 a i 1 j -1 a i 1 j -1 esq 0 dir 1 esq 0 dir 1 Ao fim desta função, temos o subarranjo de 0 até 1 ordenado e voltaremos a com a função do topo da pilha. O próximo comando desta função seria ordenar o subarranjo da direita, mas isto não é necessário pois este subarranjo tem apenas 1 elemento. void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); ordenar0, 2, a) i 2 j 1 esq 0 dir 2 void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); i 3 j 2 i 3 j 2 ordenar0, 1, a) ordenar0, 7, a) esq 0 dir 7 ordenar0, 2, a) ordenar0, 7, a) esq 0 dir 7 a i 1 j -1 a i 2 j 1 esq 0 dir 1 esq 0 dir 2
80 Encerramos a função e voltamos para a função do topo da pilha. Esta função chama então a ordenação dos elementos de a[3] até a[7]. Criamos os índices e particionamos este subarranjo. void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); i 3 j 2 ordenar0, 7, a) ordenar3, 7, a) ordenar0, 7, a) esq 0 dir 7 a i 3 j 2 a i 6 j 4 esq 0 dir 7 esq 3 dir 7 Entre os subarranjos de a[3] até a[4] e de a[6] até a[7] temos isolado o a[5], que por estar sozinho, está também, por definição, ordenado. Com uma chamada recursiva, ordenamos a[3] e a[4]. void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); i 3 j 2 i 3 j 2 ordenar3, 7, a) ordenar0, 7, a) esq 0 dir 7 ordenar3, 7, a) ordenar0, 7, a) esq 0 dir 7 a i 6 j 4 a i 6 j 4 esq 3 dir 7 esq 3 dir 7
81 Criamos os índices e particionamos. Como nenhum dos subarranjos têm mais de um elemento, damos todos os elementos estão ordenados. void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); i 6 j 4 void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); i 6 j 4 ordenar3, 7, a) esq 3 dir 7 ordenar3, 7, a) esq 3 dir 7 i 3 j 2 i 3 j 2 ordenar3, 4, a) ordenar0, 7, a) esq 0 dir 7 ordenar3, 4, a) ordenar0, 7, a) esq 0 dir 7 a i 4 j 2 a i 4 j 2 esq 3 dir 4 esq 3 dir 4 A função que sai da pilha ainda precisa ordenar os elementos de a[6] até a[7] Esta função também leva a dois subarranjos de tamanho 1, que por isto já estão ordenados. void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); ordenar3, 7, a) i 6 j 4 esq 3 dir 7 i 3 j 2 i 3 j 2 ordenar3, 7, a) ordenar0, 7, a) esq 0 dir 7 ordenar6, 7, a) ordenar0, 7, a) esq 0 dir 7 a i 6 j 4 a i 7 j 6 esq 3 dir 7 esq 6 dir 7
82 Assim sendo, a função corrente sai da pilha E a função original sai da pilha, finalizando o método com o arranjo original ordenado. void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); i 3 j 2 ordenar3, 7, a) ordenar0, 7, a) esq 0 dir 7 ordenar0, 7, a) a i 6 j 4 a i 3 j 2 esq 3 dir 7 esq 0 dir 7 Quicksort Quicksort void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); Um inconveniente deste método é que precisamos passar para a função três parâmetros quando queremos ordenar todo o arranjo. void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); Para não precisamos fazer isto, criamos um método auxiliar que é chamado para ordenar todo o arranjo, iniciando de a[0]. void quicksortint a[], int n) ordenar0, n-1, a);
83 Quicksort Análise geral void ordenarint esq, int dir, int a[]) int i, j; particionaresq, dir, i, j, a); if esq < j) ordenaresq, j, a); if i < dir) ordenari, dir, a); Deste modo, mantemos o padrão com os outros métodos de ordenação. void quicksortint a[], int n) ordenar0, n-1, a); O Quicksort é também um típico exemplo de algoritmo On log n) Algoritmos típicos desta classe são os que dividem um problema em dois problemas menores e depois os juntam fazendo uma operação em cada um dos elementos. Análise - Pivô Assim, a escolha do pivô é um fator determinante no desempenho do algoritmo pois dependemos dele para realmente dividir o arranjo em dois subarranjos de tamanho similar Imagine um algoritmo onde o pivô escolhido é sempre o menor elemento do conjunto de n elementos a
84 Ordenaremos então o subarranjo da direita que ainda tem n-1 elementos. Se em todos os passos escolhermos o pior pivô, não conseguiremos dividir o problema pela metade a cada passo. a a Em 2 dois passos de partição ordenamos apenas 2 elementos. Com a pior escolha de pivô a cada passo, precisaríamos de n passos de partição para ordenar todo o arranjo. Como a cada passo estamos achando o menor elemento do arranjo e o colocando em sua posição correta, este pior caso do Quicksort equivale exatamente à ordenação por seleção, que tem custo On 2 ) a a
85 Análise geral Assim, o algoritmo quicksort tem os seguintes casos: Pior caso: On 2 ) Melhor caso: On log n) Caso médio: On log n) O pior caso é muito pouco provável, o que mantém o caso médio em On log n) Análise Algumas estratégias podem ser utilizadas para se escolher um melhor pivô, melhorando a eficiência do algoritmo e deixando o pior caso ainda menos provável. Pivô ótimo O melhor pivô para um passo de repartição seria a mediana dos valores no arranjo. Um pivô igual à mediana dividiria o arranjo em dois arranjos de tamanho idêntico. Porém, obter a mediana de um arranjo desordenado seria um processo caro. Pivô ótimo Estratégias comuns para melhores pivôs são: Escolher a cada passo um elemento aleatório do arranjo. Escolher três elementos aleatórios do arranjo e usar a mediana dos três como pivô
86 Análise - Vantagens É um método muito eficiente para ordenar dados Somente devido à pilha de funções precisa de apenas um espaço extra muito pequeno, que é Olg n) Requer apenas On lg n) comparações. Apesar da mesma ordem de complexidade média, é usualmente mais rápido que o Merge sort no caso médio Análise - Desvantagens Tem um pior caso On 2 ), o que pode ser um fator importante em aplicações críticas, mesmo que com uma baixa probabilidade Não é um algoritmo estável pois as trocas na fase de partição não consideram a ordem relativa dos elementos Comparação Comparação Algoritmo Seleção Inserção Mergesort Quicksort Pior caso On 2 ) On 2 ) On log n) On 2 ) Melhor caso On 2 ) On) On log n) On log n) Caso médio On 2 ) On 2 ) On log n) On log n) Estabilidade Não Sim Sim Não Adaptabilidade Não Sim Não Sim Movimentações na média On) On 2 ) On log n) On log n) Memória extra O1) O1) On) Olog n) Algoritmo Seleção Inserção Mergesort Quicksort Pior caso On 2 ) On 2 ) On log n) On 2 ) Melhor caso On 2 ) On) On log n) On log n) Caso médio On 2 ) On 2 ) On log n) On log n) Estabilidade Não Sim Sim Não Adaptabilidade Entre os métodos Não eficientes, Sim Quicksort Não é Sim mais rápido Movimentações na média On) do que Ono 2 ) Mergesort, On log n) apesar On log n) de na média terem mesma ordem de grandeza. Memória extra O1) O1) On) Olog n)
87 Comparação Comparação Algoritmo Seleção Inserção Mergesort Quicksort Pior caso On 2 ) On 2 ) On log n) On 2 ) Melhor caso On 2 ) On) On log n) On log n) Caso médio On 2 ) On 2 ) On log n) On log n) Estabilidade Não Sim Sim Não Se os elementos já estivem ordenados, a Adaptabilidade Não Sim Não Sim ordenação por Inserção é sempre o método Movimentações On) On 2 ) On log n) On log n) mais na média rápido. Este melhor caso, porém, é pouco Memória extra O1) O1) On) Olog n) provável na maioria das aplicações. Algoritmo Seleção Inserção Mergesort Quicksort Pior caso On 2 ) On 2 ) On log n) On 2 ) Melhor caso On 2 ) On) On log n) On log n) Caso médio On 2 ) On 2 ) On log n) On log n) Estabilidade Não Sim Sim Não A ordenação por inserção é interessante para se Adaptabilidade Não Sim Não Sim adicionar alguns elementos a um arranjo já Movimentações On) On 2 ) On log n) On log n) ordenado. média Assim, estaremos próximos de seu Memória extra O1) O1) On) Olog n) melhor caso. Comparação Comparação Algoritmo Seleção Inserção Mergesort Quicksort Pior caso On 2 ) On 2 ) On log n) On 2 ) Melhor caso On 2 ) On) On log n) On log n) Caso médio On 2 ) On 2 ) On log n) On log n) Apesar de ter a mesma ordem de grandeza da Estabilidade Não Sim Sim Não ordenação por seleção, a ordenação por Adaptabilidade Não Sim Não Sim inserção é a mais lenta para arranjos em ordem Movimentações On) On 2 ) On log n) On log n) na decrescente. média Mesmo assim, pode continuar Memória extra O1) O1) On) Olog n) melhor que a ordenação por seleção. Algoritmo Seleção Inserção Mergesort Quicksort Pior caso On 2 ) On 2 ) On log n) On 2 ) Melhor caso On 2 ) On) On log n) On log n) Caso médio On 2 ) On 2 ) On log n) On log n) Estabilidade Não Sim Sim Não Apesar da mesma ordem de grandeza, a Adaptabilidade Não Sim Não Sim ordenação por inserção tende a ser melhor que Movimentações On) On 2 ) On log n) On log n) a na ordenação média por seleção no caso médio com Memória extra O1) O1) On) Olog n) arranjos aleatórios.
88 Comparação Comparação Algoritmo Seleção Inserção Mergesort Quicksort Pior caso On 2 ) On 2 ) On log n) On 2 ) Melhor caso On 2 ) On) On log n) On log n) Caso médio On 2 ) On 2 ) On log n) On log n) Estabilidade Não Sim Sim Não Nos casos gerais, a inserção é um método interessante apenas para arranjos bastante pequenos, com menos de 20 elementos. Adaptabilidade Não Sim Não Sim Movimentações na média On) On 2 ) On log n) On log n) Memória extra O1) O1) On) Olog n) Algoritmo Seleção Inserção Mergesort Quicksort Pior caso On 2 ) On 2 ) On log n) On 2 ) Se os registros são grandes, as movimentações Melhor caso On 2 ) On) On log n) On log n) se tornam caras e a ordenação por seleção se Caso torna médio interessante, On 2 ) On caso 2 ) não On haja log n) tantos On log n) Estabilidade Não elementos. Sim Sim Não Adaptabilidade Não Sim Não Sim Movimentações na média On) On 2 ) On log n) On log n) Memória extra O1) O1) On) Olog n) Comparação Comparação Algoritmo Seleção Inserção Mergesort Quicksort Pior caso On 2 ) On 2 ) On log n) On 2 ) Porém, outra maneira de evitar movimentos é usar ordenação indireta, ou seja, ordenamos ponteiros para elementos e só fazemos On) movimentações no final. Melhor caso On 2 ) On) On log n) On log n) Caso médio On 2 ) On 2 ) On log n) On log n) Estabilidade Não Sim Sim Não Adaptabilidade Não Sim Não Sim Movimentações na média On) On 2 ) On log n) On log n) Memória extra O1) O1) On) Olog n) Algoritmo Seleção Inserção Mergesort Quicksort Pior caso On 2 ) On 2 ) On log n) On 2 ) Melhor caso On 2 ) On) On log n) On log n) No caso médio, quicksort é a melhor opção para a maioria da situações. Caso médio On 2 ) On 2 ) On log n) On log n) Estabilidade Não Sim Sim Não Adaptabilidade Não Sim Não Sim Movimentações na média On) On 2 ) On log n) On log n) Memória extra O1) O1) On) Olog n)
89 Comparação Comparação Algoritmo Seleção Inserção Mergesort Quicksort Pior caso On 2 ) On 2 ) On log n) On 2 ) Apesar de no geral ser o melhor método entre os apresentados, ele não garante estabilidade. Melhor caso On 2 ) On) On log n) On log n) Caso médio On 2 ) On 2 ) On log n) On log n) Estabilidade Não Sim Sim Não Adaptabilidade Não Sim Não Sim Movimentações na média On) On 2 ) On log n) On log n) Memória extra O1) O1) On) Olog n) Algoritmo Seleção Inserção Mergesort Quicksort Pior caso On 2 ) On 2 ) On log n) On 2 ) Melhor caso On 2 ) On) On log n) On log n) Suas chamadas recursivas também demandam um pouco de memória extra. Caso médio On 2 ) On 2 ) On log n) On log n) Estabilidade Não Sim Sim Não Adaptabilidade Não Sim Não Sim Movimentações na média On) On 2 ) On log n) On log n) Memória extra O1) O1) On) Olog n) Comparação Algoritmo Seleção Inserção Mergesort Quicksort Pior caso On 2 ) On 2 ) On log n) On 2 ) Melhor caso On 2 ) On) On log n) On log n) Apesar de haver um pior caso, sua ocorrência é quase impossível para qualquer boa estratégia de seleção de pivôs. Caso médio On 2 ) On 2 ) On log n) On log n) Estabilidade Não Sim Sim Não Adaptabilidade Não Sim Não Sim Movimentações na média On) On 2 ) On log n) On log n) Memória extra O1) O1) On) Olog n) Comparação Quando os arranjos já estão quase ordenados, usar o elemento do meio como pivô melhora muito o desempenho do algoritmo. Porém, nem sempre usar o pivô do meio é uma boa estratégia. Algoritmo Seleção Inserção Mergesort Quicksort Pior caso On 2 ) On 2 ) On log n) On 2 ) Melhor caso On 2 ) On) On log n) On log n) Caso médio On 2 ) On 2 ) On log n) On log n) Estabilidade Não Sim Sim Não Adaptabilidade Não Sim Não Sim Movimentações na média On) On 2 ) On log n) On log n) Memória extra O1) O1) On) Olog n)
90 Comparação Algoritmo Seleção Inserção Mergesort Quicksort Pior caso On 2 ) On 2 ) On log n) On 2 ) Melhor caso On 2 ) On) On log n) On log n) Caso médio On 2 ) On 2 ) On log n) On log n) Os métodos de inserção e quicksort podem ser combinados. A inserção pode ordenar subarranjos pequenos do quicksort, o que On) On 2 ) On log n) On log n) costuma melhorar muito seu desempenho médio. Estabilidade Não Sim Sim Não Adaptabilidade Não Sim Não Sim Movimentações na média Memória extra O1) O1) On) Olog n) Testes reais Como os merge sort e quicksort são On log n) no caso médio, sabemos que todos devem ser mais eficientes que os métodos simples. Entre os dois, podemos fazer comparações reais de tempo para ver a performance em segundos dos algoritmos. Para as comparações apresentadas a seguir, foram utilizadas versões mais eficientes dos algoritmos. Testes reais Arranjos em ordem inicial aleatória Comparação com os métodos simples Testes reais Mesmo sem a ordenação por seleção, há muita diferença entre os métodos. x 10 4 Seleção Inserção Merge sort Quicksort Seleção Inserção Merge sort Quicksort 7 x Inserção Merge sort Quicksort Inserção Merge sort Quicksort Tempo segundos) 1 Tempo segundos) Tempo segundos) Tempo segundos) Tamanho do arranjo Tamanho do arranjo x Tamanho do arranjo Tamanho do arranjo x 10 4
91 Testes reais Para arranjos pequenos, porém, a ordenação por inserção pode ser mais eficiente que o merge sort. Testes reais Como esperado, métodos simples são muito mais lentos. Até 700 vezes mais lentos em arranjos grandes. 7 x Tempo segundos) Inserção Merge sort Quicksort Tempo segundos) Inserção Merge sort Quicksort Tempo em proporção ao Quicksort) Seleção Inserção Merge sort Quicksort Tempo em proporção ao Quicksort) Seleção Inserção Merge sort Quicksort Tamanho do arranjo Tamanho do arranjo x Tamanho do arranjo Tamanho do arranjo x 10 4 Testes reais Mesmo a inserção é cerca de 55 vezes mais lenta para arranjos grandes. Testes reais Quanto maior o valor de n, piores serão os métodos On 2 ) em relação aos métodos On log n) Inserção Merge sort Quicksort Inserção Merge sort Quicksort Inserção Merge sort Quicksort Inserção Merge sort Quicksort 3 3 Tempo em proporção ao Quicksort) Tempo em proporção ao Quicksort) Tempo em proporção ao Quicksort) Tempo em proporção ao Quicksort) Tamanho do arranjo Tamanho do arranjo x Tamanho do arranjo Tamanho do arranjo x 10 4
92 Testes reais Em comparação direta entre os métodos, o quicksort é usualmente mais eficiente que o merge sort. Testes reais A proporção de eficiência entre os dois métodos se mantém constante para diferentes tamanhos arranjo. 7 x Merge sort Quicksort Merge sort Quicksort Merge sort Quicksort Merge sort Quicksort 3 3 Tempo segundos) Tempo segundos) Tempo em proporção ao Quicksort) Tempo em proporção ao Quicksort) Tamanho do arranjo Tamanho do arranjo x Tamanho do arranjo Tamanho do arranjo x 10 4 Conclusão Os métodos de ordenação mostram como é possível obter variados tipos de vantagens e desvantagens entre algoritmos para a mesma classe de problemas, mesmo que este problema seja simples. Mesmo com o limite inferior de On log n), há vários fatores a se considerar em cada algoritmo como pior caso, caso médio, estabilidade, adaptabilidade, memória extra e operações de atribuição. Conclusão Apesar desta introdução ao tópico de ordenação ter sido limitada, as comparações entre algoritmos mostram a importância de conceitos como: Notação O Divisão e conquista Estruturas de dados Análise de melhor caso, pior caso e caso médio Conflito entre tempo e memória
93 Conclusão Além dos métodos apresentados aqui, existem vários outros métodos de ordenação, cada um com suas vantagens e desvantagens, alguns dos mais famosos são: Heapsort: melhora a ordenação por seleção com estruturas de dados mais eficientes Introsort: combinação de Quicksort e Heapsort que não tem o pior caso On 2 ) In-place merge sort: Mergesort sem gasto extra de memória On) Conclusão Bubble sort: Método de implementação simples Shell sort: Generalização da ordenação por inserção Ordenação por contagem: o número de ocorrências de cada elemento é contado e recolocado no arranjo original Radix Sort: Ordenação com caso médio Onk) no caso médio, onde k é o número de dígitos dos elementos Exercício Criar um arranjo com 10 elementos aleatórios entre 1 e 50 Desenhar o arranjo várias vezes demonstrando os passos de uma ordenação com os métodos: Merge sort Intercalar elementos a cada passo) Quicksort Particionar e mover a cada passo) Colocar um círculo nos elementos movimentados. Colocar um traço entre os elementos ordenados e os desordenados. Para a prova Programação é uma ferramenta, mas é uma ferramenta importante Conhecer todos os métodos e como funcionam Saber suas eficiências em notação O e o motivo de sua eficiência Conhecer seus piores e melhores casos Conhecer suas vantagens e desvantagens
94 Programação de Computadores Ordenação de Arranjos Alan R R Freitas
BCC202 - Estrutura de Dados I
BCC202 - Estrutura de Dados I Aula 12: Ordenação: Bubble, Selection e Insertion Sort Reinaldo Fortes Universidade Federal de Ouro Preto, UFOP Departamento de Computação, DECOM Website: www.decom.ufop.br/reifortes
Métodos de Ordenação Parte I
Estrutura de Dados II Métodos de Ordenação Parte I Prof a Márcio Bueno [email protected] / [email protected] Material baseado nos materiais da Prof a Ana Eliza e Prof. Robson Lins Rearranjar
BCC202 - Estrutura de Dados I
BCC202 - Estrutura de Dados I Aula 13: Ordenação: MergeSort Reinaldo Fortes Universidade Federal de Ouro Preto, UFOP Departamento de Computação, DECOM Website: www.decom.ufop.br/reifortes Email: [email protected]
Ordenação. Prof. Jonas Potros
Ordenação Prof. Jonas Potros Conceitos Básicos Ordenar: processo de rearranjar um conjunto de objetos em uma ordem ascendente ou descendente. A ordenação visa facilitar a recuperação posterior de itens
void subdivide (LISTA_ENC *pl, LISTA_ENC *pl1, LISTA_ENC *pl2) { int cont, k=1; for (cont=tam(*pl)/2;cont;cont--) {
void subdivide (LISTA_ENC *pl, LISTA_ENC *pl1, LISTA_ENC *pl2) { int cont, k=1; for (cont=tam(*pl)/2;cont;cont--) { } ins(pl1,recup(*pl,1),k++); ret(pl,1); } for (k=1, cont=tam(*pl);cont;cont--) { ins(pl2,recup(*pl,1),k++);
Projeto e Análise de Algoritmos
Projeto e Análise de Algoritmos Aula 09 Algoritmos de Ordenação Edirlei Soares de Lima Ordenação Problema: Entrada: conjunto de itens a 1, a 2,..., a n ; Saída: conjunto de itens
Método BubbleSort. Estrutura de Dados II Prof Jairo Francisco de Souza
Método BubbleSort Estrutura de Dados II Prof Jairo Francisco de Souza Introdução Ordenar corresponde ao processo de reorganizar um conjunto de objetos em uma ordem ascendente ou descendente Consiste em
Introdução Métodos de Busca Parte 1
Introdução Métodos de Busca Parte 1 SCC-201 Introdução à Ciência da Computação II Rosane Minghim 2009 Importância em estudar busca Busca é uma tarefa muito comum em computação? Vários métodos e estruturas
Métodos de Ordenação
Métodos de Ordenação Conceitos básicos sobre ordenação Ordenar corresponde ao processo de rearranjar um conjunto de objetos em uma ordem específica. Objetivo da ordenação: facilitar a recuperação posterior
Algoritmos de Ordenação
Algoritmos de Ordenação! Problema: encontrar um número de telefone em uma lista telefônica! simplificado pelo fato dos nomes estarem em ordem alfabética! e se estivesse sem uma ordem?! Problema: busca
ALGORITMOS DE ORDENAÇÃO
ALGORITMOS DE ORDENAÇÃO Prof. André Backes Conceitos básicos 2 Ordenação Ato de colocar um conjunto de dados em uma determinada ordem predefinida Fora de ordem 5, 2, 1, 3, 4 Ordenado 1, 2, 3, 4, 5 OU 5,
ORDENAÇÃO POR INTERCALAÇÃO
AULA 6 ORDENAÇÃO POR INTERCALAÇÃO Na aula 5 revimos os métodos de ordenação mais básicos, que são todos iterativos, simples e têm tempo de execução de pior caso proporcional a n 2, onde n é o tamanho da
Algoritmos de Ordenação: Tempo Linear
Algoritmos de Ordenação: Tempo Linear ACH2002 - Introdução à Ciência da Computação II Delano M. Beder Escola de Artes, Ciências e Humanidades (EACH) Universidade de São Paulo [email protected] 10/2008 Material
Algoritmos de Ordenação. Profº Carlos Alberto T. Batista
Algoritmos de Ordenação Profº Carlos Alberto T. Batista E-mail: [email protected] [email protected] Por que ordenar os dados? Encontrar elementos em uma lista torna-se algo simples e
Aula 3 Listas Lineares Sequenciais Ordenadas. prof Leticia Winkler
Aula 3 Listas Lineares Sequenciais Ordenadas prof Leticia Winkler 1 Listas Lineares Sequenciais Ordenadas Elementos da lista estão dispostos num vetor (contíguos na memória) e ordenado de acordo com alguma
INF111 Programação II Aulas 11, 12, 13 Ordenação
INF Programação II Aulas,, Ordenação Departamento de Informática UFV Ordenação A ordenação é o processo de organizar um conunto (vetor) de n obetos ou registros segundo uma determinada ordem crescente
Estrutura de Dados. Algoritmos de Ordenação. Prof. Othon M. N. Batista Mestre em Informática
Estrutura de Dados Algoritmos de Ordenação Prof. Othon M. N. Batista Mestre em Informática Roteiro Introdução Ordenação por Inserção Insertion Sort Ordenação por Seleção Selection Sort Ordenação por Bolha
Algoritmos e Estruturas de Dados I1 Prof. Eduardo 1
Algoritmos e Estruturas de Dados I1 Prof. Eduardo 1 ORDENAÇÃO E BUSCA Ordenação: Bublesort, seleção direta e inserção direta. Busca: linear e binária 1 - ORDENAÇÃO (CLASSIFICAÇÃO) DE DADOS Em diversas
Ordenação: Heapsort. Algoritmos e Estruturas de Dados II
Ordenação: Heapsort Algoritmos e Estruturas de Dados II Introdução Possui o mesmo princípio de funcionamento da ordenação por seleção Selecione o menor item do vetor Troque-o pelo item da primeira posição
Marcelo Keese Albertini Faculdade de Computação Universidade Federal de Uberlândia
Introdução à Análise de Algoritmos Marcelo Keese Albertini Faculdade de Computação Universidade Federal de Uberlândia Aula de hoje Nesta aula veremos: Sobre a disciplina Exemplo: ordenação Sobre a disciplina
Aula 18 Algoritmos básicos de busca e classificação
Aula 18 Algoritmos básicos de busca e classificação Dentre os vários algoritmos fundamentais, os algoritmos de busca em tabelas e classificação de tabelas estão entre os mais usados. Considere por exemplo
ALGORITMOS AVANÇADOS. UNIDADE III Algoritmo de Ordenação por Intercalação (Mergesort) Luiz Leão
UNIDADE III Algoritmo de Ordenação por Intercalação (Mergesort) Luiz Leão [email protected] http://www.luizleao.com Conteúdo Programático 3.1 - Definição 3.2 - Dividir para conquistar 3.3 - Problema da
BUSCA EM ARRAYS. Prof. André Backes. Ato de procurar por um elemento em um conjunto de dados
BUSCA EM ARRAYS Prof. André Backes Definição 2 Ato de procurar por um elemento em um conjunto de dados Recuperação de dados armazenados em um repositório ou base de dados A operação de busca visa responder
Extra- Algoritmos de Ordenação
Extra- Algoritmos de Ordenação 1 Introdução Ordenar: processo de rearranjar um conjunto de objetos em uma ordem ascendente ou descendente. A ordenação visa facilitar a recuperação posterior de itens do
Quicksort. David Menotti Algoritmos e Estruturas de Dados II DInf UFPR
Quicksort David Menotti Algoritmos e Estruturas de Dados II DInf UFPR Quicksort Proposto por Hoare em 1960 e publicado em 1962. É o algoritmo de ordenação interna mais rápido que se conhece para uma ampla
Classificação por Intercalação
458 Classificação por Intercalação Este é um bom exemplo de abordagem top down, ou de aplicação do princípio da divisão e conquista, associado à recursividade. Ao se observar o andamento do processo sobre
Classificação por Seleção - selection sort
Classificação por Seleção - selection sort Outro método também simples de ordenação é a ordenação por seleção. Princípio de funcionamento: 1. Selecione o menor item do vetor (ou o maior). 2. Troque-o com
Ordenação em Memória Primária Estrutura de Dados II
- Centro de Ciências Exatas, Naturais e de Saúde Departamento de Computação Ordenação em Memória Primária Estrutura de Dados II Estrutura de Dados II COM10078-2017-I Prof. Marcelo Otone Aguiar [email protected]
5. Algoritmos de Ordenação
Introdução à Computação II 5952011 5. Algoritmos de Ordenação Prof. Renato Tinós Depto. de Computação e Matemática (FFCLRP/USP) 1 Principais Tópicos 5.1. Ordenação por Inserção 5.2. Ordenação por Seleção
Programação II. Busca em Vetor (search) Bruno Feijó Dept. de Informática, PUC-Rio
Programação II Busca em Vetor (search) Bruno Feijó Dept. de Informática, PUC-Rio Busca em Vetor Problema: Entrada: vetor v com n elementos elemento d a procurar Saída m se o elemento procurado está em
Existem duas categorias de algoritmos de ordenação: Os algoritmos de ordenação são avaliados de acordo com os seguintes critérios:
MÉTODOS DE ORDENAÇÃO E PESQUISA ORDENAÇÃO: consiste em arranjar um conjunto de informações semelhantes numa ordem crescente ou decrescente; PESQUISA: consiste em executar uma pesquisa sobre a estrutura
Projeto e Análise de Algoritmos
Projeto e Algoritmos Pontifícia Universidade Católica de Minas Gerais [email protected] 26 de Maio de 2017 Sumário A complexidade no desempenho de Quando utilizamos uma máquina boa, ela tende a ter
INF1007: Programação 2 6 Ordenação de Vetores. 01/10/2015 (c) Dept. Informática - PUC-Rio 1
INF1007: Programação 2 6 Ordenação de Vetores 01/10/2015 (c) Dept. Informática - PUC-Rio 1 Tópicos Introdução Ordenação bolha (bubble sort) Ordenação por seleção (selection sort) 01/10/2015 (c) Dept. Informática
Métodos de ordenação. Bubble sort:
Métodos de ordenação Bubble sort: O método de ordenação por bubble sort ou conhecido como bolha consiste em compara dados armazenados em um vetor de tamanho qualquer, comparando cada elemento de uma posição
Universidade Estadual de Mato Grosso do Sul Bacharelado em Ciência da Computação Algoritmos e Estruturas de Dados II Prof. Fabrício Sérgio de Paula
Universidade Estadual de Mato Grosso do Sul Bacharelado em Ciência da Computação Algoritmos e Estruturas de Dados II Prof. Fabrício Sérgio de Paula Tópicos Introdução Ordenação por bolha (bubble sort)
Métodos de Classificação
Métodos de Classificação 261 Objetivos e Caracterizações O acesso a um conjunto de dados é facilitado se o mesmo está armazenado conforme uma certa ordem, baseada num critério conhecido. O objetivo básico
Aula 1. Teoria da Computação III
Aula 1 Teoria da Computação III Complexidade de Algoritmos Um problema pode ser resolvido através de diversos algoritmos; O fato de um algoritmo resolver um dado problema não significa que seja aceitável
Projeto e Análise de Algoritmos
Projeto e Análise de Algoritmos Aula 01 Complexidade de Algoritmos Edirlei Soares de Lima O que é um algoritmo? Um conjunto de instruções executáveis para resolver um problema (são
Ordenação Externa. Ordenação Externa. Ordenação Externa. Ordenação Externa
Ordenação Externa Ordenação Externa Estrutura de Dados II Prof. Guilherme Tavares de Assis Universidade Federal de Ouro Preto UFOP Instituto de Ciências Exatas e Biológicas ICEB Departamento de Computação
Carlos Eduardo Batista. Centro de Informática - UFPB
Estrutura de Dados Carlos Eduardo Batista Centro de Informática - UFPB [email protected] Aritmética de ponteiros em C (continuação) O que acontece na memória? Ponteiro para ponteiro etc. Métodos de pesquisa
Algoritmos e Estrutura de Dados. Algoritmos Prof. Tiago A. E. Ferreira
Algoritmos e Estrutura de Dados Aula 3 Conceitos Básicos de Algoritmos Prof. Tiago A. E. Ferreira Definição de Algoritmo Informalmente... Um Algoritmo é qualquer procedimento computacional bem definido
Mergesort. Aula 04. Algoritmo Mergesort. Divisão e Conquista. Divisão e Conquista- MergeSort
Mergesort Aula 0 Divisão e Conquista- MergeSort Prof. Marco Aurélio Stefanes marco em dct.ufms.br www.dct.ufms.br/ marco Mergesort é um algoritmo de ordenação recursivo Ele recursivamente ordena as duas
BCC202 - Estrutura de Dados I
BCC202 - Estrutura de Dados I Aula 15: Ordenação: ShellSort Reinaldo Fortes Universidade Federal de Ouro Preto, UFOP Departamento de Computação, DECOM Website: www.decom.ufop.br/reifortes Email: [email protected]
MÉTODOS DE ORDENAÇÃO. Introdução à Programação SI2
MÉTODOS DE ORDENAÇÃO Introdução à Programação SI2 2 Conteúdo Conceitos básicos Classificação por troca Classificação por inserção Classificação por seleção 3 Conceitos Básicos Ordenar: processo de rearranjar
Aula T19 BCC202 Pesquisa (Parte 1) Pesquisa Binária. Túlio Toffolo
Aula T19 BCC202 Pesquisa (Parte 1) Pesquisa Binária Túlio Toffolo www.decom.ufop.br/toffolo Pesquisa em Memória Primária n Introdução - Conceitos Básicos n Pesquisa Sequencial n Pesquisa Binária n Árvores
Pesquisa Linear. Adriano J. Holanda 15/3/2016
Pesquisa Linear Adriano J. Holanda 15/3/2016 Busca Linear em memória principal Introdução O dados estarão sempre armazenados na memória principal (DRAM 1 ): não há necessidade de acesso à memória secundária
Linguagem C: Ordenação
Instituto de C Linguagem C: Ordenação Luis Martí Instituto de Computação Universidade Federal Fluminense [email protected] - http://lmarti.com Tópicos Principais Introdução Algoritmos de ordenação Ordenação
ESTRUTURAS DE DADOS E ALGORITMOS ALGORITMOS DE ORDENAÇÃO POR COMPARAÇÃO - II
ESTRUTURAS DE DADOS E ALGORITMOS ALGORITMOS DE ORDENAÇÃO POR COMPARAÇÃO - II Adalberto Cajueiro Departamento de Sistemas e Computação Universidade Federal de Campina Grande ALGORITMOS VISTOS ANTERIORMENTE
Memória secundária. Memória secundária
introdução ordenação interna ordenação externa ordenar processo de rearranjar um conjunto de itens em uma ordem ascendente ou descendente visa facilitar a recuperação posterior de itens do conjunto ordenado
Análise de Complexidade de Algoritmos. mario alexandre gazziro
Análise de Complexidade de Algoritmos mario alexandre gazziro Definição A complexidade de um algoritmo consiste na quantidade de esforço computacional necessária para sua execução. Esse esforço é expresso
Árvores B. Prof. Márcio Bueno. / Fonte: Material da Prof a Ana Eliza Lopes Moura
Árvores B Prof. Márcio Bueno [email protected] / [email protected] Fonte: Material da Prof a Ana Eliza Lopes Moura Situação Problema Memória Principal Volátil e limitada Aplicações Grandes
Projeto e Análise de Algoritmos Aula 4: Dividir para Conquistar ou Divisão e Conquista ( )
Projeto e Análise de Algoritmos Aula 4: Dividir para Conquistar ou Divisão e Conquista (2.1-2.2) DECOM/UFOP 2013/1 5º. Período Anderson Almeida Ferreira Adaptado do material desenvolvido por Andréa Iabrudi
Departamento de Engenharia Rural Centro de Ciências Agrárias. Programação I
Departamento de Engenharia Rural Centro de Ciências Agrárias Programação I Algoritmos de busca Basicamente podem ser citadas duas estratégias para procurar (ou buscar) algo em uma coleção de dados: Busca
Classificação e Pesquisa Aula 6 Métodos de Ordenação: ShellSort e QuickSort. Prof. Esp. Pedro Luís Antonelli Anhanguera Educacional
Classificação e Pesquisa Aula 6 Métodos de Ordenação: ShellSort e QuickSort Prof. Esp. Pedro Luís Antonelli Anhanguera Educacional Plano de Ensino e Aprendizagem ( PEA) Algoritmo ShellSort Proposto por
Classes, Herança e Interfaces
Escola de Artes, Ciências e Humanidades EACH-USP ACH2002 Introdução à Ciência da Computação II Professor: Delano Medeiros Beder revisada pelo professor: Luciano Digiampietri EACH Segundo Semestre de 2011
Ordenação. Insertion Sort
Sumário por trocas de vizinhos. Análise. Limites teóricos Insertion sort. Shellsort recursiva: Mergesort. Análise. Com heap binário: Heapsort. Análise. Divisão e conquista: Quicksort. Análise. Limites
Radix Sorting. Várias aplicações têm chaves que são inteiros, definidos dentro de um intervalo
Radix Sorting Os registros a serem ordenados podem ter chaves bastante complexas, como por exemplo sequências de caracteres (lista telefônica) o Ordenação via comparação de chaves Várias aplicações têm
O Problema da Ordenação Métodos de Ordenação Parte 1
Métodos de Ordenação Parte 1 SCC-201 Introdução à Ciência da Computação II Rosane Minghim 2010 Ordenação (ou classificação) é largamente utilizada Listas telefônicas e dicionários Grandes sistemas de BD
Estruturas de Dados 2
Estruturas de Dados 2 Técnicas de Projeto de Algoritmos Dividir e Conquistar IF64C Estruturas de Dados 2 Engenharia da Computação Prof. João Alberto Fabro - Slide 1/83 Projeto de Algoritmos por Divisão
DAINF - Departamento de Informática
DAINF - Departamento de Informática Algoritmos 2 - Árvore binária de busca Prof. Alex Kutzke ( http://alex.kutzke.com.br/courses ) 30 de Novembro de 2015 Slides adaptados do material produzido pelo Prof.
Tabelas Hash O Que é uma Tabela Hash? O Que é uma Tabela Hash? O Que é uma Tabela Hash? Inserindo um Novo Registro. O Que é uma Tabela Hash?
Tabelas Hash O Que é uma Tabela Hash? Nesta aula são discutidos modos de armazenar informações em um vetor, e depois procurar por uma informação Tabelas Hash constituem uma abordagem comum para o problema
Método de ordenação - objetivos:
Método de ordenação - objetivos: Corresponde ao processo de rearranjar um conjunto de objetos em uma ordem ascendente ou descendente. Facilitar a recuperação posterior de itens do conjunto ordenado. São
Estrutura de Dados: Lista Linear. Parte I Introdução e Listas Sequenciais Estáticas
Estrutura de Dados: Lista Linear Parte I Introdução e Listas Sequenciais Estáticas Estrutura de dados: Lista Linear Def. Uma Lista Linear é uma coleção ordenada de componentes de um mesmo tipo. Ela é ou
QuickSort. Algoritmos e Estruturas de Dados Verão Cátia Vaz 1
QuickSort Algoritmos e Estruturas de Dados Verão 2012 1 QuickSort Algoritmo do tipo dividir para conquistar Ideia do algoritmo: efectuar partição dos dados e ordenar as várias partes independentemente
Classificação Externa: Geração de Partições Classificadas
Classificação Externa: Geração de Partições Classificadas Vanessa Braganholo Baseado no Material de: Inhaúma Neves Ferraz (IC/UFF) Cenário: Arquivos Sequencias } Acesso não pode ser feito em posições aleatórias
Edital de Seleção 032/2016 PROPESP/UFAM. Prova de Conhecimento. Caderno de Questões
Edital de Seleção 032/2016 PROPESP/UFAM Prova de Conhecimento Caderno de Questões CANDIDATO: INSCRIÇÃO: Assinatura conforme identidade INSTRUÇÕES PARA O CANDIDATO: Verifique o seu nome e o número da sua
Algoritmos e Estruturas de Dados
Introdução aos Algoritmos e Estruturas de Dados 2 o Teste - A LEIC Alameda, 2007/2008 Data: 12 de Junho de 2008 2 o Semestre Duração: 2h RESOLUÇÃO I. (2.5+2.5 = 5.0 val.) I.a) Suponha que está a trabalhar
Algoritmos de pesquisa. Tabelas de dispersão/hash
Algoritmos de pesquisa Tabelas de dispersão/hash Introdução Motivação: Considerar o problema de pesquisar um determinado valor num vetor. Se o vetor não está ordenado, a pesquisa requer O(n) de complexidade.
Pesquisa sequencial e pesquisa binária
Pesquisa sequencial e pesquisa binária Armando Matos Departamento de Ciência de Computadores Universidade de Porto 2008 2 problemas importantes... Pesquisa: Procurar um valor numa lista ou, por exemplo,
Ordenação de Dados. Ordenação de Dados
UFSC-CTC-INE INE38 - Estruturas de Dados Ordenação de Dados Prof. Ronaldo S. Mello 00/ Ordenação de Dados Processo bastante utilizado na computação de uma estrutura de dados Dados ordenados garantem uma
Arquivos Sequenciais. Estruturas de Dados II Vanessa Braganholo
Arquivos Sequenciais Estruturas de Dados II Vanessa Braganholo Arquivos Sequenciais } Pq arquivos sequenciais? Relembrando } Relembrando: uma tabela ou arquivo é um conjunto de registros que possuem a
Tabelas de hash Acabamos de estudar como implementar uma tabela hashing aberta e estudaremos agora como implementar uma tabela hashing fechada ou
Tabelas de hash Acabamos de estudar como implementar uma tabela hashing aberta e estudaremos agora como implementar uma tabela hashing fechada ou também denominada de tabela hashing com endereçamento aberto.
Medida do Tempo de Execução de um Programa. David Menotti Algoritmos e Estruturas de Dados II DInf UFPR
Medida do Tempo de Execução de um Programa David Menotti Algoritmos e Estruturas de Dados II DInf UFPR Classes de Comportamento Assintótico Se f é uma função de complexidade para um algoritmo F, então
6. Pesquisa e Ordenação
6. Pesquisa e Ordenação Fernando Silva DCC-FCUP Estruturas de Dados Fernando Silva (DCC-FCUP) 6. Pesquisa e Ordenação Estruturas de Dados 1 / 30 Pesquisa de Informação A pesquisa eficiente de informação
Árvores Binárias de Busca
Árvores Binárias de Busca SCC0202 - Algoritmos e Estruturas de Dados I Prof. Fernando V. Paulovich *Baseado no material do Prof. Gustavo Batista http://www.icmc.usp.br/~paulovic [email protected] Instituto
Centro Federal de Educação Tecnológica de Minas Gerais Programa de Pós-Graduação em Modelagem Matemática e Computacional
Centro Federal de Educação Tecnológica de Minas Gerais Programa de Pós-Graduação em Modelagem Matemática e Computacional Disciplina: Algoritmos e Estruturas de Dados Professor: Flávio Cardeal Lista de
Basicamente, os tipos de algoritmos de ordenação podem ser resumidos a: Por seleção: seleciona o menor elemento, o segundo menor e assim por diante
/ Ordenação de dados / 1 Ordenação de dados Frequentemente, é necessário que os dados devam ser armazenados obedecendo uma determinada ordem. A ordenação de dados é uma atividade relevante e fundamental
Comportamento Assintótico. Algoritmos e Estruturas de Dados Flavio Figueiredo (http://flaviovdf.github.io)
Comportamento Assintótico Algoritmos e Estruturas de Dados 2 2017-1 Flavio Figueiredo (http://flaviovdf.github.io) 1 Até Agora Falamos de complexidade de algoritmos com base no número de passos Vamos generalizar
Aula 15: Pesquisa em Memória Primária. Bruno Hott Algoritmos e Estruturas de Dados I DECSI UFOP
Aula 15: Pesquisa em Memória Primária Bruno Hott Algoritmos e Estruturas de Dados I DECSI UFOP Pesquisa em Memória Primária Introdução - Conceitos Básicos Pesquisa Sequencial Pesquisa Binária Árvores de
Árvores Binárias de Busca (ABB) 18/11
Árvores Binárias de Busca (ABB) 18/11 Definição Uma Árvore Binária de Busca possui as mesmas propriedades de uma AB, acrescida da seguintes propriedade: Para todo nó da árvore, se seu valor é X, então:
