Baixe o app para aproveitar ainda mais
Prévia do material em texto
Protocolos de Redes e de Computadores AULA 02 ALGORITMOS AVANÇADOS – AULA 02 Prof. Msc. Alexandre José Braga da Silva alex.professor@gmail.com CCT0312 – Algoritmos Avançados Objetivos da Aula: • Relembrar o conceito de Ponteiros; • Relembrar o conceito de passagem de parâmetros; • Entender o conceito de Análise de Algoritmo; CCT0312 – Algoritmos Avançados Revisão dos conceitos básicos: Ponteiros guardam endereços de memória. Quando você anota o endereço de um colega você está criando um ponteiro. O ponteiro é o seu pedaço de papel. Um ponteiro também tem tipo, eles indicam locais cujos conteúdos são diferentes. Então dois endereços são ponteiros de tipos diferentes. No C++ quando declaramos ponteiros nós informamos ao compilador para que tipo de variável vamos apontá-lo. Um ponteiro int aponta para um inteiro, isto é, guarda o endereço de um inteiro. CCT0312 – Algoritmos Avançados Revisão dos conceitos básicos: Para declarar um ponteiro temos a seguinte forma geral: tipo_do_ponteiro *nome_da_variável; É o asterisco (*) que faz o compilador saber que aquela variável vai guardar um endereço para aquele tipo especificado. Vamos ver exemplos de declarações: int *pt; → declara um ponteiro para um número inteiro. char *temp,*pt2; → declara dois ponteiros para caracteres. CCT0312 – Algoritmos Avançados Revisão dos conceitos básicos: Eles ainda não foram inicializados. Isto significa que eles apontam para um lugar indefinido. Usar o ponteiro nestas circunstâncias pode levar a um travamento do computador. O ponteiro deve ser inicializado antes de ser usado! Para atribuir um valor a um ponteiro recém-criado poderíamos igualá- lo a um valor de memória. Mas, como saber a posição na memória de uma variável do nosso programa? CCT0312 – Algoritmos Avançados Revisão dos conceitos básicos: Podemos então deixar que o compilador faça este trabalho por nós. Para saber o endereço de uma variável basta usar o operador &. Veja o exemplo: int cont=10; int *pt; pt=&cont; Criamos um inteiro com o valor 10 e um apontador para um inteiro pt. A expressão &cont nos dá o endereço de cont, o qual armazenamos em pt. Repare que não alteramos o valor de cont, que continua valendo 10. CCT0312 – Algoritmos Avançados Revisão dos conceitos básicos: Podemos, por exemplo, alterar o valor de cont usando pt. Para tanto vamos usar o operador "inverso" do operador &. que é o operador *. No exemplo anterior, uma vez que fizemos pt=&cont a expressão *pt é equivalente ao próprio cont. Isto significa que, se quisermos mudar o valor de cont para 12, basta fazer *pt=12. Vejamos dois exemplos de utilização de ponteiros: CCT0312 – Algoritmos Avançados Revisão dos conceitos básicos: #include <iostream> #include <conio.h> using namespace std; int main (){ int num,valor; int *p; num=55; p=# /* Pega o endereco de num */ valor=*p; /* Valor e igualado a num de uma maneira indireta */ cout<<"Valor: "<<valor<<endl; cout<<"Endereco para onde o ponteiro aponta: "<<p<<endl; cout<<"Valor da variavel apontada: "<<*p<<endl; } CCT0312 – Algoritmos Avançados Revisão dos conceitos básicos: Passagem de parâmetros por valor: A função recebe uma cópia da variável que é fornecida quando é invocada. Todas as alterações feitas dentro da função não vão afetar os valores originais. Passagem de parâmetros por referência: Neste caso o que é enviado para a função é uma referência às variáveis utilizadas, e não uma simples cópia, pelo que as alterações realizadas dentro da função irão certamente alterar os valores contidos nessas variáveis. CCT0312 – Algoritmos Avançados Revisão dos conceitos básicos: Exemplo de passagem de parâmetros por valor: #include<iostream> using namespace std; void troca(int a, int b){ int temp; temp=a; a=b; b=temp; } int main(){ int a=2,b=3; cout<<"Antes de chamar a funcao: \na="<<a<<"\tb="<<b<<endl; troca(a,b); cout << "\Depois de chamar a funcao: \na="<<a<<"\tb="<<b<<endl; } CCT0312 – Algoritmos Avançados Revisão dos conceitos básicos: Exemplo de passagem de parâmetros por referência: #include<iostream> using namespace std; void troca(int &a, int &b){ int temp; temp=a; a=b; b=temp; } int main(){ int a=2,b=3; cout<<"Antes de chamar a funcao: \na="<<a<<"\nb="<<b<<endl; troca(a,b); cout<<"Depois de chamar a funcao: \na="<<a<<"\nb="<<b<<endl; } CCT0312 – Algoritmos Avançados Análise de Algoritmos: A análise de algoritmos estuda a correção e o desempenho de algoritmos. Procura respostas para perguntas do seguinte tipo: Este algoritmo resolve o meu problema? Quanto tempo ele consome para processar uma entrada n? A análise de algoritmos estuda paradigmas: divisão e conquista, programação dinâmica, busca local, aproximação, etc. CCT0312 – Algoritmos Avançados Etapas para Análise de Algoritmos: Correção Analise o pior caso Enfoque o termo principal do tempo de execução Melhore a ordem de magnitude Reduza os fatores constantes CCT0312 – Algoritmos Avançados Análise Assintótica: Também conhecida como Ordem O ou Big-O, é uma análise de algoritmos que se concentra em valores enormes para algoritmos complexos. Nessa matemática, as funções são classificadas em ordens; todas as funções de uma mesma ordem são equivalentes. As cinco funções mostradas abaixo, por exemplo, pertencem à mesma ordem: n2 , (3/2)n2 , 9999n2 , n2/1000 , n2+100n CCT0312 – Algoritmos Avançados Ordem O: Dadas funções assintoticamente não negativas f e g, dizemos que f está na ordem Ο de g e escrevemos f = Ο(g) se f(n) ≤ c · g(n) para algum c positivo e para todo n suficientemente grande. Em outras palavras, existe um número positivo c e um número n0 tais que f(n) ≤ c · g(n) para todo n maior que n0. Obs.: A notação Ο empregada nesta definição é conhecida como notação assintótica ou notação de Landau. CCT0312 – Algoritmos Avançados Ordem O: Exemplo 1: Se f(n) ≤ 9999 g(n) para todo n ≥ 1000 então f = Ο(g). Exemplo 2: Suponha que f(n) = 2n² + 3n + 4 e g(n) = n². Observe que 2n2 + 3n + 4 ≤ 2n2 + 3n2 + 4n2 = 9n2 desde que n ≥ 1. Resumindo, f(n) ≤ 9 g(n) para todo n ≥ 1. Portanto, f(n) = Ο(g(n)). CCT0312 – Algoritmos Avançados Ordem O: A notação O ajuda a calcular a função de custo para algoritmos complexos e grandes processamentos de dados. A definição formal da notação O não é usada diretamente. A notação O de uma função f é entregue pelas regras de simplificação a seguir: Se f(x) é a soma de vários termos, o que possuir maior taxa de crescimento é mantido, e todos os outros são omitidos. Se f(x) é um produto de diversos fatores, quaisquer constantes (termos do produto que não depende de x) são omitidos. CCT0312 – Algoritmos Avançados Ordem O: Exemplo de uso na computação: a operação da soma O(f(n)) + O(g(n)) pode ser usada para calcular o tempo de execução de uma sequência de trechos de programas. Considere três trechos de programas com tempos de execução O(n), O(n.n) e O(n . log . n). Logo, o tempo de execução dos dois primeiros trechos de programa é o O(máx(n, n.n)), que é O(n.n). Portanto, o tempo de execução final dos três trechos é O(máx(n.n, n . log . n), que é O(n.n). CCT0312 – Algoritmos Avançados Ordem O: Resumindo: Big-O descreve o comportamento geral do algoritmo em termos do crescimento do número de operações conforme cresce o número de elementos processados (n). Usamos a letra O seguida de uma função sobre n que descreva esse crescimento do algoritmo. Quanto mais rapidamente crescer as operações para processar os itens, "pior" é o desempenho do algoritmo (pois ele executa mais instruçõese, portanto, demora mais). O gráfico a seguir ilustra as curvas de crescimento mais comuns. CCT0312 – Algoritmos Avançados Ordem O: CCT0312 – Algoritmos Avançados Ordem O: Complexidade O(n!) (fatorial) → o número de instruções executadas cresce muito rapidamente para um pequeno crescimento do número de itens processados. É o pior comportamento para um algoritmo, pois rapidamente o processamento se torna inviável. Exemplo: um algoritmo que gere todas as possíveis permutações de uma lista. CCT0312 – Algoritmos Avançados Ordem O: Complexidade O(2n) (exponencial) → também é bem ruim, pois o número de instruções também cresce muito rapidamente, ainda que numa taxa menor do que o anterior. Exemplo: algoritmos que fazem busca em árvores binárias não ordenadas. Complexidade O(n2) (quadrático) → é factível, mas tende a se tornar muito ruim quando a quantidade de dados é suficientemente grande. Exemplo: algoritmos que têm dois laços (for) encadeados, como, por exemplo, o processamento de itens em uma matriz bidimensional. CCT0312 – Algoritmos Avançados Ordem O: Complexidade O(n log n) → é melhor do que o quadrático, sendo geralmente até onde se consegue otimizar algoritmos que são quadráticos. Exemplo: algoritmo de ordenação QuickSort (que tem essa complexidade no caso médio, mas que ainda assim é quadrático no pior caso). Complexidade O(n) (linear) → é aquele cujo crescimento no número de operações é diretamente proporcional ao crescimento do número de itens. Exemplo: algoritmos de busca em uma matriz unidimensional não ordenada. CCT0312 – Algoritmos Avançados Ordem O: Complexidade O(log n) (logaritmo) → é aquele cujo crescimento do número de operações é menor do que o do número de itens. Exemplo: algoritmos de busca em árvores binárias ordenadas (no caso médio, no pior caso continua sendo linear). Complexidade O(1) (constante) → é aquele em que não há crescimento do número de operações, pois ele independente do volume de dados de entrada (n). Exemplo: acesso direto a um elemento de uma matriz. CCT0312 – Algoritmos Avançados Ordem O: Exemplo prático: Calcular o custo do algoritimo usado para encontrar o maior elemento de uma matriz n x n: void maiorElemento (int vetor [ ], int matriz[ ][ ] ) { int i, j; for (i=0; i < matriz.length; i++) { // i=0 … i=n (n+1).2 vetor[i] = matriz[i][0]; // n for (j=0; j<matriz[i].length; j++) { // j=0 … j=n n.(n+1).2 if (matriz[i][j] > vetor [i]) // n . n vetor[i] = matriz [i][j]; // pior caso=n.n melhor caso: 0 } } } CCT0312 – Algoritmos Avançados Ordem O: Melhor caso: 0 n.(n+1).2 → 2n2 + 2n n (n+1).2 → 2n + 2 C(n) = 2n 2 + 5n + 2 Pior caso: n . n → n2 n.(n+1).2 → 2n2 + 2n n (n+1).2 → 2n + 2 C(n) = 3n 2 + 5n + 2 Complexidade O(n2) CCT0312 – Algoritmos Avançados Ordem O: Exemplo prático: Calcular o custo do algoritimo abaixo: void calculo (int vetor [ ]) { const n = vetor.length; // n for (int i=0, i < n; i++) { // (n+1).2 console.log(n); // n } } Complexidade O(n) CCT0312 – Algoritmos Avançados Ordem O: Exemplo de algoritmos complexos: O(n) → Busca em um vetor ordenado ou não ordenado. O(n2) → Busca em uma árvore multidimensional. O(log n) → Localizar um item em uma árvore de busca balanceada. O(log n!) → Realizar uma transformada de Fourier rapidamente. O(n!) → Resolver o problema do “Caixeiro viajante” usando o método da “força bruta”. CCT0312 – Algoritmos Avançados Ordem O - Exercícios: 1. Um algoritmo de busca binária em um vetor A, temos: um conjunto de valores previamente armazenados, nas posições A[i], A[i+1], ... A[n], em ordem crescente. Verificar se um número V qualquer está entre este conjunto de valores. Se o número V procurado não for encontrado o algoritmo deve retornar -1. Caso contrário, deve retornar a posição no vetor A que contém o número V. a) desenvolver o algoritmo proposto e; b) calcular a complexidade para o melhor caso e pior caso para o algoritmo proposto. CCT0312 – Algoritmos Avançados Ordem O - Exercícios: a) int buscaBinaria (int x, int n, int A[]){ int v= -1; while (v < x-1) { int m = (v-x)/2; if (A[m]<x) v = m; } return v; } CCT0312 – Algoritmos Avançados Ordem O - Exercícios: b) MELHOR CASO: o número está no meio do vetor → O(1). PIOR CASO: o número não está no vetor. Como a busca ocorre sempre na metade do vetor e o algoritmo quebra sucessivamente o vetor ao meio até 1, temos uma sequencia: n, n/2, n/22 ... 1. Assim, a sequencia v = log n → O(log n) CCT0312 – Algoritmos Avançados Ordem O - Exercícios: 2. Analise o pior caso do algoritmo abaixo que calcula o valor de nn. int funcao(int n) { int i, r; r = 1; for (i=1; i<=n; i++) r = r * n; return r; } → n → (n+1) . 2 → n C(n) = 3 . n + 1 → C(n) Complexidade O(n) CCT0312 – Algoritmos Avançados Ordem O - Exercícios: 3. Analise o pior caso do algoritmo abaixo: int funcao(int n) { int a, b, c, r; b = n; c = n; r = 1; while (b >= 1) { a = b % 2; if (a == 1) r = r * c; c = c * c * c; b = b /2; } return r; } → n → n + (log n) → n → n (pior caso) → n C(n) = 5 . n + log n Complexidade O(log n) Slide 1 Slide 2 Slide 3 Slide 4 Slide 5 Slide 6 Slide 7 Slide 8 Slide 9 Slide 10 Slide 11 Slide 12 Slide 13 Slide 14 Slide 15 Slide 16 Slide 17 Slide 18 Slide 19 Slide 20 Slide 21 Slide 22 Slide 23 Slide 24 Slide 25 Slide 26 Slide 27 Slide 28 Slide 29 Slide 30 Slide 31 Slide 32 Slide 33
Compartilhar