IPOG
Estrutura de Dados
Prof. Ricardo de Andrade Kratz
EDITORA IPOG
Todos os direitos quanto ao conteúdo desse material didático são reservados
ao(s) autor(es). A reprodução total ou parcial dessa publicação por quaisquer
meios, seja eletrônico, mecânico, fotocópia, de gravação ou outros, somente
será permitida com prévia autorização do IPOG.
IP5p Instituto de Pós-Graduação e Graduação – IPOG
Estrutura de Dados. / Autor: Ricardo de Andrade Kratz.
240f. :il.
ISBN:
1. Estrutura de Dados 2. Ordenação 3. Listas e Vetores 4. Pilhas 5. Filas.
CDU: 005
Bibliotecário responsável: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
IPOG Sede
Instituto de Pós Graduação e Av. T-1 esquina com Av. T-55 N. 2.390
Graduação - Setor Bueno - Goiânia-GO. Telefone
(0xx62) 3945-5050
http://www.ipog.edu.br
UNIDADE 3 MÉTODOS DE ORDENAÇÃO
Ordenar elementos é uma tarefa fundamental na programação e nos ajuda a
organizar dados de forma eficiente. Os Métodos de Ordenação são algoritmos
utilizados para rearranjar elementos em uma determinada ordem, como
crescente ou decrescente.
Existem diversos métodos de ordenação, cada um com suas características e
complexidades. Alguns exemplos populares são o Bubble Sort, Insertion Sort,
Selection Sort, Merge Sort, Quick Sort, entre outros.
Durante seus estudos, você aprenderá como esses algoritmos funcionam,
entenderá suas vantagens e desvantagens, e poderá escolher o método mais
adequado para cada situação.
Ordenar dados é um conceito fundamental em ciência da computação e tem
aplicações em uma variedade de áreas, como processamento de dados, análise
de algoritmos, pesquisa e muito mais.
Estou aqui para ajudá-lo no seu aprendizado de Métodos de Ordenação. Se tiver
alguma dúvida ou precisar de exemplos práticos, sinta-se à vontade para
perguntar no AVA e nas aulas. Boa sorte em seus estudos e aproveite essa
jornada de aprendizado!
OBJETIVOS DA UNIDADE 3
Ao final dos estudos, você deverá ser capaz de:
• Compreender ordenação de dados.
• Aplicar e entender Ordenação de Vetores
• Algoritmo Bubble sort
• Algoritmo Selection sort
• Algoritmo Insertion sort
• Algoritmo Shell sort
• Algoritmo Merge sort
• Algoritmo Quick sort
3.1 Introdução a Ordenação
Segundo Drozdek (2016), os Métodos de Ordenação são algoritmos utilizados
para rearranjar um conjunto de elementos em uma determinada ordem, como
ordem crescente ou decrescente, de acordo com um critério de comparação
definido. Esses métodos são amplamente aplicados em programação e são
essenciais para organizar e processar eficientemente grandes conjuntos de
dados.
Quando temos uma base de dados, muitas vezes, é necessário ordenar essa
base. Assim, ordenar conjuntos de coisas é uma tarefa costumeira em nossa
vida e no desenvolvimento de sistemas. Em se tratando de computadores, como
geralmente lidamos com grandes volumes de dados, é desejável ter alguma
ordem nesses dados para facilitar sua manipulação (PUGA; RISSETTI, 2008).
Figura 1 - Exemplo de Ordenação.
Fonte: https://ww2.inf.ufg.br/~hebert/disc/aed1/AED1_04_ordenacao1.pdf
Existem diversos métodos de ordenação disponíveis, cada um com suas
próprias características e complexidades. Vou explicar brevemente dois
exemplos populares:
1. Bubble Sort:
• O Bubble Sort é um método de ordenação simples e intuitivo.
• Ele compara pares adjacentes de elementos e os troca se
estiverem fora de ordem, movendo gradualmente os elementos
maiores para o final da lista.
• O processo é repetido até que a lista esteja completamente
ordenada.
• Complexidade Big O: O(n2) ou O(n^2) no pior caso, onde n é o
número de elementos a serem ordenados.
2. Merge Sort:
• O Merge Sort é um método de ordenação baseado no princípio
"dividir para conquistar".
• Ele divide a lista em pequenos subconjuntos, ordena cada
subconjunto e depois mescla-os para obter a lista ordenada final.
• É um algoritmo eficiente para grandes conjuntos de dados e possui
uma complexidade mais estável.
• Complexidade Big O: O(n log n) no pior caso, onde n é o número
de elementos a serem ordenados.
É importante ressaltar que a escolha do método de ordenação adequado
depende do tamanho do conjunto de dados, da natureza dos elementos e dos
requisitos específicos do problema. Diferentes métodos têm diferentes
desempenhos em termos de tempo de execução e uso de recursos.
Além desses exemplos, existem muitos outros métodos de ordenação, como
Insertion Sort, Selection Sort, Quick Sort, Heap Sort, entre outros, cada um com
suas próprias características e complexidades. A escolha do método mais
adequado dependerá das necessidades e restrições do projeto em questão.
3.2 Bubble sort
O Bubble Sort é um algoritmo de ordenação simples e intuitivo. Ele percorre
repetidamente uma lista de elementos comparando pares adjacentes e trocando-
os se estiverem fora de ordem. O processo é repetido até que a lista esteja
completamente ordenada (DROZDEK, 2016).
O funcionamento do Bubble Sort pode ser explicado da seguinte forma:
1. Começando do início da lista, o algoritmo compara o primeiro elemento
com o segundo elemento.
2. Se o primeiro elemento for maior que o segundo, os elementos são
trocados de posição.
3. O algoritmo então passa para o próximo par de elementos adjacentes
(segundo e terceiro) e continua realizando as comparações e trocas,
percorrendo toda a lista.
4. Esse processo é repetido até que nenhuma troca seja necessária em
uma passagem completa pela lista. Isso indica que a lista está ordenada.
5. O algoritmo repete as passagens pela lista até que não sejam mais
necessárias trocas, garantindo que a lista esteja totalmente ordenada.
A complexidade do Bubble Sort é determinada pelo número de comparações e
trocas que ele precisa realizar. No pior caso, quando a lista está totalmente
desordenada, o algoritmo precisa percorrer a lista várias vezes para ordená-la
corretamente. A complexidade Big O do Bubble Sort é O(n^2), onde n é o número
de elementos a serem ordenados.
Essa complexidade quadrática indica que o tempo de execução do Bubble Sort
aumenta quadraticamente à medida que o tamanho da lista de elementos
aumenta. Portanto, o Bubble Sort não é eficiente para grandes conjuntos de
dados, pois seu desempenho pode ser significativamente lento. No entanto, para
listas pequenas ou quase ordenadas, o Bubble Sort pode ser uma opção viável
devido à sua simplicidade e implementação direta.
3.2.1 Implementação do Bubble Sort
Segue a implementação da função Bubble Sort:
Tabela 1 - Implementação do Bubble Sort.
# importação da biblioteca numpy
import numpy as np
def bubble_sort(vetor):
n = len(vetor)
for i in range(n):
for j in range(0, n - i - 1):
if vetor[j] > vetor[j + 1]:
temp = vetor[j]
vetor[j] = vetor[j + 1]
vetor[j + 1] = temp
return vetor
O script apresenta a implementação do algoritmo Bubble Sort em Python. Vamos
analisar o código passo a passo:
1. A função bubble_sort recebe um vetor como parâmetro,
representando a lista de elementos a serem ordenados.
2. O comprimento do vetor é armazenado na variável `n` usando a função
`len(vetor)`.
3. Em seguida, temos dois loops aninhados. O primeiro loop, `for i in
range(n)`, é responsável por percorrer o vetor de forma iterativa.
4. Dentro do primeiro loop, temos o segundo loop, `for j in range(0, n - i -
1)`, que percorre o vetor do início até a posição atual do primeiro loop.
5. Dentro do segundo loop, comparamos o elemento atual `vetor[j]` com o
próximo elemento `vetor[j + 1]`. Se o elemento atual for maior que o
próximo elemento, ocorre uma troca entre eles. Isso é feito através das
linhas:
temp = vetor[j]
vetor[j] = vetor[j + 1]
vetor[j + 1] = temp
Essa troca é realizada para colocar os elementos em ordem crescente.
Caso a ordem desejada seja decrescente, a comparação deve ser
modificada para `vetor[j] < vetor[j + 1]`.
6. O processo de comparação e troca é repetido até que o segundo loop
termine de percorrer todo o vetor.
7. Após o término dos loops, o vetor é retornado pela função,
representando a lista ordenada.
O algoritmo Bubble Sort implementado segue a lógica básica do algoritmo,
percorrendo o vetor e realizando comparações e trocas entre elementos
adjacentes até que a lista esteja totalmente ordenada. No entanto, é importante
mencionar que o código poderia ser otimizado para evitar trocas desnecessárias
caso a lista já esteja ordenada antes de completar todas as iterações.
3.2.2 Testes no Bubble Sort
Vamos testar a execução de um caso normal de vetor que necessita ser
ordenado e o pior casos, onde o vetor está com dados invertidos.
Figura 2 - Testes no Bubble Sort.
Fonte: Autoria Própria.
3.3 Selection sort
O algoritmo de ordenação Selection Sort é um método simples e intuitivo para
ordenar uma lista de elementos. Ele funciona selecionando repetidamente o
menor elemento da lista e colocando-o na posição correta (DROZDEK, 2016).
Figura 3 - Ordenação Selection Sort.
Fonte: https://www.hackerearth.com/practice/algorithms/sorting/selection-sort/tutorial/
Aqui está uma descrição do algoritmo do Selection Sort:
1. Começando do início da lista, o algoritmo encontra o menor elemento
e o coloca na primeira posição.
2. Em seguida, ele avança para a segunda posição e encontra o menor
elemento entre os elementos restantes e o coloca na segunda posição.
3. O processo continua até que todos os elementos estejam em suas
posições corretas.
O Selection Sort é um algoritmo de comparação e troca. Em cada iteração, ele
busca o menor elemento a partir da posição atual e o troca com o elemento na
posição atual. Isso garante que, após cada iteração, o menor elemento esteja na
posição correta.
A complexidade do Selection Sort é O(n2) ou O(n^2), onde "n" é o número de
elementos na lista. Isso ocorre porque o algoritmo possui dois loops aninhados:
um loop externo que percorre a lista inteira e um loop interno que encontra o
menor elemento. Portanto, em cada iteração do loop externo, o loop interno
executa "n" comparações.
Apesar de sua simplicidade, o Selection Sort não é eficiente para grandes
conjuntos de dados. Ele requer um número considerável de comparações e
trocas, mesmo quando a lista já está parcialmente ordenada. No entanto, o
algoritmo pode ser útil em casos onde a quantidade de dados é pequena ou
quando a minimização do número de trocas é uma consideração importante.
3.3.1 Implementação do Selection sort
Segue a implementação função Selection sort:
Tabela 2 - Implementação do Selection sort
# importação da biblioteca numpy
import numpy as np
def selection_sort(vetor):
n = len(vetor)
for i in range(n):
id_minimo = i
for j in range(i + 1, n):
if vetor[id_minimo] > vetor[j]:
id_minimo = j
temp = vetor[i]
vetor[i] = vetor[id_minimo]
vetor[id_minimo] = temp
return vetor
O script apresentado implementa o algoritmo de ordenação Selection Sort.
Vamos analisá-lo em detalhes:
1. A função selection_sort recebe um vetor como parâmetro, que é a lista
de elementos a serem ordenados.
2. A variável `n` recebe o tamanho do vetor, ou seja, o número de
elementos na lista.
3. Em seguida, inicia-se um loop externo que percorre a lista do início até
o penúltimo elemento. Esse loop é controlado pela variável `i`, que
representa a posição atual.
4. Dentro do loop externo, é definida a variável `id_minimo` com o valor
de `i`. Essa variável é responsável por armazenar o índice do menor
elemento encontrado durante a busca.
5. Em seguida, inicia-se um loop interno que percorre os elementos a
partir da posição `i + 1` até o último elemento. Esse loop é controlado pela
variável `j`.
6. Dentro do loop interno, compara-se o elemento na posição `id_minimo`
com o elemento na posição `j`. Se o elemento em `id_minimo` for maior
que o elemento em `j`, atualiza-se `id_minimo` para `j`, indicando que
encontrou-se um novo menor elemento.
7. Após percorrer todos os elementos do loop interno, temos o índice do
menor elemento a partir da posição `i`. Realiza-se então uma troca entre
o elemento na posição `i` e o elemento no índice `id_minimo`. Isso garante
que o menor elemento seja colocado na posição correta.
8. O processo se repete até que todos os elementos estejam em suas
posições corretas. Após o término do loop externo, o vetor estará
ordenado.
9. Por fim, a função retorna o vetor ordenado.
A complexidade do Selection Sort é O(n^2), onde "n" é o número de elementos
no vetor. Isso ocorre devido aos dois loops aninhados, onde o loop externo
executa "n" iterações e o loop interno executa "n - i" iterações a cada passo. No
pior caso, são feitas comparações e trocas em todos os elementos. Apesar de
não ser um algoritmo eficiente para grandes conjuntos de dados, o Selection Sort
é simples de implementar e adequado para situações em que a quantidade de
elementos é pequena.
3.3.2 Testes do Selection sort
Vamos testar a execução de um caso normal de vetor que necessita ser
ordenado e o pior casos, onde o vetor está com dados invertidos.
Figura 4 - Testes do Selection sort
Fonte: Autoria Própria.
3.4 Insertion sort
O algoritmo Insertion Sort é um método de ordenação simples que percorre uma
lista de elementos e, a cada iteração, insere o elemento atual na posição correta
dentro da porção ordenada da lista. Ele recebe esse nome porque o processo
de inserção se assemelha a inserir uma carta em um baralho ordenado
(DROZDEK, 2016).
Figura 5 - Algoritmo Insertion Sort.
Fonte: https://www.hackerearth.com/practice/algorithms/sorting/insertion-sort/tutorial/
Vamos entender o funcionamento passo a passo:
1. O algoritmo inicia percorrendo a lista a partir do segundo elemento. Isso
porque, inicialmente, considera-se que o primeiro elemento já está na
posição correta.
2. O elemento atual é armazenado em uma variável temporária.
3. Em seguida, o algoritmo faz uma comparação entre o elemento atual e
os elementos anteriores a ele na lista. Ele percorre esses elementos da
direita para a esquerda até encontrar a posição correta para inserir o
elemento atual.
4. Durante essa comparação, se um elemento anterior for maior do que o
elemento atual, esse elemento é movido uma posição para a direita para
abrir espaço para a inserção do elemento atual. Isso é repetido até que
se encontre a posição correta.
5. Ao encontrar a posição correta, o elemento atual é inserido nesse
ponto.
6. O processo é repetido para o próximo elemento não ordenado na lista,
até que todos os elementos estejam em suas posições corretas.
A complexidade do Insertion Sort é O(n2) ou O(n^2), onde "n" é o número de
elementos na lista. No pior caso, em que a lista está ordenada de forma inversa,
cada elemento precisa percorrer todos os elementos anteriores para encontrar
sua posição correta. No entanto, em casos em que a lista já está quase ordenada
ou possui um tamanho pequeno, o Insertion Sort pode ser uma opção eficiente.
Vale ressaltar que o Insertion Sort tem uma boa performance para listas
pequenas e para listas que já estão parcialmente ordenadas.
3.4.1 Implementação do Insertion Sort
Segue a implementação Insertion Sort:
Tabela 3 - Implementação do Insertion Sort
# importação da biblioteca numpy
import numpy as np
def insertion_sort(vetor):
n = len(vetor)
for i in range(1, n):
marcado = vetor[i]
j = i - 1
while j >= 0 and marcado < vetor[j]:
vetor[j + 1] = vetor[j]
j -= 1
vetor[j + 1] = marcado
return vetor
O script acima implementa o algoritmo de ordenação Insertion Sort.
Vamos analisar o funcionamento do código:
1. O código começa importando a biblioteca NumPy.
2. Em seguida, temos a definição da função `insertion_sort`, que recebe
como parâmetro um vetor a ser ordenado.
3. É obtido o tamanho do vetor.
4. Inicia-se um loop que percorre o vetor a partir do segundo elemento até
o último. Esse loop representa as iterações do algoritmo de ordenação.
5. Para cada iteração, o valor do elemento atual é armazenado em uma
variável chamada `marcado`.
6. Cria-se uma variável `j` que aponta para o elemento anterior ao
elemento atual.
7. Entra-se em um loop enquanto `j` é maior ou igual a zero e o valor do
`marcado` é menor do que o valor do elemento apontado por `j`. Esse loop
é responsável por encontrar a posição correta para inserir o `marcado` no
vetor ordenado.
8. Dentro do loop, o elemento apontado por `j` é deslocado uma posição
para frente, abrindo espaço para a inserção do `marcado`.
9. A variável `j` é decrementada para continuar a verificação com o
elemento anterior.
10. Ao final do loop, a posição correta para inserir o `marcado` é
encontrada, e ele é atribuído à posição `j + 1` do vetor.
11. O processo se repete para cada iteração do primeiro loop, resultando
na inserção ordenada de todos os elementos do vetor.
12. Ao final das iterações, o vetor estará ordenado, e a função retorna o
vetor ordenado.
O algoritmo de Insertion Sort é eficiente para vetores pequenos ou parcialmente
ordenados, mas sua complexidade é O(n^2) no pior caso, onde "n" é o número
de elementos no vetor.
3.5 Shell sort
O Shell Sort foi desenvolvido por Donald Shell em 1959 e é considerado um dos
primeiros algoritmos de ordenação eficientes. Donald Shell era um engenheiro
de petróleo que estava trabalhando em problemas de classificação de registros
em cartões perfurados, que eram amplamente utilizados na época para
armazenamento de dados (DROZDEK, 2016).
O objetivo de Shell era melhorar o desempenho dos algoritmos de ordenação
existentes, como o Bubble Sort e o Insertion Sort, que eram ineficientes para
conjuntos de dados maiores. Shell observou que a troca de elementos distantes
uns dos outros poderia ser mais eficiente do que a troca de elementos
adjacentes.
Figura 6 - Shell Sort.
https://www.javatpoint.com/shell-sort
Assim, ele desenvolveu uma abordagem inovadora em que os elementos do
vetor eram ordenados em subgrupos com base em um intervalo ou "salto". O
algoritmo inicialmente percorria o vetor com saltos maiores, o que permitia que
elementos distantes fossem comparados e trocados. Em seguida, o intervalo era
reduzido progressivamente, e o algoritmo executava um passo de Insertion Sort
em cada subgrupo até que o vetor estivesse totalmente ordenado.
O vetor é percorrido em subgrupos determinados pelo intervalo. Em cada
subgrupo, é aplicado o Insertion Sort para ordenar os elementos dentro desse
subgrupo.
O funcionamento do Shell Sort é o seguinte:
1. O algoritmo seleciona um intervalo ou "salto" inicial, geralmente metade
do tamanho do vetor. Esse intervalo é reduzido a cada iteração.
2. O vetor é percorrido em subgrupos determinados pelo intervalo. Em
cada subgrupo, é aplicado o Insertion Sort para ordenar os elementos
dentro desse subgrupo.
3. À medida que as iterações prosseguem, o intervalo é reduzido, o que
permite que elementos distantes uns dos outros sejam comparados e
trocados de posição.
4. O processo continua até que o intervalo se torne 1, momento em que o
algoritmo executa um último passo de Insertion Sort no vetor completo.
5. Ao final das iterações, o vetor estará ordenado.
A complexidade do Shell Sort depende do intervalo utilizado. A complexidade
média do Shell Sort é difícil de ser determinada com precisão, pois varia de
acordo com a sequência de intervalos utilizada. No entanto, a complexidade do
pior caso é geralmente considerada como O(n^2), onde "n" é o número de
elementos no vetor.
Apesar disso, o Shell Sort é conhecido por ter um desempenho melhor do que
outros algoritmos de ordenação quadráticos, como o Bubble Sort e o Insertion
Sort, especialmente quando aplicado a vetores de tamanho médio ou grande.
3.4.1 Implementação do Shell Sort
Segue a implementação Shell Sort:
Tabela 4 - Implementação do Shell Sort
# importação da biblioteca numpy
import numpy as np
def shell_sort(vetor):
intervalo = len(vetor) // 2
while intervalo > 0:
for i in range(intervalo, len(vetor)):
temp = vetor[i]
j = i
while j >= intervalo and vetor[j - intervalo] > temp:
vetor[j] = vetor[j - intervalo]
j -= intervalo
vetor[j] = temp
intervalo //= 2
return vetor
O script apresenta a implementação do algoritmo Shell Sort em Python.
O Shell Sort é uma variação do algoritmo de ordenação por inserção (Insertion
Sort) que visa melhorar a eficiência desse algoritmo em vetores maiores. Ele
utiliza a estratégia de dividir o vetor em subgrupos menores e aplicar o algoritmo
de ordenação por inserção em cada um desses subgrupos. Esses subgrupos
são criados definindo um intervalo inicial, que é gradualmente reduzido até que
o vetor seja completamente ordenado.
Aqui está o funcionamento do script:
1. A função `shell_sort` recebe um vetor como entrada.
2. A variável `intervalo` é inicializada como a metade do tamanho do vetor.
3. Um loop while é iniciado para iterar enquanto o `intervalo` for maior que
zero.
4. Dentro do loop, um loop for é usado para percorrer o vetor a partir do
`intervalo` até o final.
5. O valor atual é armazenado em `temp` para ser comparado e inserido
corretamente na subsequência ordenada.
6. Um loop while é usado para comparar o valor atual com os elementos
anteriores na subsequência ordenada.
7. Se o valor anterior for maior do que o valor atual, o elemento é
deslocado para a direita.
8. O loop while continua até que o valor atual seja maior ou igual ao
elemento anterior ou que não haja mais elementos anteriores na
subsequência.
9. Após a conclusão do loop while, o valor atual é inserido na posição
correta na subsequência ordenada.
10. O loop for continua para o próximo elemento na subsequência.
11. Após a conclusão do loop for, o intervalo é reduzido pela metade
dividindo-o por 2.
12. O processo se repete até que o intervalo seja menor ou igual a zero.
13. O vetor ordenado é retornado como resultado.
A complexidade de tempo do Shell Sort depende da sequência de intervalos
escolhida, mas no pior caso é de aproximadamente O(n²), onde n é o tamanho
do vetor. No entanto, em média, o Shell Sort tem um desempenho melhor do que
algoritmos de ordenação quadráticos, como Bubble Sort e Insertion Sort.
3.6 Merge sort
O Merge Sort é um algoritmo de ordenação que foi desenvolvido por John von
Neumann em 1945. Ele é baseado no princípio "dividir para conquistar" e é
conhecido por sua eficiência e estabilidade (DROZDEK, 2016).
O funcionamento do Merge Sort envolve a divisão recursiva do vetor em
subvetores menores, ordenação desses subvetores e, em seguida, a fusão
(merge) dos subvetores ordenados para obter o vetor finalmente ordenado.
O processo de ordenação ocorre da seguinte forma:
1. Divisão: O vetor inicial é dividido ao meio repetidamente até que cada
subvetor contenha apenas um elemento. Isso é feito recursivamente até
que não seja mais possível dividir.
2. Ordenação: Em seguida, ocorre o processo de fusão e ordenação dos
subvetores. Os pares de subvetores adjacentes são mesclados e seus
elementos são comparados e rearranjados em ordem crescente. Esse
processo continua até que todos os subvetores sejam mesclados em um
único vetor ordenado.
3. Fusão: A fusão dos subvetores é feita comparando-se os elementos de
cada subvetor e copiando-os para um novo vetor, em ordem crescente.
Isso é feito usando dois ponteiros que percorrem os subvetores
comparando os elementos e inserindo-os corretamente no vetor final.
Figura 7 - Merge Sort.
Fonte: https://dotnettutorials.net/lesson/merge-sort-algorithm-in-csharp/
O Merge Sort é um algoritmo eficiente, com uma complexidade de tempo média
e pior caso de O(n log n), onde n é o tamanho do vetor. Essa complexidade é
obtida porque o algoritmo divide o vetor pela metade em cada recursão,
resultando em um número total de comparações e trocas de elementos
proporcional a n log n.
Além disso, o Merge Sort é um algoritmo estável, o que significa que ele preserva
a ordem relativa de elementos iguais durante o processo de ordenação. Isso é
importante em alguns cenários onde é necessário manter a ordem original dos
elementos.
No entanto, o Merge Sort requer espaço adicional para a criação de subvetores
temporários durante o processo de fusão, o que pode ser uma desvantagem em
termos de uso de memória, especialmente para vetores muito grandes.
3.6.1 Implementação do Merge Sort
Segue a implementação Merge Sort:
Tabela 5 - Implementação do Merge Sort.
# importação da biblioteca numpy
import numpy as np
def merge_sort(vetor):
if len(vetor) > 1:
divisao = len(vetor) // 2
esquerda = vetor[:divisao].copy()
direita = vetor[divisao:].copy()
merge_sort(esquerda)
merge_sort(direita)
i = j = k = 0
# Ordena esquerda e direita
while i < len(esquerda) and j < len(direita):
if esquerda[i] < direita[j]:
vetor[k] = esquerda[i]
i += 1
else:
vetor[k] = direita[j]
j += 1
k += 1
# Ordenação final
while i < len(esquerda):
vetor[k] = esquerda[i]
i += 1
k += 1
while j < len(direita):
vetor[k] = direita[j]
j += 1
k += 1
return vetor
intervalo //= 2
return vetor
O script apresenta a implementação do algoritmo Merge Sort em Python.
O Merge Sort é um algoritmo de ordenação que utiliza a estratégia "dividir para
conquistar". Ele divide o vetor em subvetores menores, ordena esses subvetores
separadamente e depois mescla (merge) os subvetores ordenados para obter o
vetor final ordenado.
Aqui está o funcionamento do script:
1. A função `merge_sort` recebe um vetor como entrada.
2. Verifica se o vetor possui mais de um elemento, pois vetores com
tamanho 1 ou menor já são considerados ordenados.
3. Se o vetor possuir mais de um elemento, é feita a divisão do vetor em
duas partes.
4. A primeira metade do vetor é copiada para a variável `esquerda` e a
segunda metade é copiada para a variável `direita`.
5. Em seguida, chama-se recursivamente o `merge_sort` para ordenar
cada um dos subvetores `esquerda` e `direita`.
6. Após as chamadas recursivas, as variáveis `i`, `j` e `k` são inicializadas
como zero.
7. O loop while é usado para comparar os elementos dos subvetores
`esquerda` e `direita` e mesclá-los em ordem crescente no vetor original.
8. Enquanto houver elementos em ambos os subvetores, compara-se o
elemento `esquerda[i]` com o elemento `direita[j]`. O menor valor é
colocado no vetor original e o índice correspondente é incrementado.
9. Se ainda houver elementos restantes apenas no subvetor `esquerda`,
eles são copiados para o vetor original.
10. Se ainda houver elementos restantes apenas no subvetor `direita`,
eles também são copiados para o vetor original.
11. O vetor final ordenado é retornado.
A complexidade de tempo do Merge Sort é sempre O(n log n), onde n é o
tamanho do vetor. Isso significa que o tempo de execução do algoritmo cresce
de forma proporcional ao número de elementos no vetor, multiplicado pelo
logaritmo do número de elementos. O Merge Sort é considerado um algoritmo
de ordenação eficiente e é amplamente utilizado na prática.
4.7 Quick sort
Segundo Drozdek (2016), o Quick Sort é um algoritmo de ordenação
desenvolvido por Tony Hoare em 1959. Ele é amplamente conhecido e utilizado
devido à sua eficiência e desempenho em grande parte dos casos. O Quick Sort
também é um exemplo clássico de algoritmo "dividir para conquistar", onde um
problema é dividido em subproblemas menores e, em seguida, combinados para
obter a solução.
O funcionamento do Quick Sort é descrito a seguir:
1. Escolhe-se um elemento do vetor, chamado de "pivô". Geralmente, o
pivô é escolhido como o último elemento do vetor, mas existem várias
estratégias possíveis para a escolha do pivô.
2. Os elementos do vetor são particionados de forma que todos os
elementos menores que o pivô fiquem à sua esquerda e todos os
elementos maiores fiquem à sua direita.
3. O pivô é colocado na sua posição correta no vetor, de forma que ele
esteja ordenado em relação aos elementos à sua esquerda e à sua direita.
4. O passo 2 e o passo 3 são aplicados recursivamente nos subvetores à
esquerda e à direita do pivô, até que o vetor esteja completamente
ordenado.
A escolha eficiente do pivô é crucial para o desempenho do Quick Sort. Diversas
estratégias podem ser utilizadas, como escolher o pivô como o elemento do
meio, escolher o pivô aleatoriamente, ou até mesmo utilizar algoritmos mais
sofisticados para a seleção do pivô, como o algoritmo de mediana das três.
A complexidade de tempo do Quick Sort depende da escolha do pivô e da
distribuição dos elementos no vetor. No melhor caso, quando o pivô divide o
vetor em duas partes de tamanhos aproximadamente iguais, a complexidade é
O(n log n), onde n é o tamanho do vetor. No pior caso, quando o pivô é escolhido
de forma inadequada e divide o vetor em uma parte muito pequena e outra muito
grande, a complexidade pode chegar a O(n^2). No entanto, o caso médio do
Quick Sort é geralmente muito eficiente, com complexidade O(n log n).
Figura 8 - Quick Sort.
Fonte: https://favtutor.com/blogs/quick-sort-cpp
Apesar de possuir o pior caso quadrático, o Quick Sort é muito utilizado na
prática devido ao seu desempenho médio superior a outros algoritmos de
ordenação. Além disso, é um algoritmo in-place, ou seja, não requer espaço
adicional para ordenar o vetor.
3.7.1 Implementação do Quick Sort
Segue a implementação Quick Sort
Tabela 6 - Implementação do Quick Sort.
# importação da biblioteca numpy
import numpy as np
def particao(vetor, inicio, final):
pivo = vetor[final]
i = inicio - 1
for j in range(inicio, final):
if vetor[j] <= pivo:
i += 1
# Troca (evita a variável temp)
vetor[i], vetor[j] = vetor[j], vetor[i]
# troca do pivo para posição dele
vetor[i + 1], vetor[final] = vetor[final], vetor[i + 1]
return i + 1
def quick_sort(vetor, inicio, final):
if inicio < final:
posicao = particao(vetor, inicio, final)
# Esquerda
quick_sort(vetor, inicio, posicao - 1)
# Direito
quick_sort(vetor, posicao + 1, final)
return vetor
O script apresenta uma implementação do algoritmo de ordenação Quick Sort.
Vamos analisar o funcionamento do código:
1. A função `particao` recebe um vetor, um índice de início e um índice
de final, representando a porção do vetor a ser particionada. Ela seleciona
o pivô como o elemento final do vetor e realiza a partição dos elementos
em relação ao pivô.
2. A variável `pivo` é atribuída ao valor do pivô, que é o elemento final do
vetor.
3. A variável `i` é inicializada como o índice anterior ao início da partição.
4. O loop `for` percorre os elementos do vetor, da posição `inicio` até a
posição `final - 1`.
5. Se o elemento atual (`vetor[j]`) for menor ou igual ao pivô, incrementa-
se `i` e realiza-se a troca entre `vetor[i]` e `vetor[j]`. Essa troca garante
que os elementos menores que o pivô fiquem à sua esquerda.
6. Após o loop, realiza-se a troca entre o pivô (`vetor[final]`) e o elemento
seguinte a `i`. Essa troca posiciona o pivô na posição correta, garantindo
que ele esteja ordenado em relação aos elementos à sua esquerda e à
sua direita.
7. A função retorna o índice da posição final do pivô.
8. A função `quick_sort` é a implementação recursiva do algoritmo Quick
Sort.
9. Ela recebe o vetor, o índice de início e o índice de final da porção a ser
ordenada.
10. Verifica-se se o início é menor que o final, o que indica que ainda há
elementos para serem ordenados.
11. Chama-se a função `particao` para obter a posição correta do pivô.
12. Em seguida, realiza-se a chamada recursiva do `quick_sort` para os
subvetores à esquerda (do início até a posição do pivô - 1) e à direita (da
posição do pivô + 1 até o final).
13. Por fim, a função retorna o vetor ordenado.
A complexidade de tempo do Quick Sort depende da escolha do pivô e da
distribuição dos elementos no vetor. No melhor caso, quando o pivô divide o
vetor em duas partes de tamanhos aproximadamente iguais, a complexidade é
O(n log n), onde n é o tamanho do vetor. No pior caso, quando o pivô é escolhido
de forma inadequada e divide o vetor em uma parte muito pequena e outra muito
grande, a complexidade pode chegar a O(n^2). No entanto, o caso médio do
Quick Sort é geralmente muito eficiente, com complexidade O(n log n).
FINALIZAR
A estrutura de dados é uma área fundamental da ciência da computação que
estuda como organizar e manipular dados de forma eficiente. Ela envolve o
estudo de diferentes tipos de estruturas e algoritmos que permitem armazenar,
gerenciar e acessar dados de maneira otimizada, levando em consideração
fatores como tempo de execução, espaço em memória e facilidade de
implementação.
Durante os estudos de estrutura de dados, os alunos tiveram a oportunidade de
aprender sobre uma variedade de estruturas, como listas, pilhas, filas, árvores,
grafos e muitas outras. Eles entenderam os conceitos fundamentais por trás
dessas estruturas, bem como as operações e algoritmos associados a elas.
Ao explorar essas estruturas, os alunos puderam compreender as vantagens e
desvantagens de cada uma e quando aplicá-las em diferentes situações. Eles
também tiveram a oportunidade de implementar essas estruturas em linguagens
de programação, fortalecendo suas habilidades de programação e resolução de
problemas.
A estrutura de dados é essencial para o desenvolvimento de software eficiente,
escalável e de alta qualidade. Ela permite que os programadores organizem e
manipulem dados de forma eficiente, otimizem algoritmos e resolvam problemas
complexos. Dominar os conceitos e as técnicas de estrutura de dados é
fundamental para se tornar um programador competente e capaz de criar
soluções eficazes.
Gostaria de agradecer a todos os alunos pela participação nos estudos de
estrutura de dados. Seu envolvimento e dedicação são fundamentais para o
aprendizado e o aprimoramento de habilidades técnicas. Espero que os
conhecimentos adquiridos nesse curso sejam valiosos e que possam ser
aplicados em suas futuras carreiras!
Me. Ricardo de Andrade Kratz.
Sobre o autor
Ricardo de Andrade Kratz possui graduação em Ciência da Computação pela
Pontifícia Universidade Católica de Goiás (2000) e mestrado pela Universidade
do Vale do Rio dos Sinos (2006). Atualmente, é professor universitário do
Instituto de Pós-graduação e Graduação (IPOG), Faculdade Unidas de
Campinas (FacUnicamps) e na Pós-graduação Universidade Federal de Goiás
(UFG), entre outras instituições. Atua como assessor da Assembleia Legislativa
de Goiás (ALEGO). Foi Ex-Gerente de Tecnologia da Informação da Secretaria
de Ciência e Tecnologia do Estado de Goiás (SECTEC-GO). Tem experiência
na área de Ciência da Computação e Desenvolvimento de Sistemas, com ênfase
em Engenharia de Software, Data Science, Ferramentas de Engenharia e Rede
de Computadores, atuando principalmente nos seguintes temas: Engenharia de
Software, Design Patterns, Sistema de Informação, Inovação Tecnológica
Aplicada, Ferramentas de Gestão, Tecnologias Aplicadas à Engenharia,
Automação, Governança em TI, Tecnologia da Informação e Redes de
Computadores.
Referências Bibliográficas
DEGEN, Ronald. Aprenda Programação Orientada a Objetos em 21 dias. 1a
Edição. Pearson, 2002.
DROZDEK, Adam. Estrutura de dados e algoritmos em c++. 2a Edição.
Cengage Learning, 2016.
FORBELLONE, André Luiz Villar; EBERSPACHER, Henri Frederico. Lógica de
Programação: a construção de algoritmos e estruturas de dados. 3a Edição.
Pearson, 2005.
GUEDES, Sérgio. Lógica de Programação Algorítmica. 1a Edição. Pearson,
2015.
MENEZES, Nilo Ney Coutinho. Introdução à Programação com Python:
Algoritmos e Lógica de Programação Para Iniciantes. 1a Edição. Novatec, 2019.
PUGA, Sandra, RISSETTI, Gerson. Lógica de Programação e Estruturas de
Dados: com aplicações em Java. 2a Edição. Pearson, 2008.
RAMALHO, Luciano. Python Fluente: Programação Clara, Concisa e Eficaz. 1a
Edição. Novatec, 2015.