15
1 Algoritmos e Estruturas de Dados 2008/2009 Vectores: Algoritmos de Ordenação AED - 2005/06 2 Ordenação Problema (ordenação de vector) rearranjar os n elementos de um vector (v) por ordem crescente, ou melhor, por ordem não decrescente, porque podem existir valores repetidos vector de N elementos Algoritmos: Ordenação por Inserção Ordenação por Selecção Bubble Sort – ShellSort – MergeSort Ordenação por Partição (QuickSort) – BucketSort

Vectores: Algoritmos de Ordenação - paginas.fe.up.ptpaginas.fe.up.pt/~arocha/AED1/APONTC/vectorOrd.pdf · 1 Algoritmos e Estruturas de Dados 2008/2009 Vectores: Algoritmos de Ordenação

Embed Size (px)

Citation preview

1

Algoritmos e Estruturas de Dados2008/2009

Vectores: Algoritmos de Ordenação

AED - 2005/06 2

Ordenação

• Problema (ordenação de vector)– rearranjar os n elementos de um vector (v) por ordem crescente, ou

melhor, por ordem não decrescente, porque podem existir valores repetidos

– vector de N elementos

• Algoritmos:– Ordenação por Inserção

– Ordenação por Selecção

– Bubble Sort

– ShellSort

– MergeSort

– Ordenação por Partição (QuickSort)

– BucketSort

2

AED - 2005/06 3

Ordenação por Inserção

• N-1 passos

• Em cada passo p coloca um elemento na ordem, sabendo que elementos dos índices inferiores (entre 0 e p-1) já estão ordenados

• Algoritmo (ordenação por inserção):

– Considera-se o vector dividido em dois sub-vectores (esquerdo e direito), com o da esquerda ordenado e o da direita desordenado

– Começa-se com um elemento apenas no sub-vector da esquerda

– Move-se um elemento de cada vez do sub-vector da direita para o sub-vector da esquerda, inserindo-o na posição correcta por forma a manter o sub-vector da esquerda ordenado

– Termina-se quando o sub-vector da direita fica vazio

AED - 2005/06 4

5

ordenado desordenado

1ª iteração

v: 3 1 9 5 8

3 5 1 9 5 8

2ª iteração

1 3 5 9 5 8

3ª iteração

1 3 5 9 5 8

4ª iteração

1 3 5 5 9 8

5ª iteração

1 3 5 5 8 9desordenadoordenado

v:

Exemplo de Ordenação por Inserção

3

AED - 2005/06 5

/* Insere um valor x num array v com n elementos ordenados. Após a inserção, o array fica com n+1 elementos ordenados. Retorna a posição em que inseriu o valor (entre 0 e n). */

template <class T>int insertSorted(T v[], int n, T x){

int j;// procura posição (j) de inserção da direita (posi ção n)// para a esquerda (posição 0), e ao mesmo tempo ch ega // elementos à direita (podia usar o próprio n em v ez de j)

for (j = n; j > 0 && x < v[j - 1]; j--)v[j] = v[j-1];

v[j] = x; // insere agora

return j; // retorna a posição em que inseriu

}

Inserção de valor em vector ordenado

• Sub-problema: inserir um valor num vector ordenado (mantendo-o ordenado)

• Solução em C++ (função insertSorted) :

AED - 2005/06 6

// Ordena array v de n elementos, ficando v[0] ≤... ≤v[n-1]

template <class T> void insertionSort(T v[], int n){

// i - tamanho do sub-array esquerdo ordenado

for (unsigned int i = 1; i < n; i++)insertSorted(v, i , v[i]);

}

template <class T> void insertionSort(T v[], int n){

for (unsigned int i = 1; i < n; i++) {T x = v[i];for (unsigned int j = i; j > 0 && x < v[j - 1]; j--)

v[j] = v[j-1];v[j] = x;

}}

Implementação da Ordenação por Inserção em C++• A função em C++ para ordenar um array pelo método de inserção é agora trivial

• Juntando tudo (para ser mais eficiente) ...

4

AED - 2005/06 7

Ordenação por Inserção usando vector

// Ordena elementos do vector a// Comparable : deve conter construtor cópia, operadores// igualdade (=) e comparação(<)

template <class Comparable> void insertionSort(vector<Comparable> &a){

for (unsigned int p = 1; p < a.size(); p++){

Comparable tmp = a[p];int j;for (j = p; j > 0 && tmp < a[j-1]; j--)

a[j] = a[j-1];a[j] = tmp;

} }

AED - 2005/06 8

Eficiência da Ordenação por Inserção

• insertSorted(v, n, x) :– o nº de iterações do ciclo for é:

• 1, no melhor caso

• n, no pior caso

• n/2, em média

• insertionSort(v, n) :– faz insertSorted(,1,) , insertSorted(,2,) , ...,

insertSorted(,n-1,)

– o nº total de iterações do ciclo for de insertSorted é:• no melhor caso, 1 + 1 + ... + 1 (n-1 vezes) = n-1 ≈ n• no pior caso, 1 + 2 + ... + n-1 = (n-1)(1 + n-1)/2 = n(n-1)/2 ≈ n2/2• em média, metade do anterior, isto é, aproximadamente n2/4

⇒ T(n) = O(n2) (quadrático) (pior caso e média)

5

AED - 2005/06

Ordenação por Selecção

• Provavelmente é o algoritmo mais intuitivo:

– Encontrar o mínimo do vector

– Trocar com o primeiro elemento

– Continuar para o resto do vector (excluindo o primeiro)

• 2 ciclos encaixados, cada um pode ter N iterações :

– Complexidade O(N2)

• Variantes:

– “stableSort” – Insere mínimo na primeira posição (em vez de realizar a troca)

– “shaker Sort” – Procura máximo e mínimo em cada iteração (selecção

bidireccional)

9

AED - 2005/06

Ordenação por Selecção

Algoritmo em C++:// Ordena vector a de n elementos, ficando a[0]≤... ≤a[n-1]

template <class Comparable>

void selectionSort(vector<Comparable> &a )

{

typename vector<Comparable>::iterator it;

for(it = a.begin(); it != a.end()-1; ++it )

iter_swap(it, min_element(it, a.end()));

}

Existente em STL:• template <class ForwardIterator> ForwardIterator

ForwardIteratormin_element(ForwardIterator first, ForwardIterator last);

• template <class ForwardIterator1, class ForwardIterator2> inline void iter_swap(ForwardIterator1 a, ForwardIterator2 b);

10

6

AED - 2005/06

Bubble Sort

• Algoritmo de Ordenação “BubbleSort” (“Exchange Sort”):

– Compara elementos adjacentes. Se o segundo for menor do que o

primeiro, troca-os

– Fazer isto desde o primeiro até ao último par

– Repetir para todos os elementos excepto o último (que já está correcto)

– Repetir, usando menos um par em cada iteração até não haver mais

pares (ou não haver trocas)

• 2 ciclos encaixados, cada um pode ter N iterações:

– Complexidade O(N2)

11

AED - 2005/06

Bubble SortAlgoritmo em C++:// Ordena vector a de n elementos inteiros, ficando a[0]≤... ≤a[n-1]

template <class Comparable>

void bubbleSort(vector<Comparable> &a)

{

for(unsigned int j=a.size()-1; j>0; j--)

{

bool troca=false;

for(unsigned int i = 0; i<j; i++)

if(a[i] > a[i+1]) {

swap(a[i],a[i+1]);

troca = true;

}

if (!troca) return;

}

}

12

7

AED - 2005/06

Bubble Sort (outra implementação)

Algoritmo em C++:// Ordena vector a de n elementos inteiros, ficando a[0]≤... ≤a[n-1]

template<class T>void bubbleSort(vector<T> &a ){

typename vector<T>::reverse_iterator it1;for(it1 = a.rbegin(); it1 != a.rend(); ++it1 ) {

typename vector<T>::iterator it2;bool troca = false;for(it2 = a.begin(); &*it2 != &*it1; ++it2 ) {

if (*it2 > *(it2+1)) {iter_swap(it2, it2+1);troca = true;

}}if (!troca) return;

}}

13

AED - 2005/06 14

ShellSort

• Compara elementos distantes

• Distância entre elementos comparados vai diminuindo, até que a comparação seja sobre elementos adjacentes– Usa a sequência h1, h2, …, ht (h1=1)

– Em determinado passo, usando incremento hk, todos os elementos separados da distância hk estão ordenados, a[i] ≤ a[i+h k]

• Sequência de incrementos:– Shell: mais popular, não mais eficiente O(N2)

• ht = N/2, hk = hk+1/2

– Hibbard: incrementos consecutivos não têm factores comuns

• h = 1, 3, 7, …, 2k-1 O(N5/4)

8

AED - 2005/06 15

ShellSort ( h = {5, 3, 1} )

81 94 11 96 12 35 17 95 28 58 41 75 v:

35 17 11 28 12 41 75 95 96 58 81 94

28 12 11 35 17 41 58 81 94 75 85 96

11 12 17 28 35 41 58 75 81 94 95 96

h=

5

h=3

h=1

35 94 11 96 12 81 17 95 28 58 41 75

35 17 11 96 12 81 94 95 28 58 41 75

35 17 11 96 12 81 94 95 28 58 41 75

35 17 11 28 12 81 94 95 96 58 41 75

35 17 11 28 12 81 94 95 96 58 41 75

35 17 11 28 12 41 94 95 96 58 81 75

AED - 2005/06 16

Implementação de ShellSort em C++

// ShellSort - com incrementos de Shell

template <class Comparable>

void shellSort(vector<Comparable> &a){

int j;

for (int gap = a.size()/2; gap > 0; gap /= 2)for (unsigned int i = gap; i < a.size(); i++){

Comparable tmp = a[i];for (j = i; j >= gap && tmp < a[j-gap]; j -= gap)

a[j] = a[j-gap];

a[j] = tmp;}

}

9

AED - 2005/06 17

MergeSort

• Abordagem recursiva– Divide-se o vector ao meio

– Ordena-se cada metade (usando MergeSort recursivamente)

– Fundem-se as duas metades já ordenadas

• Divisão e conquista– problema é dividido em dois de metade do tamanho

• Análise– Tempo execução: O(N logN)

• 2 chamadas recursivas de tamanho N/2

• Operação de junção de vectores: O(N)

• Inconveniente– fusão de vectores requer espaço extra linear

AED - 2005/06 18

Implementação de MergeSort em C++template <class Comparable>void mergeSort(vector <Comparable> & a) {

vector<Comparable> tmpArray(a.size());mergeSort(a, tmpArray, 0, a.size()-1);

}

// internal method that makes recursive calls// a is an array of Comparable terms// tmpArray is an array to place the merged result// left (right) is the left(right)-most index of th e subarray

template <class Comparable>void mergeSort(vector <Comparable> & a,

vector<Comparable> & tmpArray, int left, int right){

if (left < right){

int center = (left + right) / 2;mergeSort(a, tmpArray, left, center);mergeSort(a, tmpArray, center + 1, right);merge(a, tmpArray, left, center +1, right);

}}

10

AED - 2005/06 19

Implementação de MergeSort em C++// internal method that makes recursive calls. a is an array of // Comparable terms. tmpArray is an array to place the merged result.// left ( right) is the left(right)-most index of the subarray

template <class Comparable>void merge(vector <Comparable> & a, vector<Comparab le> &tmpArray,

int leftPos, int rightPos, int rightEnd){

int leftEnd = rightPos - 1;int tmpPos = leftPos;int numElements = rightEnd - leftPos + 1;while ( leftPos <= leftEnd && rightPos <= rightEnd )

if ( a[leftPos] <= a[rightPos] )tmpArray[tmpPos++] = a[leftPos++];

elsetmpArray[tmpPos++] = a[rightPos++];

while ( leftPos <= leftEnd )tmpArray[tmpPos++] = a[leftPos++];

while ( rightPos <= rightEnd )tmpArray[tmpPos++] = a[rightPos++];

for ( int i = 0; i < numElements; i++, rightEnd-- )a[rightEnd] = tmpArray[rightEnd];

}

AED - 2005/06 20

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

11

AED - 2005/06 21

3 1 2 5 4 6 9 8 7

8 9 2 5 4 6 1 3 7

v: 8 9 2 5 4 6 1 3 7

i jx=4

3 9 2 5 4 6 1 8 7

i j

3 1 2 5 4 6 9 8 7

i j

3 1 2 4 5 6 9 8 7

ij <

≥≥≥≥ x≤≤≤≤ x

(2.3.3)

(2.3.3)

(2.3.1+2.3.2) i j

(2.3.3)

i j(2.3.1+2.3.2)

Exemplo do Passo de Partição

AED - 2005/06 22

/* calcula a mediana entre os valores dos indices l eft, 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];

}

Implementação da Ordenação por Partição em C++

12

AED - 2005/06 23

/* Ordena vector( a) entre duas posições indicadas ( left e right ). Supõe que os elementos do vector são comparáveis co m " <" e copiáveis com " =". */

template <class Comparable>void quickSort(vector<Comparable> &a, int left, int right){

if (left-right <= 10) // se vector pequeno

insertionSort(a,left,right);else {

// inicializações

Comparable x = median3(a,left,right); // x é o pivot

int i = left;int j = right-1; // passo de partição

// continua ...

Implementação da Ordenação por Partição em C++

AED - 2005/06 24

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);

}}

Implementação da Ordenação por Partição em C++

13

AED - 2005/06 25

#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;

}

Programa de teste

AED - 2005/06 26

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)

14

AED - 2005/06 27

n

n-1

n-2

1

1

n-3 1

1 1

...

n

cada rectângulo refere-se a uma

chamada recursiva n

n/2 n/2

n/4 n/4n/4 n/41+log 2 n ≈

1 1 1 1 1 1 1 1

...

Pior caso: 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)

Eficiência da Ordenação por Partição

• 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)

AED - 2005/06 28

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 espacialde quickSort é:

– O(log n) no melhor caso (e no caso médio)

– O(n) no pior caso

• Em contrapartida, a complexidade espacialde insertionSort é O(1)

15

AED - 2005/06 29

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’scount = [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

AED - 2005/06 30

Cada ponto corresponde à ordenação de 100 arrays de inteiros gerados aleatoriamente

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étodode ordenação por inserção (insertionSort) é melhor!

Quick sort

Insertion sort Heap sort

Merge sort

Comparação de tempos médios de execução(observados) de diversos algoritmos de ordenação