Ordenação de pares e ímpares

7

Click here to load reader

description

This article aims to describe two different approaches for resolving the odd and even ordering problem. The challenge showed by the problem consists in ordering a certain input set of values efficiently using ascending order for odd values and descending order for evens. The MergeSort and QuickSort algorithms were used at classification step, each of them applied in a distinct solution. During the text, the resolution strategy, the results and challenges experienced are described in details

Transcript of Ordenação de pares e ímpares

Page 1: Ordenação de pares e ímpares

Revista Brasileira de Computação Aplicada (ISSN 2176-6649), Passo Fundo, v. 5, n. 2, p. xx-xx, out. 2013 1

Ordenação de pares e ímpares

Diego Antonio Lusa1

Resumo: Este artigo visa descrever duas propostas de solução construídas para a resolução do

problema de ordenação de pares e ímpares. O desafio apresentado pelo problema consiste em

classificar determinado conjunto de entrada de modo eficiente, utilizando o critério crescente para

valores pares e decrescente para ímpares. Foram utilizados os algoritmos MergeSort e QuickSort

na etapa de ordenação, cada um aplicado a uma solução em específico. No decorrer do texto, a

estratégia de resolução, os resultados obtidos e desafios vivenciados são abordados

detalhadamente.

Palavras-chave: Ordenação. QuickSort. MergeSort.

Abstract: This article aims to describe two different approaches for resolving the odd and even

ordering problem. The challenge showed by the problem consists in ordering a certain input set of

values efficiently using ascending order for odd values and descending order for evens. The

MergeSort and QuickSort algorithms were used at classification step, each of them applied in a

distinct solution. During the text, the resolution strategy, the results and challenges experienced

are described in details.

Keywords: Sorting. QuickSort. MergeSort.

1 Introdução

Como parte da ementa da disciplina de Algoritmos e Estruturas de Dados do Mestrado em Computação

Aplicada da Universidade de Passo Fundo (UPF), foi proposto a resolução de um problema presente na ferramenta URI Online Judge, que fosse pertencente à categoria “Estruturas de Dados e Bibliotecas” e que

apresentasse nível de dificuldade maior ou igual a três.

Com base nos critérios acima, optou-se pela escolha do problema intitulado “Pares e Ímpares”. O desafio

proposto por este problema reside na ordenação de um conjunto de valores numéricos inteiros e positivos, de

cardinalidade c (onde 1 < c < 105), dentro de uma janela máxima de execução de 1 segundo.

O critério de ordenação proposto pelo problema exige que a paridade dos elementos seja considerada no

momento da classificação dos elementos. Em vista disso, o processo de ordenação deve obedecer a duas regras

fundamentais pré-definidas, que são:

Valores pares devem ser classificados em ordem crescente;

Valores ímpares devem ser classificados em ordem decrescente;

Para obter os elementos do conjunto de entrada, o algoritmo necessita respeitar um padrão de entrada de dados específico. Neste padrão, estipula-se que a primeira informação repassada ao algoritmo refere-se à

cardinalidade do conjunto de dados, ou seja, determina o número total de elementos que serão informados na

sequência.

Com base na cardinalidade (c) informada, o algoritmo deve recepcionar cada um dos elementos En

subsequentes (onde, 1<= n <= c) que irão compor o conjunto de entrada.

1 Mestrado em Computação Aplicada, UPF, Campus 1 - BR 285 - Passo Fundo (RS) - Brasil {[email protected]}

http://dx.doi.org/10.5335/rbca.2013.XXX

Page 2: Ordenação de pares e ímpares

Revista Brasileira de Computação Aplicada (ISSN 2176-6649), Passo Fundo, v. 5, n. 2, p. xx-xx, out. 2013 2

Por fim, o problema especifica ainda o formato da saída dos dados. A regra estabelecida neste caso é

apresentar um elemento a cada linha, considerando o seguinte protocolo:

Primeiramente os valores pares em ordem crescente;

Na sequência, valores ímpares, em ordem decrescente;

A Tab. 1 apresenta um exemplo dos formatos de entrada e saída definidos para o problema. No exemplo,

informa-se ao algoritmo um conjunto de oito elementos para ordenação (coluna “Entrada”). A saída esperada

após o processamento é apresentada na coluna “Saída”. Por fim, a primeira e a última coluna representam,

respectivamente, informações relevantes acerca das entradas e saída do algoritmo.

Tabela 1: Formato de entrada e saída de dados

Descrição Entrada Saída Descrição

Cardinalidade(c) 8 10

Pares em ordem

crescente E1 10 20

E2 15 30

E3 20 40

E4 25 45 Ímpares em

ordem

decrescente

E5 30 35

E6 35 25

E7 40 15

E8 45

Uma vez conhecidas as regras e formatos necessários à resolução do problema de ordenação de pares e

ímpares, foram desenvolvidas duas soluções distintas. Em ambas as soluções, contudo, o algoritmo de resolução

executa sequencialmente três etapas básicas, denominadas coleta de dados, ordenação e apresentação dos

resultados.

O que diferencia de fato uma solução da outra é o algoritmo de classificação utilizado na etapa de

ordenação. Na primeira solução desenvolvida, utilizou-se o QuickSort, enquanto que na segunda, o algoritmo

usado foi o MergeSort. As demais etapas (coleta de dados e apresentação dos resultados) têm codificação única.

A Fig. 1 apresenta o diagrama funcional da abordagem de resolução em três etapas considerada para as

duas soluções propostas.

Figura 1: Etapas do algoritmo de resolução do problema “Pares e Ímpares”

Nas seções seguintes, cada uma das etapas de resolução é descrita em detalhes. Ao final, apresenta-se um comparativo dos resultados obtidos na execução das duas soluções propostas.

2 Etapa I - Coleta de dados

A etapa de coleta de dados é a primeira a executar, sendo responsável por capturar os dados submetidos

ao algoritmo conforme o formato de entrada definido (vide Tab. 1). À medida que os dados são recepcionados,

as devidas estruturas de dados utilizadas pelo algoritmo são alimentadas.

Page 3: Ordenação de pares e ímpares

Revista Brasileira de Computação Aplicada (ISSN 2176-6649), Passo Fundo, v. 5, n. 2, p. xx-xx, out. 2013 3

A execução da etapa de coleta de dados passa por dois estágios distintos. No primeiro deles, a

cardinalidade do conjunto de entrada é obtida. Pela definição do problema, seu valor será sempre um inteiro

positivo variando de 1 a 105, não inclusos.

Visto que a cardinalidade determina o tamanho do conjunto de entrada, ela acaba por servir de base ao

segundo estágio, no qual o algoritmo entre em laço para recepcionar cada um dos elementos do conjunto de

entrada.

Durante a recepção dos elementos, os valores pares são postos em um arranjo unidimensional (vetor)

específico e elementos ímpares em outro. Essa separação visa unicamente facilitar a apresentação dos

classificados, na etapa III do algoritmo. Não há, portanto, qualquer objetivo de ganho de desempenho nesta

abordagem.

A Fig. 2 apresenta detalhadamente a etapa de coleta de dados, com os respectivos passos e estágios.

Figura 2: Descrição funcional da etapa de coleta de dados

Somente após a execução bem sucedida da etapa de coleta de dados, o algoritmo torna-se apto a avançar

para a próxima etapa, responsável pela ordenação dos elementos. Conforme já mencionado, a etapa II apresenta

duas abordagens de classificação distintas, as quais diferenciam propriamente a solução I da solução II.

O vetor de elementos pares e o vetor de elementos ímpares representam as entradas esperadas para a etapa

II, que é descrita na sequência.

3 Etapa II – Ordenação

A escolha pelo algoritmo de ordenação mais eficiente é de fundamental importância para a resolução do

problema de pares e ímpares. Um algoritmo de baixa eficiência computacional, embora eficaz em termos de

resultados, não colabora para manter a execução da solução proposta dentro do tempo máximo estabelecido, o

que desqualifica sua escolha.

Em vista disso, ambas as soluções de ordenação utilizadas na resolução do problema foram escolhidas

com base na medida da complexidade assintótica, em específico, a complexidade para o caso médio e para o pior

caso.

Aplicando-se o critério da complexidade na escolha dos algoritmos clássicos de classificação presentes na literatura, chegou-se ao QuickSort e ao MergeSort, dois algoritmos que fazem uso da técnica de divisão e

conquista em sua implementação e que apresentam excelente eficiência computacional para o caso médio. A

única ressalva fica a cargo do QuickSort, que apresenta complexidade elevada para o pior caso. Contudo,

conforme será descrito a seguir, tal característica não trás deméritos em seu uso.

De posse dos algoritmos de ordenação de melhor complexidade, foram construídas duas soluções

distintas para a etapa de ordenação, cada uma delas fazendo uso de um dos algoritmos escolhidos. Contudo,

Page 4: Ordenação de pares e ímpares

Revista Brasileira de Computação Aplicada (ISSN 2176-6649), Passo Fundo, v. 5, n. 2, p. xx-xx, out. 2013 4

antes de descrever cada uma em detalhes, é necessário apresentar as premissas básicas que ambas as soluções

devem respeitar. Basicamente, as premissas se resumem a dois pontos fundamentais:

A ordem crescente deve ser utilizada como critério de classificação em ambas as soluções;

Tanto o vetor de valores pares quanto o de ímpares deve ser submetido à classificação, sem

qualquer distinção funcional entre eles.

Uma vez conhecidos os algoritmos de classificação e as premissas envolvidas na etapa de ordenação,

segue-se com a descrição das soluções desenvolvidas, a começar pela solução I, que faz uso do QuickSort.

3.1 Solução I - QuickSort

Em termos de eficiência computacional, o QuickSort é um dos algoritmos de ordenação não estável mais

eficientes disponíveis na literatura. Sua complexidade para o caso médio é igual a O (n log n) e para o pior caso é

O (n2) [1].

Embora a complexidade para o pior caso pareça elevada à primeira vista, ela depende de um conjunto

muito específico de condições na disposição dos elementos do conjunto de entrada e na escolha do elemento

pivô, algo difícil de ocorrer. Desta forma, o QuickSort atende a contento a premissa de eficiência estabelecida para a resolução do problema.

Quanto ao seu funcionamento, o algoritmo consiste em particionar o conjunto inicial em subconjuntos

menores, de modo a ordená-los separadamente de forma recursiva. Ao final, os subconjuntos classificados são

unidos para formar o conjunto final classificado.

A primeira etapa do QuickSort requer a escolha de um elemento pivô. Tecnicamente, sua escolha pode

ser aleatória, ou seja, qualquer elemento do conjunto de entrada poderia ser utilizado como pivô. Na

implementação utilizada, o elemento pivô será sempre aquele que está mais ao centro do vetor que representa o

conjunto de dados de entrada.

Na sequência, baseado no pivô, todos os elementos de chave menor são rearranjados de modo a antecedê-

lo. Analogamente, os elementos de chave maior são postos como seus sucessores. Neste exato momento, três

importantes constatações podem ser feitas a respeito do conjunto de dados em processo de classificação:

O elemento pivô encontra-se em sua posição final;

Pode haver um subconjunto de valores menores que o elemento pivô desordenado;

Pode haver um subconjunto de valores maiores que o elemento pivô desordenado;

Para os subconjuntos restantes aplica-se recursivamente todos os passos do QuickSort novamente, até que

hajam apenas subconjuntos vazios ou com apenas um elemento. Ao alcançar tal situação, o processo de

classificação é concluído unindo-se os subconjuntos classificados em um único conjunto, através da árvore de

recursão construída durante a execução do algoritmo.

A Fig. 3 apresenta em forma de árvore um exemplo de ordenação crescente de um conjunto de cinco

elementos utilizando o QuickSort. A determinação do elemento pivô segue a implementação escolhida, que

considera sempre o elemento mais ao centro do conjunto de entrada.

Page 5: Ordenação de pares e ímpares

Revista Brasileira de Computação Aplicada (ISSN 2176-6649), Passo Fundo, v. 5, n. 2, p. xx-xx, out. 2013 5

Figura 3: Exemplo da árvore de recursão criada durante a execução do algoritmo QuickSort. Em vermelho

estão destacados os elementos pivôs. Em verde, os elementos já ordenados em sua posição final.

3.2 Solução II - MergeSort

Assim como o QuickSort, o MergeSort é um algoritmo que utiliza a técnica de divisão e conquista para

atingir seus objetivos. Em termos de complexidade, apresenta valor constante tanto para o caso médio quanto para o pior caso, sendo igual a O(n log n) [1].

Diferentemente do QuickSort porém, o MergeSort apresenta-se como um algoritmo de classificação

estável, que preserva a posição inicial relativa dos elementos que já encontram-se na ordem esperada. Também é

característica do algoritmo realizar a separação do problema inicial em subproblemas de tamanho semelhante,

mantendo a arvore de recursão relativamente balanceada.

Em termos de funcionamento, o MergeSort é composto por duas etapas, que são a divisão do conjunto de

entrada e a intercalação dos resultados. Na solução desenvolvida, todo o processo é feito de modo recorrente, até

encontrar o caso base (subconjunto contendo apenas um elemento). Ao encontrar o caso base, os subconjuntos

resultantes são intercalados a cada retorno compondo assim o conjunto final.

A Fig. 4 apresenta a sequência de passos executados pelo MergeSort para classificar em ordem crescente

um vetor de cinco elementos.

Figura 4: Passo a passo de execução do algoritmo MergeSort. Em vermelho estão descritas as etapas do

processo de divisão. Já em azul, os passos subsequentes de intercalação.

Page 6: Ordenação de pares e ímpares

Revista Brasileira de Computação Aplicada (ISSN 2176-6649), Passo Fundo, v. 5, n. 2, p. xx-xx, out. 2013 6

4 Etapa III – Apresentação dos resultados

Dispondo dos vetores de pares e ímpares já classificados em ordem crescente pela etapa II, a etapa de

apresentação dos resultados fica incumbida simplesmente de exibir na saída padrão os elementos respeitando a

ordem e o formato de saída definidos para o problema (vide Tabela 1).

Para cumprir com as exigências de saída do problema, todos os elementos pares são exibidos por

primeiro, em ordem crescente, um a cada linha. Tal tarefa é facilmente executada percorrendo-se o vetor de valores pares da primeira posição para a última.

Na sequência, os valores ímpares são exibidos. Aqui, o vetor de valores ímpares é percorrido de modo

inverso ao de pares, visto que a saída destes deve ser feita em ordem decrescente. Para tanto, inicia-se pela

última posição do vetor e, iterativamente, chega-se a primeira. Do mesmo modo que acontece na exibição dos

pares, cada valor ímpar é colocado em uma linha distinta.

A representação funcional da etapa III é demonstrada pela Fig. 5, que segue.

Figura 5: Descrição funcional da etapa de apresentação dos resultados. Nela, são escritos primeiramente na

saída padrão todos os elementos do vetor de valores pares (do primeiro até o último) e após todos os ímpares,

estes últimos partindo-se do final do vetor até alcançar o início.

5 Resultados

Ambas as soluções desenvolvidas, quando submetidas à ferramenta URI OnLine Judge, obtiveram o

resultado esperado. Além de exibir os dados corretamente, tanto a solução I quanto a II tiveram tempo total de

execução abaixo de 1 segundo, ficando assim dentro do intervalo de tempo máximo estipulado pelo problema.

Ainda com relação ao tempo de execução, observou-se uma considerável diferença entre as duas

soluções. Para o caso da solução I (que utiliza o QuickSort), a execução do programa teve duração de 0,072

segundos. Já a solução II, que utiliza o MergeSort, mostrou-se mais rápida, concluindo a tarefa em 0,048

segundos.

Com base unicamente nos tempos evidenciados pela ferramenta URI OnLine Judge e desconsiderando

qualquer outra variável envolvida, pode-se, a princípio, afirmar que a solução II tem eficiência 33,33% maior

que a solução I na resolução do problema de pares e ímpares. Contudo tal afirmação beira ao empirismo, visto

que elementos importantes, como o conjunto de entrada e hardware utilizado na execução deveriam obrigatoriamente ser os mesmos para ambos os testes, fatos estes que não se tem conhecimento nem

comprovação.

Portanto, embora observado certa diferença nos tempos de execução, não é possível comprovar que a

solução II é efetivamente mais eficiente que a I. Pode-se unicamente concluir que ambas as soluções são eficazes

na resolução do problema de pares e ímpares.

Page 7: Ordenação de pares e ímpares

Revista Brasileira de Computação Aplicada (ISSN 2176-6649), Passo Fundo, v. 5, n. 2, p. xx-xx, out. 2013 7

6 Conclusões

A resolução do problema de pares e ímpares trouxe a tona importantes questões. A primeira delas e,

certamente, uma das mais relevantes diz respeito à seleção do algoritmo mais adequado ao universo do problema

considerado.

Por vezes, e aqui é possível incluir o caso da ordenação de pares e ímpares, limitar-se à escolha de um

algoritmo simplesmente eficaz não é suficiente. É preciso ir além e buscar por aqueles que apresentam melhor eficiência em termos de complexidade assintótica.

Fundamentar a escolha dos algoritmos pela complexidade gerou bons resultados na resolução do

problema de pares e ímpares. Conforme pôde-se observar na discussão dos resultados, ambos os algoritmos

escolhidos para dar cabo ao problema, a saber, o QuickSort e o MergeSort, tiveram resultados excelentes, visto

que executaram numa faixa de tempo menor do que 10% do tempo total que dispunham para fazê-lo.

Outro importante ponto que teve forte impacto no processo de desenvolvimento das soluções foi a escolha

da linguagem de programação. Inicialmente, optou-se pelo desenvolvimento das soluções em Java, por questões

de afinidade com a linguagem. Contudo o código se mostrou pouco eficiente, principalmente na etapa coleta dos

dados. Em vista disso, a linguagem C++ passou a ser considera no desenvolvimento e a partir de então não

houveram mais problemas dessa natureza.

Por fim, no que diz respeito às soluções construídas, ficou evidente a possibilidade de otimização na etapa II, responsável pela ordenação do conjunto de entrada. Não haveriam dificuldades, por exemplo, de se paralelizar

o processo de classificação dos vetores de pares e ímpares, oriundos da etapa I. A separação destes em

estruturadas de dados distintas permite a ordenação concorrente utilizando threads, sem a necessidade de uso de

semáforos ou outra forma de bloqueio de seção crítica.

Referências

[1] TENENBAUM, A. M. et al. Estrutura de dados usando C. São Paulo: Pearson Makron Books, 1995.