1. SOCIEDADE EDUCACIONAL DE SANTA CATARINA
INSTITUTO SUPERIOR TUPY
ENGENHARIA DE COMPUTAÇÃO
RELATÓRIO DE DESENVOLVIMENTO
DE ALGORITMOS DE ORDENAÇÃO EM C#.NET
Aluno: Lorival Smolski Chapuis
Turma: ECP-341
Professor: Glauco Vinicius Scheffel
Joinville, 9 de abril de 2011
2. Sumário
1. Introdução ............................................................................................................................. 3
2. Ferramentas e métodos utilizados ........................................................................................ 4
3. Algoritmos de ordenação ...................................................................................................... 5
3.1. BubbleSort ..................................................................................................................... 6
3.2. SelectionSort ................................................................................................................. 9
3.3. InsertionSort................................................................................................................ 12
3.4. MergeSort ................................................................................................................... 15
3.5. QuickSort ..................................................................................................................... 21
4. Execução dos testes automatizados ................................................................................... 24
5. Estatísticas entre os algoritmos de ordenação ................................................................... 27
6. Conclusão ............................................................................................................................ 30
3. 1. Introdução
Este relatório tem como objetivo detalhar o desenvolvimento dos algoritmos de
ordenação bubblesort, selectionsort, insertionsort, mergesort e quicksort.
Os algoritmos foram desenvolvidos em C# utilizando a IDE Visual Studio 2010, .Net
Framework 4.0, testes automatizados e orientação a aspectos.
A solução criada possui dois projetos. Um deles é responsável por implementar os
algoritmos de ordenação e o outro responsável apenas pelos testes.
Ao apresentar cada algoritmo de ordenação, é resumido seu funcionamento e
detalhado o código fonte em C#, que aparece logo em seguida, juntamente com os
códigos dos testes automatizados.
Ao final foram feitas estatísticas de desempenho comparando os algoritmos,
descritos acima, para ver o tempo de ordenação, de cada um, em um vetor de mil
posições.
Todos os códigos fontes estão em formas de texto exceto por duas imagens, prints
dos códigos. Isto acontece, pois foi utilizado um recurso chamado “region” que
permite separar e organizar melhor o código, podendo ocultar ou exibir trechos de
códigos. Os prints foram tirados para mostrar o código desejado juntamente com os
regions “fechados”, que serão “abertos” e explicados na sequência.
4. 2. Ferramentas e métodos utilizados
Para o desenvolvimento dos algoritmos foi utilizado o seguinte ambiente:
Microsoft Visual Studio 2010;
Microsoft .Net Framework 4.0 com a linguagem C#;
Testes automatizados;
Orientação a aspectos;
Os testes automatizados foram de dois tipos unitários e integrados. Isto devido à
complexidade de testar alguns métodos e de o autor não querer colocar mocks para
não complicar mais os programas.
A programação orientada a aspectos – POA – é um paradigma de desenvolvimento
que possibilita implementar funcionalidades que se encontram espalhadas por toda
aplicação (crosscutting concern) de forma encapsulada e modularizada.
Dentro do .Net Framework um dos princípios do POA é chamado de extension
methods que consiste em desenvolver um algoritmo separado de seu objeto de uso e
armazená-lo junto aos demais métodos do Framework, para ser usado a qualquer
momento de qualquer lugar da solução que utilize a biblioteca em questão.
Um exemplo de extension method, seria criar um método chamado ToShortString()
para conversão de um datetime (1/12/2999 00:00:00) para uma data curta
(01/12/20999). Ao criar este método informamos que será aplicado ao tipo datetime,
logo os objetos datetime terão mais um método (ToShortString()) podendo ser usado
de qualquer parte da solução e a qualquer momento. Exemplo de uso do método
acima:
DateTime date = DateTime.Now;
String shortDate = date.ToShortString();
Se não utilizar um extension method, o modo convencional seria criar um método
que recebe-se como parâmetro um datetime e retorna-se uma string. Como é apenas
um algoritmo de conversão, poderia estar em uma classe estática chamada
DateConvertionHelper. Veja um exemplo de uso:
DateTime date = DateTime.Now;
String shortDate = DateConvertionHelper.ToShortString(date);
Usar um extension method torna muito mais simples a utilização final.
5. 3. Algoritmos de ordenação
Para não tornar o relatório longo e ser o mais objetivo possível, cada tópico de
algoritmo será dividido em 3 partes:
Objetivo;
Algoritmo;
Testes.
Para mais informações sobre os testes, veja o item “4. Execução dos testes
automatizados”.
Foi criada uma classe chamada SortingAlgorithms que contém os 6 algoritmos de
ordenação. O escopo desta classe está abaixo e o conteúdo dos “regions” estão
detalhados em cada algoritmo.
6. 3.1. BubbleSort
Objetivo: Percorrer o vetor diversas vezes e a cada passagem fazer flutuar para o final
do vetor o item de maior valor. A cada passagem é analisado a quantidade de itens do
vetor menos um, pois o último já estará ordenado.
Algoritmo:
#region BubbleSort
public static void BubbleSort(this IList<int> list)
{
int lastListPosition = list.Count - 1;
bool hasChanged = true;
while (hasChanged)
{
hasChanged = false;
for (int i = 0; i < lastListPosition; i++)
{
if (list[i] > list[i + 1])
{
var aux = list[i];
list[i] = list[i + 1];
list[i + 1] = aux;
hasChanged = true;
}
}
lastListPosition--;
}
}
#endregion
9. 3.2. SelectionSort
Objetivo: Percorrer o vetor diversas vezes e a cada passagem trazer para a primeira
posição do vetor o item de menor valor. A cada passagem é analisado a quantidade de
itens do vetor menos um, pois o primeiro já estará ordenado. A grande diferença do
selectionsort para o bubblesort é que o bubble faz varias trocas a cada passagem e a
seleção procura o menor valor e faz apenas uma troca no final da passagem.
Algoritmo:
#region SelectionSort
public static void SelectionSort(this IList<int> list)
{
int lowestValuePosition = 0;
for (int lastPosition = 0; lastPosition < list.Count; lastPosition++)
{
lowestValuePosition = lastPosition;
for (int currentPosition = lastPosition; currentPosition < list.Count; currentPosition++)
{
if (list[currentPosition] < list[lowestValuePosition])
lowestValuePosition = currentPosition;
}
if (lowestValuePosition > lastPosition)
{
var aux = list[lastPosition];
list[lastPosition] = list[lowestValuePosition];
list[lowestValuePosition] = aux;
}
}
}
#endregion
12. 3.3. InsertionSort
Objetivo: Percorrer o vetor da esquerda para a direita e à medida que avança vai
deixando os elementos mais a esquerda ordenados. A cada passagem vai abrindo
espaço para o item corrente e procurando, mais a esquerda qual é o lugar daquele
item. Ao encontrar empurra todos os itens para a direita e insere o item selecionado
no seu respectivo local.
Algoritmo:
#region InsertionSort
public static void InsertionSort(this IList<int> list)
{
int valueWillBeInserted, lastPosition;
for (int currentPosition = 1; currentPosition < list.Count; currentPosition++)
{
valueWillBeInserted = list[currentPosition];
lastPosition = currentPosition - 1;
while (lastPosition >= 0 && list[lastPosition] > valueWillBeInserted)
{
list[lastPosition + 1] = list[lastPosition];
lastPosition--;
}
list[lastPosition + 1] = valueWillBeInserted;
}
}
#endregion
15. 3.4. MergeSort
Objetivo: Criar uma sequência ordenada baseada em outras duas também ordenadas.
Para isto é necessário dividir a sequência original até chegar a um limite ordenável por
um insertionSort (100 itens neste exemplo) ou até sobrarem pares para ordenar.
Depois vai agrupando todas as sequências divididas até formar a sequência ordenada.
Modo com InsertionSort e sem InsertionSort
Foi desenvolvido dois métodos de chamada. Um para mergeSort com
InsertionSort e outro sem InsertionSort. Ambos utilizam o mesmo algoritmo para
ordenação com a diferença de um parâmetro que informa se irá usar o algoritmo
InsertionSort quando chegar nos 100 itens ou não.
16. Algoritmo:
#region Private Methods
private static IList<int> ExecuteMergeSort(IList<int> list, bool sortWithInsertionSort)
{
if (sortWithInsertionSort)
{
if (list.Count <= 100)
{
list.InsertionSort();
return list;
}
}
else
if (list.Count <= 1)
return list;
int midlePosition = list.Count / 2;
IList<int> leftSide = new List<int>();
IList<int> rightSide = new List<int>();
for (int i = 0; i < midlePosition; i++)
leftSide.Add(list[i]);
for (int i = midlePosition; i < list.Count; i++)
rightSide.Add(list[i]);
return ExecuteMergeSort(ExecuteMergeSort(leftSide, sortWithInsertionSort),
ExecuteMergeSort(rightSide, sortWithInsertionSort));
}
private static IList<int> ExecuteMergeSort(IList<int> left, IList<int> right)
{
IList<int> listToBeReturned = new List<int>();
while (left.Count > 0 && right.Count > 0)
if (left[0] > right[0])
{
listToBeReturned.Add(right[0]);
right.RemoveAt(0);
}
else
{
listToBeReturned.Add(left[0]);
left.RemoveAt(0);
}
for (int i = 0; i < left.Count; i++)
listToBeReturned.Add(left[i]);
for (int i = 0; i < right.Count; i++)
listToBeReturned.Add(right[i]);
return listToBeReturned;
}
#endregion
21. 3.5. QuickSort
Objetivo: Ordena o vetor dividindo-o recursivamente, através de um pivô, colocando
os itens menores que o pivô de um lado e maiores do outro. Ao final os itens estarão
ordenados. Este é o algoritmo mais rápido e eficiente para ordenação para vetores
grandes.
Algoritmo:
#region QuickSort
public static void QuickSort(this IList<int> list)
{
Sort(list, 0, list.Count - 1);
}
#region Private Methods
private static void Sort(IList<int> list, int startPosition, int endPosition)
{
if (startPosition < endPosition)
{
int pivotPosition = Partition(list, startPosition, endPosition);
Sort(list, startPosition, pivotPosition - 1);
Sort(list, pivotPosition + 1, endPosition);
}
}
private static int Partition(IList<int> list, int startPosition, int endPosition)
{
int pivo = list[startPosition];
int startPartitionPosition = startPosition + 1, endPartitionPosition = endPosition;
while (startPartitionPosition <= endPartitionPosition)
{
if (list[startPartitionPosition] <= pivo)
startPartitionPosition++;
else if (pivo < list[endPartitionPosition])
endPartitionPosition--;
else
{
int troca = list[startPartitionPosition];
list[startPartitionPosition] = list[endPartitionPosition];
list[endPartitionPosition] = troca;
startPartitionPosition++;
endPartitionPosition--;
}
}
list[startPosition] = list[endPartitionPosition];
list[endPartitionPosition] = pivo;
return endPartitionPosition;
}
#endregion
#endregion
24. 4. Execução dos testes automatizados
Para facilitar os testes automatizados para grandes vetores foi criado uma classe chamada
NumericListCreator que tem como objetivo criar dois vetores iguais com a diferença que um
está ordenado e o outro não. Quando criamos a instância desta classe informamos o tamanho
do vetor que queremos e ela disponibiliza duas propriedades, cada uma sendo um vetor.
Para criar os dois vetores com os mesmos valores, foi utilizado um recurso do .Net
Framework. Na verdade é um objeto chamado SortedList, que nada mais é do que uma lista
que se ordena conforme vai incluindo os itens. Esta classe gera os vetores com no máximo cem
mil posições.
Veja abaixo a codificação desta classe:
using System.Collections;
using System.Collections.Generic;
using Lorival.Collections;
using System;
namespace Tester
{
internal class NumericListCreator
{
internal IList OrderedList { get; private set; }
internal IList<int> UnorderedList { get; private set; }
private const int limitGeneratedLists = 100000;
internal NumericListCreator(int listSize)
{
OrderedList = new List<int>();
UnorderedList = new List<int>();
if (listSize > limitGeneratedLists)
listSize = limitGeneratedLists;
SortedList sortedList = new SortedList();
while(sortedList.Count < listSize)
{
int randowNumber = new Random().Next(limitGeneratedLists);
if (!sortedList.ContainsKey(randowNumber))
{
sortedList.Add(randowNumber, null);
UnorderedList.Add(randowNumber);
}
}
OrderedList = sortedList.GetKeyList();
}
}
}
25. Todos os testes foram escritos seguindo um pouco de babysteps. Começam com
vetores de duas posições e vai aumentando até um vetor de 100 posições, gerado através do
objeto gerador de vetores já citado.
Foi criada uma classe abstrata para os testes. Esta tem como único objetivo armazenar
informações pertinentes ao framework de testes. Veja abaixo:
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Tester
{
public abstract class AbstractTester
{
/// <summary>
///Gets or sets the test context which provides
///information about and functionality for the current test run.
///</summary>
public TestContext TestContext { get;set;}
#region Additional test attributes
//
// You can use the following additional attributes as you write your tests:
//
// Use ClassInitialize to run code before running the first test in the class
// [ClassInitialize()]
// public static void MyClassInitialize(TestContext testContext) { }
//
// Use ClassCleanup to run code after all tests in a class have run
// [ClassCleanup()]
// public static void MyClassCleanup() { }
//
// Use TestInitialize to run code before running each test
// [TestInitialize()]
// public void MyTestInitialize() { }
//
// Use TestCleanup to run code after each test has run
// [TestCleanup()]
// public void MyTestCleanup() { }
//
#endregion
}
}
26. Abaixo segue o resultado da execução de todos os 36 testes.
27. 5. Estatísticas entre os algoritmos de ordenação
Abaixo segue tabelas comparando o tempo de execução dos algoritmos ordenando
vetores. Para a realização dos testes foi gerado um vetor com valores aleatórios e utilizado o
mesmo vetor para todos os algoritmos. O programa utilizado será explicado após as tabelas de
comparação.
Ordenando vetores com 10 itens
BubbleSort {00:00:00}
SelectionSort {00:00:00.0010001}
InsertionSort {00:00:00}
MergeSort {00:00:00.0020002}
MergeSortWithInsertionSort {00:00:00}
QuickSort {00:00:00.0010001}
Ordenando vetores com 100 itens
BubbleSort {00:00:00.0012501}
SelectionSort {00:00:00.0010001}
InsertionSort {00:00:00.0012501}
MergeSort {00:00:00.0012501}
MergeSortWithInsertionSort {00:00:00.0012501}
QuickSort {00:00:00.0012500}
Ordenando vetores com 1000 itens
BubbleSort {00:00:00.0179982}
SelectionSort {00:00:00.0109989}
InsertionSort {00:00:00.0069993}
MergeSort {00:00:00.0029997}
MergeSortWithInsertionSort {00:00:00.0019998}
QuickSort {00:00:00.0019998}
Ordenando vetores com 10000 itens
BubbleSort {00:00:01.6988304}
SelectionSort {00:00:01.0148985}
InsertionSort {00:00:00.6489351}
MergeSort {00:00:00.0749925}
MergeSortWithInsertionSort {00:00:00.0569943}
QuickSort {00:00:00.0059994}
28. Ordenando vetores com 50000 itens
BubbleSort {00:00:41.9553894}
SelectionSort {00:00:28.3195700}
InsertionSort {00:00:16.4596458}
MergeSort {00:00:01.2951295}
MergeSortWithInsertionSort {00:00:01.1681168}
QuickSort {00:00:00.0280028}
Para implementar os extensions methods (orientação a aspectos) é necessário utilizar
classes estáticas. Estas por sua vez não permite herdar de interfaces o que inviabiliza a
aplicação do command pattern.
Pela ótica de como foi desenvolvido, o projeto responsável por contemplar o programa
que gera as estatísticas foi o projeto de testes, visto que as estatísticas, neste caso, fazem
parte dos testes de avaliação do software.
Abaixo segue a classe que gerou as estatísticas apresentadas:
using System;
using System.Linq;
using Lorival.Collections;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Tester
{
[TestClass]
public class SpeedTester : AbstractTester
{
[TestMethod]
public void EffectTest()
{
DateTime startTime;
DateTime endTime;
startTime = DateTime.Now;
NumericListCreator numericListCreator = new NumericListCreator(50000);
endTime = DateTime.Now;
TimeSpan numericListCreatorSpeedTime = endTime - startTime;
startTime = DateTime.Now;
int[] bubble = numericListCreator.UnorderedList.ToArray<int>();
bubble.BubbleSort();
endTime = DateTime.Now;
TimeSpan bubbleSpeedTime = endTime - startTime;
//--
startTime = DateTime.Now;
int[] selection = numericListCreator.UnorderedList.ToArray<int>();
selection.SelectionSort();
endTime = DateTime.Now;
TimeSpan selectionSpeedTime = endTime - startTime;
//--
startTime = DateTime.Now;
30. 6. Conclusão
Os objetivos do trabalho foram alcançados com sucesso, ou seja, foi possível construir
todos os algoritmos e apresenta-los neste relatório juntamente com estatísticas de
desempenho de cada um.
O tempo aproximado total utilizado para desenvolvimento e geração deste relatório
foi de doze horas.
Os testes unitários foram imprescindíveis para a perfeita conclusão de todos os
algoritmos. Mesmo depois de concluído cada um, foi necessário passar por uma fase de
revisão e refinação o que permitiu serem feitas de forma muito mais rápidas e assertivas, visto
que existiam testes validando a ordenação de todos os algoritmos.
O maior problema encontrado foi na geração de um vetor não ordenado com valores
aleatórios para a realização dos testes. Como esta era uma responsabilidade do projeto de
testes foi possível utilizar o “SortedList” para facilitar o desenvolvimento, porém o tempo para
geração de um vetor de 50 mil posições sem valores repetidos foi de quase 20 minutos.
Existem várias outras formas de gerar este vetor, porém como não fazia parte dos objetivos
propostos e fazia parte apenas dos testes foi utilizada a forma mais rápida de desenvolver, que
implicou unicamente em ter maior tempo de espera na geração das estatísticas.
Por fim o trabalho foi de grande valia para entendimento, principalmente, das
diferenças de desempenho entre os algoritmos de ordenação e compreender que sempre
existem métodos mais eficientes de resolver um problema recorrente.