Programação II Ordenação (sort) Bruno Feijó Dept. de Informática, PUC-Rio
Quick Sort (Ordenação Rápida)
Algoritmo Quick Sort (recursivo) O algoritmo de Quick Sort foi desenvolvido por Sir Charles Hoare (Tony Hoare), em 1960, aos 26 anos. Suponha que você sabe colocar um dos elementos x do vetor (por exemplo, o primeiro) numa posição tal que todos os elementos antes são menores ou iguais a x e todos os elementos depois dele são maiores (note que, nesta tarefa, não importa se alguns elementos trocam de posição): x x x x 5 5 n elementos 5 4 3 7 1 2 6 9 1 4 3 2 5 7 6 9 n = 8 Vamos denominar o elemento x escolhido de pivô e chamar esta etapa da ordenação de PARTIÇÃO. Neste caso, se você souber ordenar o subvetor esquerdo e o subvetor direito, você terá o vetor inicial completamente ordenado! Isto nos leva a definir uma função recursiva quicksort na qual o caso-base é um vetor com 1 ou nenhum elemento (e, neste caso, nada é preciso fazer) e o caso geral é fazer a partição seguida da chamada da função para o subvetor esquerdo e da chamada da função para o subvetor direito: quicksort do vetor se n 1 então retorna faz PARTIÇÃO com pivô x (i.e. coloca x no lugar certo) quicksort do subvetor à esquerda de x quicksort do subvetor à direita de x No próximo slide estão sugeridos um processo simples e direto para a PARTIÇÃO e uma maneira de se referenciar aos subvetores à esquerda e à direita de x.
PARTIÇÃO x a a v a x a para Algoritmo Quick Sort (recursivo) - CRESCENTE Para a PARTIÇÃO, podemos ir caminhando com os índices do vetor: a b... a b v b x b para se a b, troca e incrementa região dos x se b a, troca pivô x com v b Subvetor à esq v b x v a 0 a b posição correta de x b região dos x b Subvetor à dir n - a repete processo para cada subvetor caminham enquanto fica repetindo até que 25 48 37 12 57 86 33 92 a b a b b b b 25 12 37 48 57 86 33 92 a b b a 12 25 37 48 57 86 33 92 12 incr a se v a x decr b se v b x troca e incr/decr não troca nem in/decr troca pivô quicksort(n,v) se n <= 1 então retorna x = v 0 a = 1 b = n-1 faça enquanto a < n e v a x a = a + 1 enquanto v b > x b = b - 1 se a < b então troca v a com v b e a= a+1 e b= b-1 enquanto a b troca pivô x com v b quicksort(b, subvetor esquerdo) quicksort(n-a, subvetor direito)
Partição Código Quick Sort CRESCENTE quicksort(n,v) se n <= 1 então retorna x = v 0 a = 1 b = n-1 faça enquanto a < n e v a x a = a + 1 enquanto v b > x b = b - 1 se a < b então troca v a com v b e a= a+1 e b= b-1 enquanto a b troca pivô x com v b quicksort(b, subvetor esquerdo) quicksort(n-a, subvetor direito) void quicksort(int n, int * v) int x = v[0]; int temp; int a = 1; int b = n-1; if (n<=1) return; do while (a < n && v[a] <= x ) while ( v[b] > x ) if (a < b) temp = v[a]; v[a] = v[b]; v[b] = temp; while (a <= b); v[0] = v[b]; v[b] = x; quicksort(b,v); quicksort(n-a,&v[a]);
Generalizando o Algoritmo Quick Sort - Crescente O desenvolvimento apresentado nos slides anteriores refere-se à ordenação em ordem CRESCENTE de um vetor de INTEIROS. Uma primeira generalização é perceber que a partição divide o vetor em elementos que atendem o critério de ordenação à direita do pivô e elementos que NÃO atendem à esquerda. Por exemplo, a ilustração da partição para uma ordem CRESCENTE seria: 5 5 5 4 3 7 1 2 6 9 1 4 3 2 5 7 6 9 O critério de ordenação pode ser definido numa função auxiliar de comparação que é usada em dois momentos no algoritmo de quick sort, sendo um momento a negação do outro (operador!): static int cmpint(int x, int y) return x > y ; void quicksort(int n, int * v) int x = v[0]; int temp; int a = 1; int b = n-1; if (n<=1) return; do while (a < n &&!cmpint(v[a],x)) while (cmpint(v[b],x)) if (a < b) temp = v[a]; v[a] = v[b]; v[b] = temp; while (a <= b); v[0] = v[b]; v[b] = x; quicksort(b,v); quicksort(n-a,&v[a]); Elementos à esquerda NÃO atendem ao critério Elementos à direita atendem ao critério Geralmente definimos a função auxiliar como sendo static. Função static é invisível fora do arquivo no qual é declarada. O que é útil em módulos.
Generalizando o Algoritmo Quick Sort - Decrescente O desenvolvimento apresentado nos slides anteriores refere-se à ordenação em ordem CRESCENTE de um vetor de INTEIROS. Uma primeira generalização é perceber que a partição divide o vetor em elementos que atendem o critério de ordenação à direita do pivô e elementos que NÃO atendem à esquerda. Por exemplo, a ilustração da partição para uma ordem DECRESCENTE seria: 5 5 5 4 3 7 1 2 6 9 7 9 6 5 1 2 3 4 O critério de ordenação pode ser definido numa função auxiliar de comparação que é usada em dois momentos no algoritmo de quick sort, sendo um momento a negação do outro (operador!): static int cmpint(int x, int y) return x < y ; void quicksort(int n, int * v) int x = v[0]; int temp; int a = 1; int b = n-1; if (n<=1) return; do while (a < n &&!cmpint(v[a],x)) while (cmpint(v[b],x)) if (a < b) temp = v[a]; v[a] = v[b]; v[b] = temp; while (a <= b); v[0] = v[b]; v[b] = x; quicksort(b,v); quicksort(n-a,&v[a]); Elementos à esquerda NÃO atendem ao critério Elementos à direita atendem ao critério
Template void qsorttipo(int n, Tipo * v) // Ex: Tipo= int, Tipo= float, Tipo= Aluno * Tipo x = v[0]; Tipo temp; int a = 1, b = n-1; if (n<=1) return; do while (a < n &&!cmpfunction(v[a],x)) while (cmpfunction(v[b],x)) if (a < b) temp = v[a]; v[a] = v[b]; v[b] = temp; while (a <= b); v[0] = v[b]; v[b] = x; qsorttipo(b,v); qsorttipo(n-a,&v[a]); Função critério de ordenação static int cmpfunction(tipo x1, Tipo x2) // Criterio que retorna verdadeiro ou falso Pode ser um critério simples (p.ex., se for vetor de inteiros) ou um critério composto se for uma estrutura: p.ex., primeiro ordena crescente pelo peso e depois crescente pela idade. Neste caso, o critério é uma expressão booleana: pesox1 > pesox2 (pesox1 == pesox2 && idadex1 > idadex2)
Exercício 1 Ordenar vetor de floats Escreva um programa completo que ordena CRESCENTEMENTE um vetor de floats através do Quick Sort (e usando função de comparação). Depois indique a mudança para a ordenação DECRESCENTE.
Solução Exercício 1 Vetor de floats #include <stdio.h> int cmpfunction(float x1, float x2) // funcao criterio de ordenacao return x1 < x2; void qsortfloat(int n, float * v) float x = v[0]; float temp; int a = 1, b = n - 1; if (n <= 1) return; do while (a < n &&!cmpfunction(v[a], x)) while (cmpfunction(v[b], x)) if (a < b) temp = v[a]; v[a] = v[b]; v[b] = temp; while (a <= b); v[0] = v[b]; v[b] = x; qsortfloat(b, v); qsortfloat(n - a, &v[a]); // Decrescente, e return x1 > x2; se Crescente printarray(float * v, int n) int i; for (i = 0; i < n; i++) printf("%.1f\n", v[i]); int main(void) float vtemp[] = 40.0, 15.0, 25.0, 30.0, 10.0 ; int n = 5; qsortfloat(n, vtemp); printarray(vtemp, n); return 0;
Exercício 2 Ordena vetor de ponteiros para Dados Escreva programa completo, em módulos, que ordena um vetor de ponteiros para estrutura Dados através do Quick Sort (e usando função de comparação), de acordo o seguinte critério: primeiro em ordem crescente da idade e depois em ordem crescente do peso. struct dados int idade; int peso; ; typedef struct dados Dados; Sugestão para teste: Dados tab[] = 20,50,10,30,25,40,18,65,10,40,18,60; Dados * v[] = tab,tab+1,tab+2,tab+3,tab+4,tab+5; Faça a implementação em módulos com: dados.h, dados.c e testedados.c.
qsort Quick Sort da Biblioteca C
Ponteiros para Funções Em C é possível definir ponteiros para funções que podem ser colocados em vetores, passados para funções e retornados por funções No exemplo a seguir, a função opera(m, x, y, func) recebe um ponteiro de função como um de seus argumentos (func) e retorna m func(x,y). Devemos notar que o nome da função mult é um ponteiro quando escrevemos opera(2,3,5,mult); #include <stdio.h> Se argumentos são ponteiros, usamos const quando queremos garantir que a função func não modificará os valores que são int mult(int x,int y); apontados: int(*func)(const int *, const int *)) int soma(int x, int y); int opera(int m, int x, int y, int(*func)(int, int)); int main(void) int a, b; a = opera(2,3,5,mult); b = opera(2,3,5,soma); printf("%d %d\n",a,b); return 0; Saída: 30 16 int mult(int x,int y) return x*y; int soma(int x, int y) return x+y; int opera(int m,int x,int y,int(*func)(int, int)) return m*func(x,y);
Quick Sort da stdlib do C void qsort(void * v, int n, int tam, int (*cmp)(const void *, const void *)); v: vetor de ponteiros genéricos n: número de elementos do vetor tam: tamanho em bytes de cada elemento (use sizeof para especificar) cmp: ponteiro para função que compara elementos genéricos int nome(const void * a, const void * b); deve retornar <0 se a<b, >0 se a>b e 0 se a == b const é para garantir que a função não modificará os valores dos elementos static int compfloat(const void * a, const void * b) /* converte os ponteiros genericos */ float * aa = (float *)a; float * bb = (float *)b; /* faz a comparacao com (*aa) e (*bb) retornando -1,0, ou 1 */ if (*aa > *bb) // atenção para o * return 1; else if (*aa < *bb) return -1; else return 0; chamada: qsort(v,n,sizeof(float),compfloat); // N é o tamanho de v
Exemplo Quick Sort da stdlib #include <stdlib.h> int main(void)... void QsortPessoa(int n, Pessoa ** v) qsort(v,n,sizeof(pessoa *),comppessoa); Pessoa tab[] = "Diana Maria",22,...; Pessoa * v[] = tab,tab+1,...;... QsortPessoa(n,tabPessoa); // ordenacao com Quick Sort... int comppessoa(const void * a, const void * b) Pessoa ** aa = (Pessoa **)a; Pessoa ** bb = (Pessoa **)b; int cmp = strcmp((*aa)->nome,(*bb)->nome); if (cmp>0 (cmp==0 && ((*aa)->idade>(*bb)->idade))) return 1; else if (cmp<0 (cmp==0 && ((*aa)->idade<(*bb)->idade))) return -1; else return 0;