Vectores: Algoritmos de Ordenação (2) Algoritmos e Estruturas de Dados 2009/2010 Ordenação por Partição (Quick Sort) • Algoritmo (ordenação por partição): 1. Caso básico: Se o número (n) de elementos do vector (a) a ordenar for muito pequeno, usar outro algoritmo (insertionSort) 2. Passo de partição: 2.1. Escolher um elemento “arbitrário” (x) do vector (chamado pivot) 2.2. Partir o vector inicial em dois sub-vectores (esquerdo e direito), com valores x no sub-vector esquerdo e valores x no sub-vector direito (podendo existir um 3º sub-vector central com valores =x) 3. Passo recursivo: Ordenar os sub-vectores esquerdo e direito, usando o mesmo método recursivamente • Algoritmo recursivo baseado na técnica divisão e conquista AED - 2009/10 2 1 Exemplo do Passo de Partição i v: 8 9 2 5 x=4 4 j 6 1 i 8 9 2 5 4 6 9 3 1 1 1 3 7 1 8 7 6 9 8 7 6 9 8 7 6 9 x 8 7 j 2 5 4 2 2 6 j i 3 7 j i 3 3 5 4 i j 5 4 j < i 3 AED - 2009/10 1 2 x 4 5 3 Implementação da Ordenação por Partição em C++ /* calcula a mediana entre os valores dos indices left, right, e centro (left+right)/2 do vector a */ template <class Comparable> const Comparable &median3(vector<Comparable> &a, int left, int right) { int center = (left+right) /2; if (a[center] < a[left]) swap(a[left], a[center]); if (a[right] < a[left]) swap(a[left], a[right]); if (a[right] < a[center]) swap(a[center], a[right]); //coloca pivot na posicao right-1 swap(a[center], a[right-1]); return a[right-1]; } AED - 2009/10 4 2 Implementação da Ordenação por Partição em C++ /* Ordena vector(a) entre duas posições indicadas (left e right). Supõe que os elementos do vector são comparáveis com "<" e copiáveis com "=". */ template <class Comparable> void quickSort(vector<Comparable> &a, int left, int right) { if (left-right <= 10) // se vector insertionSort(a,left,right); else { pequeno // inicializações Comparable x = median3(a,left,right); int i = left; int j = right-1; // passo de partição // x é o pivot // continua ... 5 AED - 2009/10 Implementação da Ordenação por Partição em C++ for(; ; ) { while (a[++i] < x) ; while (x < a[--j]) ; if (i < j) swap(a[i], a[j]); else break; } swap(a[i], a[right-1]); //repoe pivot // passo recursivo quickSort(a, left, i-1); quickSort(a, i+1, right); } } AED - 2009/10 6 3 Programa de teste #include <iostream.h> #include <stdlib.h> // Para usar rand() // inserir aqui a função QuickSort void imprime(const int v[], int n) { for (int i = 0; i < n; i++) cout << v[i] << ' '; cout << '\n'; } main() { const int SIZE = 100; int v[SIZE]; for (int i = 0; i < SIZE; i++) v[i] = rand(); imprime(v, SIZE); quickSort(v, 0, SIZE - 1); imprime(v, SIZE); return 0; } AED - 2009/10 7 Análise da Ordenação por Partição • Escolha pivot determina eficiência – pior caso: pivot é elemento mais pequeno O(N2) – melhor caso: pivot é elemento médio O(N logN) – caso médio: pivot corta vector arbitrariamente O(N logN) • Escolha pivot – má escolha: extremos do vector ( O(N2) se vector ordenado ) – aleatório: envolve mais uma função pesada – recomendado: mediana de três elementos (extremos do vector e ponto médio) AED - 2009/10 8 4 Eficiência da Ordenação por Partição Pior caso: cada rectângulo refere-se a uma chamada recursiva n n-1 n-3 n/2 1 1+log 2 n 1 1 profundidade de recursão: n tempo de execução total (somando totais de linhas): T(n) = O[n+n+(n-1) + ... +2] = O[n+(n-1)(n + 2)/2] = O(n2) n/4 n/2 n/4 n/4 n/4 ... 1 1 1 1 1 1 1 1 1 ... • • n 1 n-2 n Melhor caso: • profundidade de recursão: 1+log 2 n (sem contar com a possibilidade de um elemento ser excluído dos sub-arrays esquerdo e direito) • tempo de execução total (uma vez que a soma de cada linha é n): T(n) = O[(1+log2n) n] = O(n log n) 9 AED - 2009/10 Complexidade Espacial de QuickSort • O espaço de memória exigido por cada chamada de quickSort, sem contar com chamadas recursivas, é independente do tamanho (n) do array • O espaço de memória total exigido pela chamada de quickSort, incluindo as chamadas recursivas, é pois proporcional à profundidade de recursão • Assim, a complexidade espacial de quickSort é: – O(log n) no melhor caso (e no caso médio) – O(n) no pior caso • Em contrapartida, a complexidade espacial de insertionSort é O(1) AED - 2009/10 10 5 BucketSort • Ordenação linear – usa informação adicional sobre entrada • Algoritmo – vector de entrada: inteiros positivos inferiores a M a = [ A1, A2, …, AN ] ; Ai < M – inicializar um vector de M posições a 0’s count = [c1, c2, …, cM] ; cj = 0 – Ler vector entrada (a) e para cada valor incrementar a posição respectiva no vector count : count[Ai]++ – Produzir saída lendo o vector count • Eficiência – tempo linear 11 AED - 2009/10 Comparação de tempos médios de execução (observados) de diversos algoritmos de ordenação Insertion sort Heap sort Merge sort Cada ponto corresponde à ordenação de 100 arrays de inteiros gerados aleatoriamente Quick sort Fonte: Sahni, "Data Structures, Algorithms and Applications in C++" Método de ordenação por partição (quickSort) é na prática o mais eficiente, excepto para arrays pequenos (até cerca 20 elementos), em que o método de ordenação por inserção (insertionSort) é melhor! AED - 2009/10 12 6