Aulas de. Algoritmos e Modelação Matemática

Tamanho: px
Começar a partir da página:

Download "Aulas de. Algoritmos e Modelação Matemática"

Transcrição

1 Aulas de Algoritmos e Modelação Matemática Paulo Mateus André Souto 2013

2 ii À guardiã

3 Precaução dirigida aos alunos Os temas apresentados neste trabalho são uma versão muito preliminar da sebenta da cadeira de Algoritmos e Modelação Matemática dos cursos de Engenharia Biomédica e de Matemática Aplicada à Computação do Instituto Superior Técnico da Universidade de Lisboa. Notem que pode haver gralhas, exemplos incompletos, definições e teoremas que podem estar apresentados de forma não uniforme. Na verdade podem existir tópicos que necessitarão de ser reescritos ou escritos de raiz. Parte dos exercícios são retirados das referências bibliográficas dadas. Futuramente, serão substituídos por outros que possam servir os nossos propósitos enquanto docentes da cadeira e de acordo com os objetivos estabelecidos para os conhecimentos a adquirir pelos alunos. No que diz respeito a esta notas o leitor deve estar preparado para se deparar com problemas de compreensão das matérias abordadas, dado que serão para já muito incompletas, e portanto sugerimos a leitura de livros considerados importantes nas áreas que serão abordadas de modo a complementarem a informação apresentada. Sugerimos por exemplo a leitura de [CSRL01], [Eck02] e outras referências que faremos ao longo deste trabalho. iii

4

5 Prefácio Este trabalho tenciona compilar num livro os conteúdos programáticos de um curso introdutório à Algoritmia e à Modelação Matemática. Os temas abordados neste trabalho são naturalmente parte integrante dos conteúdos programáticos planeados para a cadeira de Algoritmos e Modelação Matemática a lecionar nos cursos de Engenharia Biomédica e de Matemática Aplicada à Computação do Instituto Superior Técnico da Universidade de Lisboa. A primeira compilação data do ano de A ideia deste trabalho é providenciar ao alunos (e não só) uma referência de estudo, ainda que incompleta, sobre algoritmia com uma forte componente matemática de análise e correção sendo por isso dado um especial enfase a estas problemática sob o ponto de vista de matemático e do ponto de vista das ciências da computação. A juntar a este objetivo pretende-se que este compêndio possa futuramente ser também uma ferramenta útil para outros cursos que abordem temáticas semelhantes. Pelo apoio financeiro, o autor André Souto está profundamente grato à Fundação para a Ciência e Tecnologia pela bolsa SFRH/BPD/76231/2011. Estamos também gratos a todos os membros do SQIG pelo excelente ambiente de trabalho pelo encorajamento dado. Ficamos também gratos aos alunos por qualquer comentário que ajudem a melhorar este trabalho Departamento de Matemática Instituto Superior Técnico, Universidade de Lisboa Security and Quantum Information Group v

6 Instituto de Telecomunicações Conteúdo Precaução dirigida aos alunos iii Prefácio v Conteúdo vi I Aulas Teóricas 3 1 Introdução e Bibliografia Método de Avaliação Bibliografia recomendada Conteúdos programáticos da cadeira Ferramentas a utilizar Java - Um primeiro contacto e um exemplo Pacotes utilitários em Java Introdução ao Java Funções Efeitos colaterais Tipos de dados primitivos Tipos e expressões Composição alternativa Atribuições Quick and Dirty Recursão em Java Declaração de vetores e matrizes e tensores Notação Assimptótica vi

7 2.10 Custos das operações em Java Análise da correção e complexidade de algoritmos imperativos: ordenação Noção de invariante e variante de um ciclo Algoritmos de ordenação e complexidade no pior caso e caso médio Algoritmo de Inserção Algoritmo Bubblesort Análise de algoritmos recursivos: Quicksort Continuação dos algoritmos de ordenação Algoritmo QuickSort Árvores de recursão e o Teorema Mestre Análise do algoritmo de Quicksort O melhor caso O pior caso O caso médio Tipos de dados abstratos e interfaces em Java Tipos de dados abstratos Implementação de tipos de dados abstratos em Java Implementação estática de Listas Implementação dinâmica de Listas Noções de Herança e Polimorfismo Herança Polimorfismo Pesquisa em Tabelas Pesquisa em Tabelas Implementação em listas ligadas Tabelas de dispersão Função geradora Caso de insucesso da pesquisa em tabelas Caso de sucesso da pesquisa em tabelas Escolha da função de dispersão vii

8 8 Árvores binárias de pesquisa Árvores binárias Árvores binárias de pesquisa B-trees Complexidade da inserção aleatória em árvores binárias de pesquisa B - trees Algoritmos em grafos Grafos Problema da acessibilidade Busca em Largura Árvore de Extensão Mínima Grafos planares Leitura e escrita em ficheiro usando Java Serialização Escrita e leitura de ficheiros em java II Projeto Objetivo Conceitos básicos Classificador Dados Classificar vs estimar Redes de Bayes Aprendizagem de Redes de Bayes Minimum Description Length (MDL) Tipos de dados para a 1 a entrega Amostra Grafos orientados Redes Bayesianas Bibliografia 129 1

9

10 Parte I Aulas Teóricas 3

11

12 AULA 1 Introdução e Bibliografia Avaliação; Bibliografia; Conteúdos programáticos da cadeira; Ferramentas a utilizar; Um primeiro exemplo de um programa em Java; 5

13 1.1 Método de Avaliação SECÇÃO 1.1 Método de Avaliação Avaliação da cadeira será feita segundo os seguintes parâmetros: 1 exame - a contar 50% da nota final, cujas datas devem ser defindas pelos alunos 1 projeto - a contar os restantes 50% da nota final, cuja elaboração será feita em Java. A descrição do projecto será publicada por volta da Semana 4 e terá três fases de entrega/apreciação: 1 a Parte - a entregar na Semana 8; 2 a Parte - a entregar na Semana 13; 3 a Parte - uma oral a realização na semana 14 onde será inferido a involvência individual de cada aluno na realização do projeto. Bonificação - a aplicar a quem só entregar um dos exames previstos. Bibliografia recomendada SECÇÃO 1.2 Para a cadeira, entre outras que serão oportunamente acrescentadas, sugerem-se como referências bibliográficas principais: [CSRL01] T. Cormen, C. Stein, R. Rivest, and C. Leiserson. Introduction to Algorithms. McGraw- Hill Higher Education, 2nd edition, [Eck02] B. Eckel. Thinking in Java. Prentice Hall Professional Technical Reference, 3rd edition, [MS13] P. Mateus, A. Souto, Aulas de Algoritmos e Modelação Matemática, Preprint 2013 (em preparação) 6

14 Capítulo 1. Introdução e Bibliografia Conteúdos programáticos da cadeira SECÇÃO 1.3 Do programa da disciplina fazem parte os seguintes conteúdos: 1. Programação imperativa em JAVA. Introdução ao estudo da correção e eficiência de algoritmo. Invariantes, análise no pior caso e no caso médio. Mecanismos de modularidade e orientação por objectos: herança, polimorfismo. Programação de GUI. Utilização de API s. 2. Algoritmos de ordenação elementares e avançados: inserção direta, seleção direta, bubblesort, quicksort, fusão binária e heapsort. Algoritmos e tipos de dados abstratos: pilhas, filas de espera, filas de prioridade, árvores, acervos. Implementações vetoriais e dinâmicas. Árvores binárias de pesquisa. Árvores de pesquisa equilibradas. Tabelas de dispersão. Resolução de colisões por encadeamento e por endereçamento aberto. Endereçamento linear, quadrático e dispersão dupla. Grafos e grafos pesados; 3. Complexidade computacional: P, NP, problema NP completo, redução polinomial. Tese Churck- Markov- Turing para circuitos Booleanos. Teorema de Cook e SAT. Chama-se a atenção dos alunos para o facto de os conteúdos poderem por vezes não ser apresentados pela ordem aqui expressa. Ferramentas a utilizar SECÇÃO 1.4 Para um bom funcionamento da cadeira será necessário que os alunos obtenham as seguintes ferramentas: Eclipse IDE for Java Developers disponível em 7

15 1.5 Java - Um primeiro contacto e um exemplo Java JDK no Windows, Mac OS, Linux disponível em Java - Um primeiro contacto e um exemplo SECÇÃO 1.5 A título de exemplo apresenta-se de seguida um programa em linguagem Java. O programa pretende que seja transmitido ao utilizador, quando corrido, a mensagem Olá Mundo! em Inglês, ou seja, Hello World!. 1 public class myfirstjavaprog { 2 public static void main ( String args []) { 3 System. out. println (" Hello World!"); 4 } 5 } Cada linguagem de programação tem as suas especificações no que diz respeito à sintaxe de escrita! A linguagem Java está pensada para uma utilização multiplataforma e é orientada a objetos pois, na sua base de escrita de programas estão, como o próprio nome indica, os objetos que, como se verá mais à frente têm, entre outras, atributos. No caso apresentado, o comando System.out.println instrui o sistema a produzir, numa linha, o conteúdo que lhe é dado. A versatilidade de um programa em Java decorre do facto de deste ser compilado, em vez do sistema suporte da máquina onde corre, numa máquina virtual inerente ao próprio desenvolvimento de Java. Esta funcionalidade traz também a vantagem de a linguagem Java pode ser mais rápida do que se fosse simplesmente interpretada. O código gerado na compilação pode ser transportado entre plataformas distintas que suporte tecnologia Java (nomeadamente Windows, Mac/Os, Unix, etc..) não sendo, portanto, necessário recompilar um programa antes de o executar, ao contrário do que acontece por exemplo com programas escritos em linguagem C. Na figura seguinte esquematiza-se de forma sucinta a execução de um programa em Java. 8

16 Capítulo 1. Introdução e Bibliografia Figura 1.1: Esquema da execução de um programa em Java. A tecnologia Java é distribuída para 3 plataformas: 1. J2EE (Entreprise Edition), para desenvolvimento de aplicações empresariais. 2. J2ME (Micro Edition), para dispositivos de capacidades limitadas (telemóveis e PDA s). 3. J2SE (Standard Edition), para desktop e servidores. Debaixo da plataforma existem vários directórios: 1. J2xx Runtime Environment (JRE): a) Interpretador JVM, classes de ambiente,... b) Usado apenas para correr aplicações. 2. J2xx Development kit (JDK): 9

17 1.5 Java - Um primeiro contacto e um exemplo a) JRE, compilador, classes utilitárias (Swing,...),... b) Usado no desenvolvimento de aplicações. 10

18 AULA 2 Pacotes utilitários em Java Introdução ao Java; Edição e compilação de programas; Tipos e expressões Declaração de variáveis Atribuição, composição sequencial, iterativa e alternativa de comandos Definição de funções e recursão Efeitos colaterais Vetores e matrizes Complexidade das operações em C e notação assimptótica Análise de complexidade do insertsort 11

19 2.1 Introdução ao Java SECÇÃO 2.1 Introdução ao Java Como já foi referido, ao contrário do Mathematica e de outras linguagens de programação, o código Java é compilado para uma máquina virtual em vez de interpretado. O compilador gera um ficheiro class em código máquina que pode ser chamado do sistema operativo com a linha de comando java. O código é editado, compilado e depurado no ambiente Eclipse. No que se segue pretende-se introduzir os conceitos básicos da linguagem Java, não na sua potencialidade máxima, isto é, lidando diretamente com objetos, mas a forma de programar sequencial usual em linguagens de programação como C. Ao tipo de programas que não usam as potencialidades da utilização de objetos costuma dar-se o nome de pacotes utilitários. Considere-se o seguinte exemplo de um programa em Java que calcula o fatorial de 10. Exemplo 2.1 (Cálculo de fatorial de 10) 1 class Utils { 2 // Exemplo 1: factorial de 10 3 public static void main ( String args []) { 4 int i,r; 5 r =1; 6 i =2; 7 while (i <=10) { 8 r=r*i; 9 i ++; 10 } 11 System. out. println ("A solucao e:"+r); 12 } 13 } Neste exemplo é de salientar os seguintes factos: a estrutura básica começa com uma classe que basicamente designa o conjunto de programas do qual faz parte o main e outras funções que visam a elaboração de uma tarefa, no caso o 12

20 Capítulo 2. Pacotes utilitários em Java cálculo do fatorial de 10. para inserir qualquer comentário numa linha de código acrescenta-se uma barra inclinada para a direita dupla, //, que o próprio programa interpreta como sendo algo que não é para interpretar ou compilar. o tipo de dados retornado pelo main é void isto é vazio, pois não se espera que o main devolva algum resultado mas sim que execute as tarefas descritas no seu interior. a declaração public static será estudada pormenorizadamente mais adiante a estrutura main(string args[ ]) será analisada mais à frente e neste momento o leitor deve pensar nela como algo que é fixo. qualquer variável que se utilize tem de ser declarada. Ao contrário de outras linguagens a declaração pode ser feita em qualquer altura do programa, não sendo necessário alocar a memória dessa variável à priori, isto é, assim que o programa se inicia. Cada variável tem uma designação e um tipo. No exemplo dado as variáveis i e r são do tipo inteiro. No seguimento, apresentar-se-ão outros tipos de variáveis. o ponto e vírgula ; é usado como terminação de instruções. a execução do programa é feita de forma sequencial em que as instruções são separadas por ; com a interpretação dada anteriormente. a instrução i = 2 designa-se por atribuição e consiste em dar à variável i o valor 2. os ciclos while têm a estrutura while(gurada){corpo} em que a guarda será, em muitos casos, uma condição de verificação que dependerá de uma ou mais variáveis que devem ser atualizadas dentro do corpo. O corpo é o local de escrita de comando a executar sempre que a condição na guarda se verifique. Antes de avançarmos, é conveniente frisar que Java é uma linguagem fortemente tipada, isto é, se se declarar que uma variável é inteira então ela não pode, em momento algum do programa, assumir valores que não sejam inteiros. 13

21 2.2 Funções SECÇÃO 2.2 Funções Tal como se referiu um programa em Java é idealizado sobretudo com recursos a funções. Apresenta-se então a forma básica de uma função em Java: 1 public static tipo de retorno nomefuncao ( tipo1 var1,..., tipon varn ) { 2 declaracao de var locais instrucoes ; return expressao 7 } em que é necessário observar o seguinte: 1. qualquer função devolve um valor e portanto é necessário dizer logo à partida que tipo de dado é devolvido. Isso é feito definindo tipo de retorno semelhante à declaração de variáveis. No caso de não se querer que um resultado seja devolvido deve declara-se o tipo como void. 2. toda a função tem um nome. 3. pode receber vários dados de diferentes tipos que é necessário listar como argumentos da função. 4. similarmente ao que foi dito para o main, há que declarar as variáveis, executar as instruções. 5. por último é necessário devolver um valor que pode ser dado por uma expressão através do comando return. 6. se existir um comando return então qualquer instrução elaborada posteriormente é ignorada. É conveniente observar que a primeira função a ser executada de um programa em Java é sempre a função main. No que se segue recupera-se o exemplo do cálculo do fatorial de 10 mas desta vez escrito como função. 14

22 Capítulo 2. Pacotes utilitários em Java Exemplo class Utils { 2 // Exemplo : funcao factorial 3 public static int factorial ( int k) { 4 int i,r; 5 r =1; 6 i =2; 7 while (i <=k) { 8 r=r*i; 9 i ++; 10 } 11 return r; // Retorno da funcao 12 } 13 public static void main ( String args []) { 14 int i =10; 15 System. out. println ("A solucao e :" + factorial (10) ); 16 } 17 } Note-se que a instrução i++ é equivalente à atribuição i = i + 1, mas como se explicar mais à frente esta forma de atribuição/atualização é computacionalmente mais rápida. Convida-se o leitor a verificar que na realidade, o programa apresentado devolve como resultado 10! Efeitos colaterais As funções têm acesso aos argumentos que lhes são passados mas não alteram os seus valores no local onde foram chamadas. A este tipo de passagem dá-se o nome de Passagem por valor! As funções podem alterar as variáveis globais (para além dos argumentos e variáveis locais).todos os tipos não primitivos (classes), que se explicará nas na secções subsequentes o que são, são passados por referência e podem ser alterados. (Na prática é uma passagem por valor enviando o endereço). A este tipo de passagem dá-se o nome de Passagem por referência! 15

23 2.2 Funções Em suma, se os dados forem do tipo primitivo, quando são dados à função, são copiados e o seu valor na função principal não se altera mesmo que usados dentro da função. No caso dos dados não serem primitivos são passados (e não copiados) e se alterados dentro da função são globalmente alterados. Exemplo public class Utils { 2 static public int r =0; 3 static public int numeropares ( int n) { 4 // quando a funcao efeito e chamada no programa principal main a 5 // variavel n domain e copiada para o argumento n de efeito 6 int j=n; 7 n =0; // o valor e alterado apenas no bloco da funcao e nao no main 8 while (j >=0) { 9 if( j % 2==0) r ++; // a variavel r e incrementada sempre que a 10 // funcao efeito e chamada 11 j - -; 12 } 13 return n; 14 } 15 static public void main ( String args []) { 16 int n =10; 17 numeropares (n); 18 numeropares (2* n); 19 System. out. println ("O n e " +n+ " e o r e " + r); 20 } 21 } Se se correr o programa seguinte o resultado que se obtém é O n é 10 e o valor de r é

24 Capítulo 2. Pacotes utilitários em Java Tipos de dados primitivos SECÇÃO 2.3 Expõe-se agora os tipos fundamentais que se pode atribuir a uma variável de um programa de Java: char que corresponde a um caratere (escrito em código Unicode 16); int que corresponde a um inteiro (com 32bits, o que equivale a representar números entre 2 31 e ); long que corresponde a um inteiro com uma representação de 64bits; float que corresponde a um número de vírgula flutuante de precisão simples; double que corresponde a um número de vírgula flutuante de precisão dupla; boolean que corresponde a um valor lógico de false e true; Existem outros tipos de variáveis que se podem utilizar em Java, nomeadamente vetores, matrizes e tensores, que serão objeto de reflexão mais à frente. Na tabela seguinte está a representação em ASCII de alguns caracteres. 17

25 2.4 Tipos e expressões Figura 2.1: Tabela de código de ASCII de alguns caracteres. SECÇÃO 2.4 Tipos e expressões Os operadores mais comummente utilizados são: Operadores aritméticos dos quais se salienta: soma + ; produto ; subtração ; divisão / que é inteira no caso das variáveis recebidas serem inteiras; resto da divisão inteira %; 18

26 Capítulo 2. Pacotes utilitários em Java Predicados e operadores lógicos dos quais se salienta: maior >; maior ou igual ; menor <; menor ou igual ; igual ==; diferente %; e &&; ou ; not!. Note-se que por exemplo 5/2 = 2 pois os dois tipos de dados são inteiros, mas 5.0/2 = 2.5 pois neste caso, prevalece o tipo de dado mais abrangente, no caso, float. Composição alternativa SECÇÃO 2.5 Existem algumas formas alternativas de fazer composição alternativa. Algumas dessas formas listam-se de seguida: 1 if ( guarda ) { 2 instrucao ; 3 } Neste primeiro exemplo executa-se a instrução se a condição da guarda for verdadeira e não se executa nada caso a condição da guarda seja falsa. 19

27 2.5 Composição alternativa 1 if ( guarda ) { 2 instrucao 1 3 } else { 4 instrucao 2 5 } No segundo exemplo executa-se a instrução se a condição da guarda for verdadeira e é executada outra instrução caso a condição da guarda seja falsa. 1 if ( guarda 1) { 2 instrucao 1 3 } else if ( guarda 2) { 4 instrucao 2 5 } else { 6 instrucao 3 7 } Aqui executa-se a instrução da primeira guarda cuja condição se verifique e não se faz nada caso contrário. 1 if ( guarda1 ) { 2 instrucao 1 3 } else if ( guarda2 ) { 4 instrucao 2 5 } else if } else if ( guardan ) { 8 instrucao n 9 } 20

28 Capítulo 2. Pacotes utilitários em Java Por fim, executa-se a instrução da primeira guarda cuja condição se verifique e é executada outra instrução caso a condição da guarda seja falsa. Todas estas formas de estruturar uma condição alternativa são válidas e úteis em problemas concretos. A sua utilização claramente depende do propósito pretendido. A título de exemplo de uso desta estrutura apresenta-se de seguida a função de Collatz. Para o leitor interessado, refere-se que a função de Collatz é tal que ainda ninguém provou ou refutou o facto de esta terminar sempre qualquer que seja o dado inicial. Exemplo 2.4 (Função de Collatz) Nesta função a ação depende do valor intermédio calculado. A computação termina quando o valor da computação for igual a 1. Se o valor for par, divide-se por dois e caso contrário, isto é, se o valor for ímpar, multiplica-se por 3 e soma-se 1. Note-se que para testar se um número é ímpar basta verificar se o resto da divisão inteira por 2 é igual a 1. 1 class Utils { 2 // Exemplo : Problema 3 n +1 - conjectura de Collatz 3 public static void collatz ( int n) { 4 while (n!=1) { 5 if ( n % 2==1) n =3* n +1; 6 else n=n /2; 7 } 8 } 9 // main : chamada a funcao de collatz 10 } Até à data de publicação destas notas sabe-se que para todo o número até a função de Collatz termina. Os tempos de execução desta função estão retratados no gráfico abaixo: 21

29 2.6 Atribuições Quick and Dirty SECÇÃO 2.6 Atribuições Quick and Dirty A expressão quick and dirty é usada com o intuito de se referir uma qualquer uma forma fácil e abreviada e não tão legível de uma expressão que tira partido das imperfeições da própria linguagem de programação. O Java é também uma linguagem que permite tais abreviaturas. São quick porque, de modo geral, são mais rápidas quando comparadas com as expressões mais completas que abreviam. São dirty porque podem tornar o código de leitura difícil. Uma boa programação evita ambiguidades dando primazia a legibilidade do código. Por norma, a forma mais legível de escrever uma atribuição é fazê-lo através do comando i=express~ao. 22

30 Capítulo 2. Pacotes utilitários em Java Contudo algumas destas expressões podem ser abreviadas: 1. i + +, que retorna i e incrementa i; i que incrementa i e retorna i incrementado; 3. i e i com as respetivas interpretações análogas às anteriores; 4. i+ = 10 que retorna i e incrementa i de dez unidades. 5. i = 10 e i = 10 com as respetivas interpretações análogas às anteriores; A linguagem Java herdou muito Quick and dirty. Outros exemplos são: while(1){} que representa num ciclo infinito; while(i < 0)i++ em que o significado é simplesmente um while com uma instrução única e que pode ser usado também noutras estruturas. Refere-se ao leitor o cuidado de este tipo de fenómenos apenas executar repetidamente uma instrução. inti=20; while (i ); Recursão em Java SECÇÃO 2.7 Como qualquer outra linguagem de programação, Java permite o uso de recursão, isto é, a chamada iterativa de uma função dentro da própria função. No exemplo seguinte ilustra-se o uso de recursão para o cálculo de um elemento da sucessão de Fibonacci. Recorde-se que se: 1 se n=1,2 f ib(n) = f ib(n 1) + f ib(n 2) caso contrário 23

31 2.8 Declaração de vetores e matrizes e tensores Exemplo 2.5 (Termos da sucessão de Fibonacci) 1 class Utils { 2 // Exemplo : Fibonacci 3 static public int fib ( int j ) { 4 if( j <=1) return j; 5 else return fib (j -1) + fib (j -2) ; 6 } 7 8 static public void main ( String args []) { 9 int n =10; 10 System. out. println (" fib ("+ n +")=" + fib (n)); 11 } 12 } Declaração de vetores e matrizes e tensores SECÇÃO 2.8 Para declarar que uma variável é um vetor ou uma matriz ou um tensão com um certo tipo de dado basta considerar: int[] a = new int[10]; double[] V = new double[20], W = new double[20]; double[][] A = new double[20][20]; double[][][] T = new double[10][10][10]; De notar que é necessário inicializar a variável e atribuir-lhe uma dimensão. Para aceder ao 1 o e ao último valor do vetor V considera-se respetivamente V[0] e V[19]. De forma semelhante a outras variáveis se se quiser passar como argumento a uma função, faz-se da seguinte 24

32 Capítulo 2. Pacotes utilitários em Java forma: double innerproduct(double V[], double W[], int size) Exemplo 2.6 (Produto interno) 1 double innerprod ( double V[], double W []) { 2 int i=0, size =V. length ; 3 double r =0. 0; 4 while (i< size ) { 5 r+=v[i]*w[i]; 6 i ++; 7 } 8 return r; 9 } Note-se que contrariamente à linguagem C podemos determinar o tamanho dos vetores através do comando V.length. Notação Assimptótica SECÇÃO 2.9 No cálculo do tempo que um dado algoritmo demora que se estudará durante o curso é importante, não determinar exatamente para cada instância o tempo exato, mas sim o tempo assimptótico, isto é, quando se consideram instâncias de um tamanho arbitrariamente grande quanto tempo demora, no máximo, o algoritmo a terminar quando comparado com o tamanho da instância. É comum na literatura considerar-se a notação O-grande para a análise da complexidade de algoritmos. Definição 2.1 Sejam f e g duas funções de domínio N. Diz-se que f (n) O(g(n)) se e somente se existe uma constante k N e uma ordem n 0 tais que para todo o n n 0 se tem que f (n) k g(n). Existem outras definições também elas importantes que o leitor interessado pode consultar no Capítulo 3 em [CSRL01]. Desta definição decorrem os seguintes casos particulares de especial relevo: 25

33 2.10 Custos das operações em Java Constante O(1); Logarítmico O(log n); Linear O(n); Quadrático O(n 2 ); Polinomial O(n c ) para algum c N; Exponencial O(c n ) para algum c N; SECÇÃO 2.10 Custos das operações em Java Como se sabe, um algoritmo é uma composição de instruções, comparações e operações aritméticas. Todas estas partes do algoritmo têm associadas um custo, em termos de tempo computacional. Portanto, na análise de tempos de execução é necessário determinar o custo destas operações básicas. Em Java: 1. Comparar inteiros ou vírgulas flutuantes tem um custo constante: O(1); 2. Operações aritméticas sobre inteiros ou vírgulas flutuantes: O(1); 3. Aceder a um valor de um vetor ou matriz: O(1); 4. Adição de inteiros, tem custo linear: O(n) onde n é o número de bits dos inteiros; 5. Multiplicação de inteiros, tem custo quadrático: O(n 2 ) onde n é o número de bits dos inteiros; 6. Resto da divisão, tem custo quadrático: O(n 2 ) onde n é o número de bits dos inteiros; 26

34 AULA 3 Análise da correção e complexidade de algoritmos imperativos: ordenação Noção de invariante e variante de um ciclo; Prova (informal) da correção de algoritmos imperativos; Algoritmos de ordenação; Análise da complexidade dos algoritmos de ordenação; Análise do pior caso; Análise de caso médio suposições probabilísticas 27

35 3.1 Noção de invariante e variante de um ciclo SECÇÃO 3.1 Noção de invariante e variante de um ciclo A correção e a terminação dos algoritmos são tópicos fulcrais nesta cadeira. A análise de ambos passa por encontrar propriedades relevantes dos ciclos que compõe o algoritmo que se mantêm quando essas partes do algoritmo são executadas. Essas propriedades designam-se por invariantes de ciclos e podem ser definidos na Lógica de Hoare pela seguinte regra: {C I}body{I} {I}while(C)body{ C I} Em jeito de interpretação desta regra e da relação com a correcção pode-se definir de forma informal de invariante como se segue Definição 3.1(informal de invariante) Uma asserção I diz-se um invariante para um ciclo while(g){ corpo } se, quando for verificada no início do ciclo, também é verificada no fim de cada iteração do ciclo. Assim, um algoritmo que envolve ciclos está correcto se partindo do princípio que o invariante é válido antes da execução do ciclo e, sempre que o invariante do ciclo é válido antes de cada iteração, também o é depois dessa iteração. Sem entrar em muito detalhe menciona-se que para tornar esta definição rigorosa era necessário definir os seguintes termos: 1. Estado do programa (valores das variáveis + estado do controlo); 2. Sintaxe da lógica para os invariantes (primeira-ordem com predicados relevantes); 3. Semântica; Informalmente entende-se por estado a configuração do programa, isto é, o valor das variáveis e o program counter (que comando está a ser executado). Nesta disciplina opta-se, como é comum na literatura, por utilizar linguagem matemática informal para analisar os algoritmos imperativos não sendo necessário recorrer à definição formal de correção de um algoritmo para demonstrar que este realmente funciona. É esperado apenas que o leitor consiga 28

36 Capítulo 3. Análise da correção e complexidade de algoritmos imperativos: ordenação identificar um bom invariante, isto é, que esteja relacionado com a construção do ciclo, e demonstre que o invariante realmente funciona. No exemplo que se segue explora-se esta abordagem encontrando um invariante para o cálculo do factorial com um ciclo decrescente. Exemplo 3.1 (Factorial) 1 public static int factorial ( int k) { 2 int i,r; 3 r =1; 4 i=k +1; 5 while (i >2) { 6 i-- 7 r=r*i; 8 } 9 return r; 10 } um invariante para o ciclo while é: r = n k=i k i > 1 n n Note-se que no início do ciclo i = n + 1, e portanto o produtório transforma-se em k = k = 1 k=i k=n+1 que coincide com o valor de r. Além disso claramente i 1. Por outro, supondo que no início de uma iteração i > 2 do ciclo, o valor de r é valor de r é ((i + 1) 1) r = i n k=i+1 k = n k=i+1 n k=i k então no fim dessa iteração obtém-se que o novo k. No caso em que a guarda falha, isto é, quando i 2, significa que i obtido da iteração anterior foi 2, e portanto em qualquer dos casos, i 1 como se pretendia. Assim demonstra-se de forma informal a seguinte proposição: Proposição 3.2 Se o programa anterior terminar então este calcula o fatorial de um número. 29

37 3.1 Noção de invariante e variante de um ciclo Definição 3.2 Uma relação (A, <) é bem fundada se qualquer subconjunto não vazio B de A tem elemento minimal. Exemplo 3.3 Claramente (N, <) é uma relação bem fundada. Outro exemplo de uma relação bem fundada é a relação ({{0, 1}, {1, 2}, {0, 1, 2}}, ) sendo os seus elementos minimais {0, 1}, {1, 2} Definição 3.3 Um variante de um ciclo while(g){body} e invariante I é um mapa dos estados que satisfazem I para o suporte A de uma relação bem fundada (A, <) cujo valor decresce estritamente em relação a < por cada iterada do ciclo. Daqui pode-se deduzir uma regra da correção total, que no cálculo de Hoare se expressa através do predicado: < é bem fundada, [I C V = z]s[i V = z]. [I] while C do S[I C] Teorema 3.1 Dado um invariante I, um ciclo while termina sse existe um variante para esse ciclo e para esse invariante I. Definição 3.4 Um variante diz-se natural se o suporte da relação bem-fundada for um subconjunto dos números naturais. Teorema 3.2 Ciclos while com variantes naturais não são universais. O teorema anterior é um resultado negativo muito importante. Note que nesta disciplina todos os procedimentos estudados são algoritmos e, o problema da terminação destes é simples e tem (quase sempre) variante natural. Serve no entanto de aviso que nem todos os algoritmos admitem variantes naturais. 30

38 Capítulo 3. Análise da correção e complexidade de algoritmos imperativos: ordenação SECÇÃO 3.2 Algoritmos de ordenação e complexidade no pior caso e caso médio Nesta secção apresentam-se alguns dos algoritmos de ordenação mais comummente utilizados como instrumento utilitário para introduzir e explorar conceitos importantes de algoritmia que fazem parte da estrutura curricular do curso. Sugere-se a leitura cuidado do Capítulo 2 do livro [CSRL01] para um estudo completo dos tópicos aqui referidos. O problema de ordenação é frequente e recorrente na prática. Formalmente, define-se este problema da seguinte forma: Problema 3.4 (Problema de Ordenação) Instância Dado um vetor de números (a 1,..., a n ); Solução Uma permutação dos elementos desse vetor (a 1,..., a n) de tal modo a que os elementos estejam ordenados. Por exemplo, dado o vector (31; 41; 59; 26; 41; 58), pretende-se obter o vector reordenado (26; 31; 41; 41; 58; 59). No que se segue apresentam-se vários algoritmos que resolvem este problema Algoritmo de Inserção Como o nome indica, o que o algoritmo faz é inserir um nome elemento e ordenar os elementos em mão. O algoritmo pode ser escrito em Java com o seguinte código: 31

39 3.2 Algoritmos de ordenação e complexidade no pior caso e caso médio 1 public static void insertionsort ( int V []) { 2 int i=1, j, aux,n=v. length ; 3 while (i < n) { 4 aux =V[i]; 5 j = i -1; 6 while (j >=0 && V[j] >aux ) { 7 V[j +1] = V[j]; 8 j - -; 9 } 10 V[j +1] = aux ; 11 i ++; 12 } 13 } Deixa-se ao cuidado do aluno provar a seguinte proposição: Proposição 3.5 Se o programa anterior terminar então este retorna um vetor ordenado. Para tal sugere-se que o leitor verifique que para o ciclo interior o predicado: Os valores V[j + 1],.., V[i] aux estão ordenados e i n é um invariante e que o predicado: Os valores V[0]...V[i 1] estão ordenados e i n é um invariante do ciclo exterior. Como já foi referido numa das aulas anteriores, é importante saber quanto tempo (ou espaço) um algoritmo demora (consome) até terminar a computação. A esta análise, do tempo de execução, chamase análise da complexidade temporal de um algoritmo. Existem outro tipos de análise de complexidade que saem do âmbito desta cadeira. Recorde-se, da última aula, em que foi apresentado o custo básico das operações em Java. Como as operações que não dependem de ciclos são todas constantes, no pior caso, pode-se dizer que dado um i 32

40 Capítulo 3. Análise da correção e complexidade de algoritmos imperativos: ordenação o ciclo interior será percorrido i vezes e o ciclo exterior será percorrido n 1 vezes, logo, no pior caso o tempo de execução do algoritmo é: O ( n 1 c 1 + i=1 ) ( i 1 c 2 = O c j=0 ) n 1 ( c ) i = O 2 n(n 1) = O(n 2 ) i=1 O pior caso acontece quando o vetor já está ordenado mas por ordem decrescente. No melhor caso, que acontece quando o ciclo interior não é percorrido vez nenhuma em cada i, ou seja, quando o vetor já está ordenado por ordem crescente, o tempo de execução é: ( n 1 ) O c i=1 = O (c(n 1)) = O(n) Comparando os dois casos, verifica-se uma diferença substancial entre o melhor e o piro caso. Pode-se ainda fazer outro tipo de análise temporal baseada no cálculo da média. Tal análise designa-se por análise no caso médio. Esta noção permite ter uma ideia de como é o comportamento aproximado do algoritmo numa qualquer situação. Pode acontecer que as instâncias que conduzem ao pior caso, nunca ocorram e portanto a análise do pior caso seja pouco verosímil. Contudo, para se proceder a análise do caso médio é necessário fazer suposições probabilísticas sobre as entradas. Assim, diferentes suposições podem levar a diferentes comportamentos médios. A análise do caso médio corresponde a calcular o valor esperado da complexidade temporal tendo em linha de conta a distribuição de probabilidades das entradas. Exemplo 3.6 Retomando o exemplo do algoritmo de inserção, dado que nada se sabe sobre a distribuição das entradas assume-se que o ciclo interior poderá terminar com j = 1, 0,...., i 1 equiprovavelmente. Assim, dado j o ciclo será percorrido i j + 1 vezes e como há i + 1 possibilidades para o valor de j conclui-se que a média de tempo gasta em cada ciclo interior é: i 1 (i j + 1)p(j) = j= 1 = i j=0 (i j)p(j) i jp(j) j=0 = i j=0 j i + 1 = i 2. Logo, em média, o tempo de execução do algoritmo de inserção é: 33

41 3.2 Algoritmos de ordenação e complexidade no pior caso e caso médio O ( ) n 1 i c 1 + c 2 = O 2 i=1 ( n 1 i=1 ) ( ) i 1 = O n(n 1) = O(n 2 ) Algoritmo Bubblesort O próximo algoritmo de ordenação designa-se por Bubblesort pois tem as suas raízes na diferença de potencial de bolhas de ar, que quando o volume de ar for maior deixa fluir para a bolhas mais pequena trocando de posição. 1 class Node { 2 int val ; 3 Node next ; 4 }; 5 6 public class Lista { 7 int comp ; 8 Node prim ; }; Deixa-se ao cuidado do aluno mostrar as seguintes proposições: Proposição 3.7 O algoritmo Bubblesort está correto e para, isto é, termina sempre e no fim produz como resultado um vector ordenado. Sugere-se a utilização dos invariantes: V[n i + 1]...V[n 1] está ordenado e tem os maiores elementos de V e i n + 1 e V[j] é o maior valor de V[0]...V[j] de V e j n i 34

42 Capítulo 3. Análise da correção e complexidade de algoritmos imperativos: ordenação Proposição 3.8 O algoritmo Bubblesort tem complexidade do pior caso, melhor caso e caso médio igual a O(n 2 ). 35

43

44 AULA 4 Análise de algoritmos recursivos: Quicksort Quicksort; Notação assimptótica Ω e Θ; Árvores de recursão; Solução de recorrências e o Teorema Mestre; Análise do algoritmo de Quicksort; Análise no melhor caso; Análise no pior caso; Análise no caso médio. 37

45 4.1 Continuação dos algoritmos de ordenação SECÇÃO 4.1 Continuação dos algoritmos de ordenação Nesta secção analisar-se-á um outro algoritmo de ordenação que apesar de ser mais complexo de entender os seus passos é menos custoso computacionalmente, isto é, com melhores performances em termos de tempo, como veremos Algoritmo QuickSort Este algoritmo foi inventado por Hoare em 1960 enquanto aluno da Universidade de Moscovo a sua publicação data de 1962 após uma série de refinamentos. O código Java do QuickSort é o seguinte: O algoritmo Quicksort tem por base uma estratégia conhecida por divisão e conquista que basicamente consiste em dividir o problema em problemas mais pequenos. Seguidamente o Quicksort ordena, ou seja, conquista as sublistas de menores e maiores recursivamente até que a lista completa se encontre ordenada. Resumidamente os passos deste algoritmo de ordenação são: 1. Escolhe-se um pivot, que é um elemento da lista: 2. Troca-se os elementos da lista de forma que todos os elementos anteriores ao pivot sejam menores que ele, e todos os elementos posteriores ao pivot sejam maiores que ele. 3. No final do processo o pivot estará na sua posição final e haverá duas sublistas possivelmente não ordenadas. Essa operação é denominada de partição; 4. Recursivamente ordena a sublista dos elementos menores e a sublista dos elementos maiores escolhendo novos pivots; Note-se a base da recursão deste algoritmo são as listas de tamanho sem elementos ou com um único elemento. Por essa razão estão claramente ordenadas. Note-se também que este algoritmo é finito, pois a cada iteração há pelo menos um elemento que é colocado na sua posição final e não mais será manipulado na iteração seguinte. 38

46 Capítulo 4. Análise de algoritmos recursivos: Quicksort 1 public static void main ( String [] args ) { 2 3 void quicksort ( int vec [], int left, int right ){ 4 int r; 5 if ( right > left ) { 6 r = partition ( vec, left, right ); /* pivot */ 7 quicksort (vec, left, r - 1); 8 quicksort (vec, r + 1, right ); 9 } 10 } int partition ( int vec [], int left, int right ){ 13 int i, j; 14 i = left ; 15 for ( j = left + 1; j <= right ; ++ j) { 16 if ( vec [j] < vec [ left ]){ 17 ++i; 18 swap (vec,i,j); 19 } 20 } 21 swap (vec,left, i); 22 return i; 23 } void swap ( int vec [] a, b){ 26 int tmp ; 27 tmp = vec [a]; 28 vec [a] = vec [b]; 29 vec [b] = tmp ; 30 } } Na figura que se segue ilustra-se o comportamento do procedimento partition. A entrada vec[r] é o pivot. As células sombreadas ao de leva são elementos do vetor e são parte a primeira partição com elementos 39

47 4.1 Continuação dos algoritmos de ordenação não maiores que vec[r]. As células mais escuras são elementos da segunda partição com valores maiores que vec[r].os elementos brancos ainda não fizeram parte de uma das partição e o último elemento sem sombra é o pivot. (a) vector inicial e a consideração das variáveis. Sem que nenhum dos elementos esteja numa das duas partições. (b) O valor 2 é trocado com ele próprio e colocado na partição dos valores menores. (c) (d) Os valores 8 e 7 são adicionados à partição dos valores maiores. (e) Os valores 1 e 8 são trocados e a partição dos menores cresce. (f) Os valores 3 e 7 são trocados, e a partição dos menores cresce. (g) (h) A partição dos maiores cresce para incluir 5 e 6, e o ciclo termina. (i) Nas linhas 7 8, o elemento pivot é trocado de modo a ficar entre as duas partições. Figura 4.1: Exemplo de aplicação do processo de partição No que se segue mostra-se por indução sobre os invariantes que o algoritmo Quicksort está correcto, ou seja, faz exactamente o que devia fazer. 40

48 Capítulo 4. Análise de algoritmos recursivos: Quicksort Proposição 4.1 O algoritmo Quicksort apresentado na página?? está correto. A prova desta proposição passa por mostrar que Demonstração : Para a base (n = 1): Qualquer vetor com apenas um elemento está claramente ordenado, Hipótese de Indução: Suponha que para vetores de tamanho n 1 o algoritmo Quicksort ordena bem os elementos. Suponha que o procedimento partition está a funcionar corretamente, isto é, que à direita do pivot só há elementos maiores que o pivot e à esquerda só há elementos menores ou iguais ao pivot. Como quicksort(vec, le f t, r 1) e quicksort(vec, r + 1, right) são vectores de dimensão no máximo n 1 (pois r é no máximo n) por hipótese de indução ficam ordenados depois da aplicação do Quicksort. Por outro lado, como o pivot fica exactamente na posição r, e qualquer elemento à sua esquerda, isto é com índice menor, é menor que o pivot, e qualquer elemento à sua direita, isto é com índice maior, é maior que o pivot, então conclui-se que o algoritmo quicksort está implementado corretamente. Deixa-se ao cuidado do leitor verificar que o procedimento partition está correto indicando os seus invariantes e demonstrando que realmente são invariantes. Antes de se avançar com a análise da complexidade do algoritmo Quicksort complementam-se as noções do notação O-grande introduzidas na segunda aula. Recorde-se que Definição 4.1 Sejam f e g duas funções de domínio N. Diz-se que f (n) O(g(n)) se e somente se existe uma constante k N e uma ordem n 0 tais que para todo o n n 0 se tem que f (n) k g(n). Tal como foi referido na altura, existem outras noções também elas importantes. São o caso da noção de limite inferior Ω e exata Θ. Como os próprios nomes indicam, Ω refere-se a um comportamento que minimiza assimptoticamente a função e Θ refere-se a um comportamento assimptoticamente exato da função. As definições formais que serão usadas durante o curso e em especial nesta aula são as que se descrevem a seguir: Definição 4.2(Notação Ómega) Sejam f e g duas funções de domínio N. Diz-se que f (n) Ω(g(n)) se e somente se existe uma constante k N e uma ordem n 0 tais que para todo o n n 0 se tem que k g(n) f (n). 41

49 4.2 Árvores de recursão e o Teorema Mestre Definição 4.3(Notação Theta) Sejam f e g duas funções de domínio N. Diz-se que f (n) Θ(g(n)) se e somente se f O(g) e g O( f ) ou seja se existem constantes k 1, k 2 N e uma ordem n 0 tais que para todo o n n 0 se tem que k 1 g(n) f (n) k 2 g(n). Figura 4.2: Esquema ilustrativos dos vários tipos de enquadramentos das funções. Árvores de recursão e o Teorema Mestre SECÇÃO 4.2 Já se referiu que recursão é o processo que um algoritmo quando um dos passos do procedimento em questão envolve a repetição completa deste mesmo procedimento. As árvores de recursão são uma forma esquemática de representar a informação sobre a recursão que permite visualizar o esforço computacional de todo o processo recursivo e estabelecer limites para este esforço. No exemplos que se seguem considere-se que T(n) é o tempo de necessário para uma recursão com uma entrada de tamanho n. Exemplo 4.2 (Recursão da forma T(n) = 2T(n/2) + n 2 ) Da observação da figura seguinte, dado que a árvore é balançada, concluímos que há log n níveis (explique porquê esta profundidade!) e no nível i o custo total de chamada é n 2 /2 i. Logo: T(n) = O ( ) log n 1 n 2 1/2 i = O ( n 2 O(1) ) = O(n 2 ). i=0 42

50 Capítulo 4. Análise de algoritmos recursivos: Quicksort Figura 4.3: Exemplo de uma árvore de recursão. Exemplo 4.3 (Recursão da forma T(n) = T(n/3) + +T(2n/3) + n) Figura 4.4: Exemplo de uma árvore de recursão. Observando a figura comece por explicar o porquê da profundidade ser log 3/2 n. Observe que a árvore não é totalmente balançada pois o custo de um dos lados é superior ao do outro. Contudo o custo cada nível de chamada da recursão é n. Logo: T(n) = O n log 3/2 n 1 i=0 1 = O (n O(log n)) = O(n log n). No teorema seguinte estabelece-se de que forma podemos resolver recorrências de forma sistemática! Este teorema é uma ferramenta importante na análise assimptótica de recorrências pois a maioria das recorrências que são usadas em algoritmos ditos de divisão e conquista (divide and concur) que usu- 43

51 4.2 Árvores de recursão e o Teorema Mestre almente se estudam estão nas condições do teorema. É conveniente frisar que nem todas as recorrências se podem resolver usando este teorema. Teorema 4.1 Sejam a 1 e b > 1 constantes, f e T duas funções definidas sobre os números naturais que satisfazem a seguinte relação de recorrência: T(n) = at(n/b) + f (n) em que n/b = n/b ou n/b = n/b. Então T(n) pode ser limitado da seguinte forma: ( se f (n) O n log a ε) b para algum ε > 0 então T(n) O(n log b a ); ( se f (n) Θ n log a) b então T(n) Θ(n log b a log n); ( se f (n) Ω n log a+ε) b para algum ε > 0 e se a f (n/b) c f (n) para alguma constante c < 1 então T(n) O(n log b a ). Aqui apresenta-se um esquisso da prova do teorema deixando-se a prova formal e completa com detalhe ao cuidado do leitor que poderá, no entanto consultá-la em [CSRL01]. Note-se que o resultado da recorrência depende da comparação entre f (n) e (n log b a ). Note ainda que a diferença entre esses valores tem que ser polinomial e portanto o teorema mestre não se aplica a todas recorrências. Usar-se-á este Teorema na próxima secção para determinar a complexidade do Quicksort. Demonstração Esquisso da prove : Apresenta-se a prova para o caso em que se assume que n é uma potência de b, i.e., n = b i para algum i. Para as potências de b a recorrência assume os seguintes valores: Θ(1) se n = 1 T(n) =. at(n/b) + f (n) se n = b i Assim, a solução da recorrência é: log b n 1 T(n) = Θ(n log b a ) + j=0 a j f (n/b j ). A figura seguinte ajuda a entender o argumento que demonstra esta solução. 44

52 Capítulo 4. Análise de algoritmos recursivos: Quicksort Figura 4.5: A recorrência gerada por T(n) = at(n/b) + f (n). Esta árvore tem n log b a folhas. A cada passo da iteração a árvore bifurca em a ramos e a soma de cada um deles é a f (n/b). No fim há exatamente n log b a folhas cada uma com um custo constante. Cada nó interior de profundidade j custa a j f (n/b j ) operações. Daqui resulta a relação descrita. Apresenta-se a prova da alínea a mostrando que se g(n) = então g(n) O(n log b a ). Dado que f (n) O(n log ba ε então f (n/b j ) O((n/b j ) log ba ε. Logo log b n 1 a j f (n/b j ) e f (n) O(n log ba ε para algum ε > 0 j=0 Como b e ε são constantes então log b n 1 j=0 log b n 1 ( ab a j f (n/b j ) = n log b a ε ε ) j j=0 b log b a log b n 1 = n log b a ε b εj j=0 = n log b a ε ( O(n log b a ) ) j b ε log b n 1 b ε 1 45

53 4.3 Análise do algoritmo de Quicksort SECÇÃO 4.3 Análise do algoritmo de Quicksort O melhor caso O algoritmo Quicksort na melhor das hipóteses corre em tempo da ordem n log n. Este caso acontece quando o número de partições que será preciso efetuar é mínimo. Assim, o melhor caso ocorre quando a árvore é balançada, ou seja, quando a partição divide sempre a meio os elementos pelas duas chamadas recursivas. Assim, a recursão no melhor caso pode ser expressa por: T(n) = 2T(n/2) + Θ(n). Aplicando o Teorema mestre com a = b = 2 e f (n) = n obtemos que no melhor caso o Quicksort é linear, ou seja, se t(n) for o tempo de execução do Quicksort para listas de tamanho n então t(n) Θ(n log n) O pior caso No pior caso acontece quando o algoritmo Quicksort ao chamar o procedimento partition e este colocar todos os elementos numa única partição, pois nesse caso o número de chamadas do procedimento partition é máximo. Assim, a recursão no pior caso do algoritmo Quicksort pode ser expressa por: T(n) = T(n 1) + Θ(n). Esta recursão não encaixa no Teorema Mestre mas pode ser resolvida facilmente. Note-se que: T(n) = T(n 1) + Θ(n) = T(n 2) + Θ(n 1) + Θ(n) Logo, a recursão toda pode ser expressa por: = T(n 3) + Θ(n 2) + Θ(n 1) + Θ(n). 46

54 Capítulo 4. Análise de algoritmos recursivos: Quicksort T(n) = Logo, no pior caso, o Quicksort é quadrático. ( n n ) Θ(k) = Θ n = Θ(n 2 ). k=1 k= O caso médio Para analisar o caso médio do Quicksort suponha que a separação que é feita pelo procedimento partition é equiprovável, ou seja, com igual probabilidade obtêm duas partições, uma com i elementos e outra com n 1 i elementos. Logo, a expressão da recursão neste caso é descrita por: T(n) = 1 n ( T(0) + T(n 1) + ) n 1 T(i) + T(n 1 i) + Θ(n). i=1 Como no pior caso T(0), T(n 1) O(n) pode-se fazer a seguinte majoração: T(n) = 1 n 1 n = 2 n ( T(0) + T(n 1) + ( n 1 T(i) + T(n 1 i) i=1 ) ( n 1 T(i) i=1 + Θ(n) Para terminar a prova é necessário os seguinte lema: ) n 1 T(i) + T(n 1 i) + Θ(n) i=1 ) + Θ(n) Lemma 4.4. Seja T(n) a expressão de recorrência descrita acima. Então, existem a, b > 0 tais que para todo o n, T(n) Θ(n) a n log n + b. Demonstração : A prova é feita por indução sobre n. (Base n = 1): Basta escolher a = 1 e b = T(1). Contudo para que os detalhes no passo indutivo sejam coerentes, considera-se b = T(1) e a tal que 2b a 4 n b. Com estas escolhas temos que: a n log n + b = a 1 log 1 + T(1) = T(1) T(1) Θ(n). 47

55 4.3 Análise do algoritmo de Quicksort (Passo indutivo): Suponha que para todo o i n 1 T(i) a n log n + b para alguns a e b positivos. Então: T(n) Θ(n) ( ) 2 n 1 n T(i) + Θ(n) Θ(n) i=1 ( ) 2 n 1 n a i log i + b i=1 2a n c + 2b (n 1) n 2a n ( 1 2 n2 log n 1 ) 8 n2 + 2b (n 2) n a n log n a 4 n + 2b a n log n + b A última desigualdade decorre da escolha feita de a. Resta mostrar a desigualdade n 1 i log i < 1 2 n2 log n 1 8 n2. i=1 n 1 i log i (log n 1) i=1 n 1 = log n i=1 n/2 1 i=1 n/2 1 i i=1 i + log n i n 1 i= n/2 i 1 2 n2 log n 1 8 n2 Retomando o cálculo da complexidade média tem-se então que T(n) 2 n 2a n ( ) n 2 T(i) + Θ(n) i=1 ( 1 2 n2 log n 1 8 n2 ) + Θ(n) a n log n a n + 2b + Θ(n) 4 O(n log n) 48

56 AULA 5 Tipos de dados abstratos e interfaces em Java Interfaces em Java vs tipos de dados abstratos; Classes; Implementação de tipos de dados abstratos. 49

57 5.1 Tipos de dados abstratos SECÇÃO 5.1 Tipos de dados abstratos Como já se viu numa das primeiras aulas, O Java tem vários tipos primitivos de dados, como o int. Cada um destes tipos possuem: Conjunto de valores ou domínio; Operações possíveis entre estes valores; Por vezes, os tipos de dados primitivos não permitem, dado um problema concreto, representar de forma clara os objetos inerentes ao problema. Torna-se então útil definir novas entidades, que se designam de ojetos abstratos. Da mesma forma, que os tipos primitivos, além dos valores, têm ações, ao definirem-se outro tipo de dados, torna-se necessário especificar os seus possíveis valores e as operações que podem ser executadas sobre esses mesmos dados. O objetivo destes dados é reduzir a informação necessária para a criação/programação de um algoritmo através da abstração das variáveis envolvidas criando uma única entidade fechada que agrega as suas principais características comuns especificando as operações próprias que lhe inerentes. A título de exemplo consideremos um aluno que se matricula no universidade. Sem tipos de dados abstratos cada aluno teria de ser representado por variáveis soltas, como nome, idade, localidade, curso, disciplinas, notas etc. Estas variáveis seriam operadas independentemente sem que houvesse uma ligação lógica entre elas. Com este tipo de dados abstraem-se as propriedades comuns a todos os alunos e cria-se uma entidade, comummente designada por tipo, estudante. Para potenciar a utilização de tipos abstratos é necessário definir que operações e ligações lógicas existem entre as várias características do tipo. Um outro exemplo é o de uma fila de inteiros que basicamente consiste como o próprio nome indica um conjuntos de inteiros (visto com um vetor) que admite inserção de novos elementos e a remoção de elementos já existentes, designar o primeiro elemento e especificar se a fila é ou não vazia. Uma fila é uma estrutura sujeita à regra de operação First In, First Out que de forma informar significa que sempre que houver uma remoção, o elemento removido é o que está na estrutura há mais tempo, ou seja, o primeiro objecto inserido na fila é também o primeiro a ser removido. 50

58 Capítulo 5. Tipos de dados abstratos e interfaces em Java Como foi referido, as filas têm operações e predicados que podem ser vistas como funções. A título de exemplo expositório considere-se assinatura (da designação das operações, tipos dos seus argumentos, tipos de resultado): nova, que partindo do vazio, cria uma nova fila; insere, que partindo de uma fila não vazia e um inteiro retorna uma fila primeiro, que dada uma fila devolve um inteiro, no caso o primeiro inteiro da fila; vazia, que devolve um valor booleano que indica se uma dada fila esta está vazia; retira, que dada uma fila devolve uma outra fila igual à primeira com o primeiro elemento retirado; Todas estas operações são regidas por relações ou regras de cálculo, entre elas, que permitem derivar o resultado das operações para cada valor dos argumentos. Estas relações são usualmente designadas por especificações. Por exemplo, quando se opera primeiro(insere(nova, N)), o resultado pretendido é N dado que o primeiro elemento de uma nova fila em que se inseriu um número N deve devolver que o seu primeiro elemento é o próprio N. Para definir mais rigorosamente as relações entre elas é necessário recordar alguns conceitos. Os termos ou entidades de tipo Fila são um conjunto definido indutivamente da seguinte forma: O conjunto de variáveis X está contida nos termos de Tfila; O operador O p que toma termos t 1,..., t k respetivamente em T 1,..., T k e devolve uma fila é um termo em Tfila. Para uma introdução mais completa e cuidado desta temática sugere-se a leitura atenta do Capítulo 9 do livro [CSS + 04]. Observe que Tfila é a álgebra livremente gerada a partir da assinatura e conjunto de variáveis X. Uma equação é uma igualdade entre termos do mesmo tipo t 1 = t 2. 51

59 5.2 Implementação de tipos de dados abstratos em Java Uma especificação equacional é um conjunto de equações. Exemplo 5.1 primeiro(insere(nova, N)) = N; primeiro(insere(insere(w, N1), N2) = primeiro(insere(w, N1)); vazia(nova) = true vazia(insere(w, N)) = f alse retira(insere(nova, N)) = nova retira(insere(insere(w, N1), N2) = insere(retira(insere(w, N1)), N2) Deixa-se ao cuidado do leitor verificar e interpretar estas equações. Uma classe é um tipo de dados que contém o molde,isto é a especificação dos seus elementos (objetos), à semelhança de um tipo básico como inteiro que contém o molde para as variáveis declaradas como inteiros. A classe envolve e associa funções e dados, controlando a forma como é feito o acesso a estes. A sua definição consiste em especificar os seus atributos (dados) e seus métodos (funções). Um objecto é uma instanciação de uma classe. Uma interface é o conjunto de métodos que permitem alterar os atributos dos objetos das classes. Implementação de tipos de dados abstratos em Java SECÇÃO 5.2 Considera-se o exemplo de implementação de Fila discutido na secção anterior com as funcionalidades: nova (que pode ser omitido dado que o Java tem um construtor universal); insere; retira; vazia; 52

60 Capítulo 5. Tipos de dados abstratos e interfaces em Java Descrevem-se dois métodos de implementação: um estático em que o tamanho dos objetos é definido à priori e outro em que o tamanho dos objetos é dinâmico. A vantagem de usar este método, apesar de ser ligeiramente mais complicado de implementar, é que apenas aloca memória à medida que é necessária permitindo que quando o valor de uma variável deixa de ser preciso o java liberta essa memória. O java tem uma funcionalidade designada de Garbage collector que vai percorrendo as variáveis e libertando as que não são necessárias. O interface funciona como a parte o projeto de Java que descrimina a assinatura do tipo de dados abstrato, ou seja, o interface declara que métodos tem os objetos do tipo Fila. 1 public interface Fila { 2 // Desnecessario implementar a nova pois ha sempre um construtor new 3 // Todas as funcoes que retornam e recebem uma fila devem retornar void e retirar essa fila do argumento 4 // Um objecto tem estado e comportamento, estas funcoeses alteram o estado e naxso retornam nada. 5 6 void insere ( int x); 7 int primeiro (); 8 void retira (); 9 boolean vazia (); 10 } Note-se que os métodos que foram declarados devolvem, de acordo com a funcionalidade que implementam, um tipo de dados (primitivo). Dado que o Java é uma plataforma muito usual e muitos utilizadores partilham o seu código é costume, para cada implementação, criar-se uma espécie de pequeno manual de funcionamento do código, onde se pretende informar quem utiliza o código das funcionalidades que se projetaram e programaram naquele pedaço de código. Essas indicações das funcionalidade criadas são usualmente descritas como comentários sucintos com a indicação do que cada uma delas é suposto fazer e devolver de modo a que seja percetível para o para o programador. 53

61 5.2 Implementação de tipos de dados abstratos em Java Por outro lado, dado que o propósito da programação industrial passa também por ter lucros, normalmente é disponibilizado o código compilado estando apenas parte dele visível a quem o utiliza. Esta particularidade tem impacto na forma como estes estruturas são implementadas (bem como nos comentários feitos) e no seguimento far-se-ão alguns comentários relativos ao código propriamente dito tendo em conta esta visão Implementação estática de Listas No que se segue apresenta-se a implementação estática de Filas completando o código descrito ulteriormente. 1 public class Filaest implements Fila { 2 3 private int [] vec ; 4 private int pos, max ; 5 6 public Filaest ( int i){ // construtor nova? ver comentario ulterior 7 vec = new int [i]; 8 max =i; 9 pos =0; 10 } public void insere ( int x) { 13 if(pos < max ){ 14 vec [ pos ]=x; 15 pos ++; 16 } 17 } public int primeiro () { 20 if (pos >0) return vec [0]; 21 return -1; 22 } 23 54

62 Capítulo 5. Tipos de dados abstratos e interfaces em Java 24 public void retira () { // retira ultimo 25 if (pos >0) pos - -; 26 } public boolean vazia () { 29 return pos ==0; 30 } 31 } Por uma questão de completude e para um melhor entendimento do que se pretende do código tecem-se algumas considerações sobre o que foi apresentado: Há variáveis que são declaradas private e variáveis que são declaradas como public (e ainda outras como protected que só as subclasses definidas à custa dela podem aceder e que serão discutidas numa das próximas aulas). As primeiras destinam-se a serem utilizadas internamente pela implementação não estando disponíveis para outras partes de um possível programa que use esta implementação. Apenas o código sob a alçada dado implements pode aceder aos seus valores. As public destinam-se a devolver os resultados das ações. O comentário relativo à construção de uma nova fila deve-se ao facto de o Java ter in built um construtor para cada entidade que usa; As variáveis pos e max não são fundamentais na criação de uma nova fila, contudo são preponderantes para os restantes métodos. Por exemplo, se se quiser inserir um novo elemento, se se souber qual a posição do último elemento da fila torna-se simples acrescentar um novo elemento. No método insere é necessário que após a inserção do novo elemento se atualize a posição do último elemento. No método retira apenas é necessário apontar atualizar a posição do último elemento, que no caso passa a ser o penúltimo elemento da fila. Note-se que em vez de se retirar o primeiro elemento da fila está-se a retirar o último elemento. Deixa-se ao cuidado do leitor implementar o método que retira o primeiro. Deverá, no entanto, ter o cuidado de colocar todos os elementos da fila numa posição anterior. 55

63 5.2 Implementação de tipos de dados abstratos em Java Note-se que no método primeiro é necessário, caso não haja primeiro elemento devolver um resultado que seja entendido como erro para quem programa. Note ainda que no caso de não ter mantido o traço sobre o último elemento o comportamento do método vazia eventualmente não será o desejado Implementação dinâmica de Listas Apresenta-se agora a versão dinâmica da implementação de Filas. A esta implementação denomina-se lista ligada. Ao contrário de outras linguagens de programação como C, Java não é uma linguagem desenvolvida com recurso a apontadores que tenham uma aritmética utilizável. Contudo estes são utilizados implicitamente pelo Java, no entanto, por uma questão de simplicidade, não vamos detalhar como é que o objetos são implementados como ponteiros em Java. A ideia é simples com recurso a nós: sempre que se precisa de um novo elemento, cria-se um novo nó e com a funcionalidade de se manter o traço de se esse elemento aponta para um seguinte ou aponta para o valor nulo. No último caso, esse será o primeiro elemento da fila. A classe que discrimina nós pode ser vista por: 1 class Node { 2 int val ; 3 Node next ; 4 5 Node ( int v){ 6 val =v; 7 next = null ; 8 } 9 } Note-se que a classe Node deve ser entendida como um objeto com dois dados: o valor e um apontador para o nó que se lhe segue na Fila. 56

64 Capítulo 5. Tipos de dados abstratos e interfaces em Java A implementação dinâmica é expressa pelo seguinte código: 1 public class Filadyn implements Fila { 2 3 private Node last ; 4 5 public void insere ( int x) { 6 Node n= new Node (x); 7 n. next = last ; 8 last =n; 9 } public int primeiro () { 12 if ( last == null ) return -1; 13 Node aux = last ; 14 while ( aux. next!= null ) aux = aux. next ; 15 return aux. val ; 16 } public void retira () { 19 if ( last!= null ) last = last. next ; 20 } public boolean vazia () { 23 return last == null ; 24 } } Deixa-se ao cuidado do leitor a interpretação deste código à semelhança do que foi feito para a versão estática da implementação. Na próxima aula analisar-se-á com mais detalhe a implementação quando forem estudadas as noções de herança e polimorfismo. 57

65

66 AULA 6 Noções de Herança e Polimorfismo Herança; Polimorfismo; Exemplos práticos; 59

67 6.1 Herança SECÇÃO 6.1 Herança Dado que Java é uma linguagem em que há a necessidade de se utilizar várias classes torna-se muito útil, em vez de se definirem classes diferentes para designar objetos que têm características comuns, cria-se uma nova classe com todas essas características novas, herdando características de um objeto ou classe já existente. Posto de outra forma, a herança é, na verdade, uma classe derivada de outra classe. Uma particularidade fundamental do Java é permitir que uma classe herde apenas características de uma única classe, isto é, não se pode dar a uma classe, por herança, propriedades que duas ou mais classes diferentes se estas forem classes distintas. Apenas pode herdar propriedades de várias classes se for em cadeias, ou seja, pode herdar de duas desde que uma seja já subclasse da outra. No que se segue explica-se através de exemplos o uso desta valência do Java. Um exmplo ilustrativo é o seguinte: Exemplo 6.1 Consideremos uma classe de objetos designada por Eletrodoméstico onde estão definidos atributos como ligado (boolean), voltagem (int) e consumo (int). etc... Dentro dos eletrodoméstico podemos definir uma classe de computadores, que para além de serem eletrodomésticos têm outros atributos particulares como por exemplo, processador (long), etc. De entre os computadores ainda se pode especificar mais definindo por exemplo a subclasse de portáteis onde se incluem características como peso(long), tamanhoecra(long)... De modo a se demonstrar os conceitos anteriores a funcionar considera-se de novo o exemplo da aula anterior com a implementação em Java de uma classe Lista. Exemplo 6.2 Considere a situação de ter acesso à classe Lista (compilada e não ao código fonte) à qual se quer acrescentar o método comprimento: int comp ( ) e redefinir o método retira que em vez de retirar o primeiro elemento, retira o último. Para tal cria-se a seguinte (sub)classe. 60

68 Capítulo 6. Noções de Herança e Polimorfismo 1 public class Filadext extends Filadyn { 2 3 protected int comp ; 4 5 public Filadext (){ 6 comp =0; 7 } 8 9 public int comp (){ 10 return comp ; 11 } public void insere ( int i){ 14 super. insere (i); 15 comp ++; 16 } public void retira () { 19 if( last!= null && first!= last ) { 20 Node aux = first ; 21 while ( aux. next!= last ) aux = aux. next ; 22 last = aux ; 23 last. next = null ; 24 comp - -; 25 } 26 else if( first!= null && first == last ) { 27 first = null ; 28 last = null ; 29 comp - -; 30 } 31 } 32 } Note-se que é necessário redefinir o método insere uma vez que de cada vez que se insere um novo elemento é necessário incrementar o comprimento da fila. Dado que a única alteração ao código do 61

69 6.2 Polimorfismo método insere é acrescentar a instrução comp++, basta fazer uma chamada da função insere da super classe Fila mas para que o Java saiba qual das funções usar, usa-se o comando super para fazer referência à função da classe original. Como estamos tratando de herança de classes, toda classe tem seu método construtor. Portanto, se estamos trabalhando com duas classes, temos dois métodos construtores. Para se aceder ao método construtor da classe que está sendo herdada usa-se o comando super. Podemos usar o comando super para qualquer construtor da classe pai, pois o Java consegue diferenciar os construtores por causa da sobrecarga de métodos. Note que uma particularidade do Java com a qual deve ter cuidado é que se usar dois métodos de interfaces diferentes mas com o mesmo nome e com tipos diferentes de resultados o compilador dar erro. Polimorfismo SECÇÃO 6.2 Considere-se o seguinte código: 1 public class Main { 2 3 public static void print ( Fila f){ 4 System. out. println (f. primeiro ()); 5 f. retira (); 6 System. out. println (f. primeiro ()); 7 f. retira (); 8 System. out. println (f. primeiro ()); 9 } public static void main ( String [] args ) { 12 Fila f= new Filadext (),g= new Filadyn (); 13 Filadyn h; 14 f. insere (1) ; 15 f. insere (3) ; 62

70 Capítulo 6. Noções de Herança e Polimorfismo 16 f. insere (7) ; 17 System. out. println ((( Filadext ) f). comp ); 18 print (f); 19 } 20 } Note que quando se implementa a função print o compilador não sabe ao certo que função retira deve utilizar dado que depende do tipo de Fila que vai receber. Se for do tipo Filadyn vai usar o método retira da subclasse e se for do tipo Fila usará o método da classe original. Este comportamento é um exemplo claro do que se entende por polimorfismo, ou seja, dependendo do tipo de dados, a função comporta-se de forma diferente. Note-se que o Java usa o método retira o mais abaixo na hierarquia de classes possível. Experimente chamar a função print com a fila h e interprete os resultados. Ao se definir f como do tipo Fila (ainda que depois seja mais especificamente do tipo dinâmico) o Java interpreta que o método comp não está disponível para f. Se se souber que realmente f é do tipo dinâmico pode-se aceder ao método comp, exigindo que f se transforme em fila dinâmica, operação que se designa por cast através do comando ((Filadext) f).comp. 63

71

72 AULA 7 Pesquisa em Tabelas Temas abordados Tipos de dados de Tabela; Problema da pesquisa em tabelas; Analise de complexidade da pesquisa Noção de funções geradoras; Uso de funções geradoras no calculo da complexidade; Parte do conteúdo desta aula e material que e parte integrante das sebentas de Matemática Discreta de P. Mateus e C. Sernadas. Para um entendimento completo das temáticas que aqui se abordam sugere-se, como literatura complementar as referidas notas [?]. 65

73 7.1 Pesquisa em Tabelas SECÇÃO 7.1 Pesquisa em Tabelas Funcionalmente o tipo de dados Tabela e parecido com o tipo de dado Fila que foi visto numa das últimas aulas. Pode-se genericamente ver um tipo de dados Tabela com os seguintes métodos: pesquisa: que dada uma tabela e um inteiro retorna um booleano de acordo com a ocorrência desse inteiro na tabela. retira: que dada uma tabela e um inteiro devolve uma tabela sem o dado inteiro caso ele exista. vazia: que retorna verdadeiro se a tabela for vazia e falso caso contrário. insere: que dada uma tabela e um inteiro devolve uma tabela com o dado inteiro inserido. A titulo de exemplo refere-se a specificação equacional para o pesquisa e retira: pesquisa(nova, X) = f alse pesquisa(insere(w, X), Y) = (X == Y pesquisa(w, Y)) retira(nova, X) = retira retira(insere(w, X), X) = W; retira(insere(w, X), Y) = insere(retira(w, Y)) se X! = Y A interface deste tipo de dados abstrato é descrita em Java pelo seguinte código: 1 public interface Tabela { 2 3 public void insere ( int i); 4 public void retira ( int i); 5 public boolean vazia (); 6 public boolean pesquisa ( int i); 7 } 66

74 Capítulo 7. Pesquisa em Tabelas Implementação em listas ligadas SECÇÃO 7.2 Especifica-se agora a forma de como, em Java, se implementa a classe Tabela como sendo uma extensão da classe fila dinâmica: 1 public class FilaP extends Filadyn implements Tabela { 2 3 public boolean pesquisa ( int i) { 4 Node aux = first ; 5 while ( aux!= null && aux. val!=i) aux = aux. next ; 6 return aux!= null ; 7 } 8 9 public void retira ( int i) { 10 Node aux = first ; 11 if ( aux == null ) return ; 12 if ( aux. val ==i) { 13 retira (); 14 return ; 15 } 16 while ( aux. next!= null && aux. next. val!=i) aux = aux. next ; 17 if(aux. next!= null ) aux. next = aux. next. next ; 18 } 19 } A análise da complexidade de algoritmos de pesquisa é usualmente feita consideram-se os seguintes dois casos: O caso em que a pesquisa é bem sucedida, isto é quando o elemento é encontrado; O caso em que a pesquisa falha, ou seja, o elemento não pertence à lista; Seja n o comprimento da fila. No caso de sucesso, a operação pesquisa demora: 67

75 7.3 Tabelas de dispersão No pior caso: O(n), dado que no pior caso ter-se-á de comparar todos os elementos; ( n ) k ( n ) No caso médio: O = O = O(n), admitindo que a lista está ordenada. Note-se que n 2 k=1 se há sucesso na pesquisa, então são feitas, para cada inteiro i, no máximo i comparações. No melhor caso: O(1), quando o elemento em causa é logo o primeiro elemento da lista. Um majorante para a complexidade no caso de insucesso do método de pesquisa é O(n). (O leitor deve entender o porquê desta afirmação e indagar o porquê da sua veracidade!) Contudo a questão que o leitor pode fazer é se, no caso de não estar na lista o elemento, se se poderá fazer melhor? Outra questão que o leitor deve colocar-se a si mesmo é: Qual a relevância deste problema? Certamente que depois de alguma reflexão o leitor obteve uma resposta para a última questão. Dado que a pesquisa é a operação mais efectuada com listas, o valor O(n), onde n é o número de registo é inaceitável especialmente em listas com milhões de registos. respondida nas secções que se seguem. A resposta à primeira questão será SECÇÃO 7.3 Tabelas de dispersão Uma da técnicas para melhorar a pesquisa de uma tabela de registo é chamada técnica de dispersão aleatória (em Inglês hashing ). A ideia consiste em dividir o espaço de armazenamento de registos em m tabelas e considerar uma função de dispersão h que transforma cada chave possível no índice da tabela a procurar a chave k, ou seja, num valor h(k) {0,..., m 1}. Desta forma o registo com chave k fica guardado na tabela de índice k. Note-se que uma tabela de dispersão tem a mesma assinatura de uma tabela descrita ulteriormente, mas requer uma função de dispersão h. O código em Java de implementação desta classe é: 68

76 Capítulo 7. Pesquisa em Tabelas 1 public class TabelaDisp implements Tabela { 2 FilaP [] disp ; 3 private int hash ( int i){ 4 return i% disp. length ; 5 } 6 TabelaDisp ( int dim ){ 7 disp = new FilaP [ dim ]; 8 int j =0; 9 while (j<dim ) { 10 disp [j]= new FilaP (); 11 j ++; 12 } 13 } public void insere ( int i) { 16 disp [ hash (i)]. insere (i); 17 } public boolean vazia () { 20 int j =0; 21 while (j< disp. length ) { 22 if (! disp [j]. vazia ()) return false ; 23 j ++; 24 } 25 return true ; 26 } public void retira ( int i) { 29 disp [ hash (i)]. retira (i); 30 } public boolean pesquisa ( int i) { 33 return disp [ hash (i)]. pesquisa (i); 34 } 35 } 69

77 7.4 Função geradora Mas a verdadeira questão mantêm-se! O que se ganhou com este processo? No pior caso de insucesso continua-se a ter O(n). Mas como se justifica mais a diante, o ganho é no caso médio. Embora se possa efetuar a análise sem recorrer a funções geradoras, opta-se por introduzir desde já esta técnica com uma ferramenta muito útil para o estudo de complexidade. Função geradora SECÇÃO 7.4 As funções geradoras são um instrumento fundamental em probabilidades. Uma função geradora de uma variável aleatória é o valor esperado de uma certa transformação da variável aleatória da qual se podem caracterizar e inferir momentos da própria variável aleatória num raio de convergência. Sob certas condições, a função geradora de momentos determina completamente a distribuição de probabilidade. No que se segue apresentam-se as definições rigorosas de função geradora de sucessão de números reais e de um uma variável aleatória, destacando algumas das suas propriedades relevantes para o estudos da complexidade supra referido. Definição 7.1 Dada uma sucessão de números reais s = {s n } n N, a função geradora associada a s é definida como G(z) = s i z i. i=0 Tal como se expressou atrás, as funções geradoras têm propriedades úteis. Dessas propriedades destacamse as que se enumeram na proposição seguinte e que se serão úteis no seguimento desta aula. Proposição 7.1 z m G(z) = G(c z) = s i m z i onde s k = 0 para todo k 0; i=0 c i s i z i ; i=0 70

78 Capítulo 7. Pesquisa em Tabelas G (z) = i=0 (i + 1)s i+1 z i ; z 1 G(z)dz = 0 i s i 1z i i=1 Seja X uma variável aleatória que toma valores inteiros não negativos, podemos associar à variável uma sucessão de probabilidades como se segue: ( s X) n = P X(n). Neste contexto, podemos definir a função geradora de probabilidade de (s X ) como G X (z) = P X (k)z k. k=0 A função gerada inclui toda a informação sobre a variável aleatória X. Além disso, a função geradora de uma soma de variáveis aleatórias independentes é o produto das funções geradoras de cada componente da soma, ou seja, temos ainda que G Xi (z) = G Xi (z) se X i é independente de X j para i = j. Os momentos de uma variável aleatória podem ser obtidos pela derivação da função geradora. Proposição 7.2 Dada uma variável aleatória X que toma valores inteiros não negativos, E(X) = G X(1); E(X n ) = k n P X (k); k=0 E(X 2 ) = G X(1) + G X(1); Var(X) = G X(1) + G X(1) ( G X(1) ) 2. Teorema 7.1 Sejam X e Y duas variáveis aleatórias. Tem-se que: G X = P Y (Y = y)g X y. y Y 71

79 7.4 Função geradora As provas destas propriedades podem ser consultadas em qualquer livro sobre probabilidades e estatística e em especial em [?]. A convergência ordinária de uma sequência de funções geradoras corresponde à convergência das correspondentes distribuições Caso de insucesso da pesquisa em tabelas Considere-se a situação de m listas e n chaves. Cada chave pode estar inserida numa das m listas. Logo há m n possibilidades para a inserção das n chaves. Como exemplo ilustrativo, suponha que se têm duas listas L 1 e L 2 e duas chaves K 1 e K 2. As configurações possíveis são: K 1 L 1 e K 2 L 1 ; K 1 L 1 e K 2 L 2 ; K 1 L 2 e K 2 L 1 ; K 1 L 1 e K 2 L 2. Suponha ainda que lhe é dada uma nova chave K 3. Se a chave não se encontrar nas listas, então, dado que K3 pode ocorrem numa das duas listas então para a contagem do número de comparações tem de se considerar todos os 8 casos existentes: K 1, K 2, K 3 L 1 - Neste caso são necessárias duas comparações; K 1, K 2 L 1 e K 3 L 2 - Neste caso não são necessárias comparações; K 1, K 3 L 1 e K 2 L 2 - Neste caso é preciso uma comparação; K 1 L 1 e K 2, K 3 L 2 - Igualmente uma comparação; K 1, K 3 L 2 e K 2 L 1 - Igualmente uma comparação; K 1 L 2 e K 2, K 3 L 1 - Igualmente uma comparação; K 3 L 1 e K 1, K 2 L 2 - Neste caso não são necessárias comparações; K 1, K 2, K 3 L 2 - Este último caso requer duas comparações; 72

80 Capítulo 7. Pesquisa em Tabelas Considere-se as variáveis aleatórias X i independentes com i 1,..., n, tomando valores em {0, 1} definida por X i = 1 se e só se h(k i ) = h(k 3 ). Assuma que todas as listas são igualmente prováveis, ou seja que P(X i = 1) = 1 m. Claramente, o número total de comparações é a variável aleatória NC = n X i. i=1 Pretende-se então calcular o valor médio e a variância deste número de comparações. Para isso calculase a função geradora G i de X i : G i (z) = = 1 P (X i = j) z j j=0 (m 1) m z m z1 Logo a função geradora G de NC é = (m 1 + z) m ( (m 1 + z) G(z) = m ) n e portanto E(NC) = G (1) = n m e também Var(NC) = G (1) + G (1) ( G (1) ) 2 = ( 1 + m)n m Caso de sucesso da pesquisa em tabelas Considere-se novamente a situação de m listas e n chaves. No caso de sucesso, naturalmente a chave a encontrar pode estar entre 1 e n. Tomando o exemplo ilustrativo anterior, suponha-se que temos duas listas L 1 e L 2 e três chaves K 1, K 2 e K 3 e as listas encontram-se na seguinte configuração K 1, K 2 L 1, K 3 L 2. 73

81 7.4 Função geradora O número de comparações necessário para encontrar a chave é: K 1 1 comparação; K 2 2 comparações; K 3 1 comparação. Seja Y a variável aleatória tal que Y = j sse a chave a encontrar é K j. Consideremos as variáveis aleatórias X i independentes com i {1,..., n}, tomando valores em {0, 1}, mais precisamente, tomando valor 1 sse h(k i ) é igual ao valor de h calculado sobre Y-ésima chave. O número de comparações NC j dado que a chave a encontrar é K j é tal que: Observe que P ( X j = 1 Y = j ) = 1. Logo NC j = j X i i=1 G NCj (z) = = j i=1 G i j (z) ( j 1 ) P (X i = k Y = j) z k i=1 k=0 ( j 1 1 ) = ( P (X i = k Y = j) z k ) z i=1 k=0 = = ( j 1 ) G i (z) i=1 ( j 1 i=1 z ) (m 1 + z) z m = ( ) (m 1 + z) j 1 z m 74

82 Capítulo 7. Pesquisa em Tabelas Portanto como P(NC = x) = n P(Y = j)p(nc j = x) e logo j=1 G NC = = = n P(Y = j)g NCj j=1 n G NCj n j=1 ( ( ) ) n (m 1+z) j 1 j=1 m z = m z n ( ( m 1+z ) n ) m 1 n(z 1) e portanto, conclui-se que e que E(NC) = 1 + 2m + n 2m Var(NC) = ( 1 + n)( 5 + 6m + n) 12m 2. Observe que se n = O(m) no caso médio temos que a pesquisa é O(1)! No entanto chama-se a atenção que a eficiência do uso de funções de dispersão está intimamente ligada à escolha da função de dispersão. Escolha da função de dispersão SECÇÃO 7.5 A função de dispersão deve ser tal que h 1 (y) n para todo o y. No entanto observe que mesmo m nesta situação nada impede que seja sempre escolhidos (por algum adversário) registos que vão parar à mesma tabela de dispersão. Para evitar este cenário considera-se que a função de dispersão deve ser escolhida de uma família H dita universal. 75

83 7.5 Escolha da função de dispersão Definição 7.2 Uma coleção de funções de dispersão H é dita universal se para cada par de chaves distintas k 1, k 2, o número de funções de dispersão em h H, tal que h(x) = h(y) é precisamente H m. Teorema 7.2 Se h for escolhido de uma coleção de funções de dispersão universais e h for usada para dispersar n chaves numa tabela de tamanho m com n m, então o valor esperado para o número de colisões envolvendo uma chave k é menor que 1. Demonstração : Seja C k,y a variável aleatória que toma o valor 1 quando h(k) = h(y) para um h escolhido aleatoriamente em H. Então E(C k,y ) = 1 m por definição de universalidade de H. Assim E(C k) = E( C k,y ) = y =k E(C k,y ) = n 1 m. y =k Exemplo 7.3 Coleção universal de funções de dispersão: Assuma que as chaves têm um tamanho máximo e que uma chave pode ser decomposta em r + 1 partes k = (k 0,..., k r ) onde o valor máximo de cada parte é menor que um primo m. Seja a = (a 0,..., a m ) Z r+1 m, isto é, a j Z m = {0,..., m 1}. h a (x) = r a i k i mod m; i=0 H = {h a : a Z r+1 m }. e tem m r+1 elementos. Teorema 7.3 A classe de funções de dispersão H definida no Exemplo 6 é universal. Demonstração : Sejam k = (k 0,..., k r ) e y = (y 0,..., y r ) chaves distintas. Logo existe uma parte j tal que k j = y j. Então, h a (x) = h a (y) se e só se a j (k j y j ) = a i (x i y i ) i =j (mod m). Para cada possível valor dos a i s existe uma única solução para a j (por m ser primo), logo há m r = mr+1 m. 76

84 AULA 8 Árvores binárias de pesquisa Temas abordados Árvores binárias; Pesquisa em árvores binárias; 77

85 8.1 Árvores binárias SECÇÃO 8.1 Árvores binárias Considere que se enriquece o conjunto de operações sobre Java com o tipo árvore binária de inteiros cuja assinatura é: ArvBin: arv, que é uma função construtora da árvore inicialmente vazia. ArvBinDyn:int arv arv arv que dada uma nova raiz, e duas árvores constrói uma nova árvore binária. maior:arv int que dada uma árvore devolve o maior valor dos nós da árvore. maior:arv int que dada uma árvore devolve o menor valor dos nós da árvore. prof:arv int que devolve a profundidade máxima da árvore. pesquisa:int boolean que dado um inteiro pesquisa se ele ocorre na árvore; insere:int arv que insere um inteiro na árvore; retira:int arv que retira um inteiro da árvore caso ele ocorra; vazia:arv boolean que verifica se a árvore é vazia. 1 public interface ArvBin { 2 public ArvBinDyn (); 3 public ArvBinDyn ( int i, ArvBin l, ArvBin r); 4 public int maior (); 5 public int menor (); 6 public int prof (); 7 public void insere ( int i); 8 public void retira ( int i); 9 public boolean pesquisa ( int i); 10 } 78

86 Capítulo 8. Árvores binárias de pesquisa A especificação dos métodos supra referidos é expressa pelo código que se apresenta de seguida. Começase por definir a classe BinTNode que permite criar, partindo de elementos base para criar uma Árvore binária, uma nova Árvore binária com esses elementos. Assim, dado um valor chave e duas árvores binárias constrói-se um novo nó (que no fundo é uma estrutura de árvore binária) que especifica que a raiz dessa estrutura é o valor chave e os ramos que dela derivam são as duas árvores dadas. 1 class BinTNode { 2 ArvBin ltree, rtree ; 3 int key ; 4 BinTNode ( int i, ArvBin l, ArvBin r){ 5 key =i; 6 ltree =l; 7 rtree =r; 8 } 9 } O código que implementa então a interface descrita acima é: 1 public class ArvBinDyn implements ArvBin, Tabela { 2 BinTNode root ; 3 4 public ArvBinDyn (){ 5 root = null ; 6 } 7 8 public ArvBinDyn ( int i, ArvBin l, ArvBin r){ 9 root = new BinTNode (i,l,r); 10 } public int maior () { 13 if( root!= null ){ 14 int rmax = root.key, lmax = root. key ; 15 if( root. rtree!= null ) rmax = root. rtree. maior (); 79

87 8.1 Árvores binárias 16 if( root. ltree!= null ) lmax = root. ltree. maior (); 17 return Math. max ( Math. max (rmax, lmax ),root. key ); 18 } 19 return -1; 20 } public int menor () { 23 if( root!= null ){ 24 int rmin = root.key, lmin = root. key ; 25 if( root. rtree!= null ) rmin = root. rtree. menor (); 26 if( root. ltree!= null ) lmin = root. ltree. menor (); 27 return Math. min ( Math. min (rmin, lmin ),root. key ); 28 } 29 return -1; 30 } public int prof () { 33 if( root!= null ){ 34 int rprof =0, lprof =0; 35 if( root. rtree!= null ) rprof = root. rtree. prof (); 36 if( root. ltree!= null ) lprof = root. ltree. prof (); 37 return 1+ Math. max ( rprof, lprof ); 38 } 39 return 0; 40 } public boolean pesquisa ( int i) { 43 if( root == null ) return false ; 44 if ( root. key ==i) return true ; 45 if( root. rtree!= null && root. rtree. pesquisa (i)) return true ; 46 if( root. ltree!= null && root. ltree. pesquisa (i)) return true ; 47 return false ; 48 } 49 80

88 Capítulo 8. Árvores binárias de pesquisa 50 public void insere ( int i) { 51 if( root == null ) root = new BinTNode (i,null, null ); 52 else if( root. rtree == null ) root. rtree = new ArvBinDyn (i,null, null ); 53 else if( root. ltree == null ) root. ltree = new ArvBinDyn (i,null, null ); 54 else { 55 root = new BinTNode (i,new ArvBinDyn ( root.key, root. ltree, root. rtree ),null ); 56 } 57 } public void retira ( int i) { 60 if(i== root. key ) { 61 if( root. rtree == null ) root =(( ArvBinDyn ) root. ltree ). root ; 62 else if( root. ltree == null ) root =(( ArvBinDyn ) root. rtree ). root ; 63 else { 64 root. key =(( ArvBinDyn ) root. rtree ). root. key ; 65 root. rtree. retira ( root. key ); 66 } 67 } else if( root. rtree!= null && root. rtree. pesquisa (i)) root. rtree. retira (i); 68 else if ( root. ltree!= null && root. ltree. pesquisa (i)) root. ltree. retira (i); 69 } public boolean vazia () { 72 return root == null ; 73 } 74 } 81

89 8.2 Árvores binárias de pesquisa SECÇÃO 8.2 Árvores binárias de pesquisa Um tipo de especial interesse de árvores binárias são as árvores binárias de pesquisa. Este tipo de estruturas tem a particularidade de todos os nós da subárvore esquerda terem um valor numérico inferior ao nó raiz e todos os nós da subárvore direita terem um valor superior ao nó raiz. O objetivo destas árvores é estruturar os dados de forma flexível, permitindo pesquisa binária Figura 8.1: Exemplo de uma árvore de pesquisa binária. O método de verificação se uma dada árvore binária é uma árvore binária de pesquisa pode ser expressa pela implementação do seguinte método pesqq acrescentado à classe ArvBinDyn. 1 public boolean pesqq () { 2 if( root == null ) return true ; 3 if( root. rtree!= null && ( root. rtree. menor () <root. key! root. rtree. pesqq ())) return false ; 4 if( root. ltree!= null && ( root. ltree. maior () >root. key! root. ltree. pesqq ())) return false ; 5 return true ; 6 } 82

90 Capítulo 8. Árvores binárias de pesquisa Note-se que neste tipo de estrutura os seus métodos não podem ser bem iguais aos das árvores binárias em geral. De facto para preservarem a estrutura de árvore binária de pesquisa, por exemplo é necessário que o método insere localize (através de comparações) o melhor sítio para inserir o elemento. Assim, o método insere numa árvore não vazia de um elemento que não ocorre na árvore ocorre colocando, por exemplo o número num novo nó criado numa folha à esquerda ou à direita conforme o valor de referência do nó encontrado. Eventualmente, alcança-se a folha, inserindo-se então o valor nesta posição. Ou seja, a raiz é examinada e introduz-se um nó novo na subárvore da esquerda se o valor novo for menor do que a raiz, ou na subárvore da direita se o valor novo for maior do que a raiz. Da mesma forma, no método retira tem de se ter em linha de conta que retirar apenas pode comprometer a estrutura de árvore binária de pesquisa. No exemplos que se seguem ilustram-se alguns possíveis casos. Exemplo 8.1 (de funcionamento do método retira ) Retirar uma folha: basta removê-lo da árvore. Retirar um nó com um filho: o filho sobe para a posição do pai. Retirar um nó com dois filhos: pode-se fazê-lo de duas formas diferentes. Uma substituindo o valor do nó a ser retirado pelo valor sucessor (o nó mais à esquerda da subárvore direita). A outra através do valor antecessor (o nó mais à direita da subárvore esquerda), removendo-se aí o nó sucessor (ou antecessor). De modo geral tem-se: 83

91 8.2 Árvores binárias de pesquisa Neste exemplo para retirar o nó com valor 30 e dado que possui como sucessor imediato o valor 35 (nó mais à esquerda da sua subárvore direita, este último é promovido ao lugar do nó a ser excluído, enquanto a sua subárvore (direita) será promovida para sub-árvore esquerda do 40. Teorema 8.1 Seja T uma árvore binária de pesquisa com profundidade h. Pesquisar um elemento, encontrar o máximo e o mínimo em T pode ser feito em O(h). Demonstração : Considere o seguinte algoritmo de pesquisa 1 public class ArvBinPesq extends ArvBinDyn implements Tabela { 2 3 public ArvBinPesq ( int i, ArvBinPesq l, ArvBinPesq r) { 4 5 } 84

92 Capítulo 8. Árvores binárias de pesquisa 6 7 public void insere ( int i) { 8 if( root == null ) { 9 root = new BinTNode (i,null, null ); 10 } else { 11 if(i== root. key ) return ; 12 if(i> root. key ) { 13 if( root. rtree!= null ) root. rtree. insere (i); 14 else root. rtree = new ArvBinPesq (i,null, null ); 15 } else { 16 if( root. ltree!= null ) root. ltree. insere (i) ; 17 else root. ltree = new ArvBinPesq (i,null, null ); 18 } 19 } 20 } public void retira ( int i) { 23 if ( root == null ) return ; 24 if ( root. key ==i) { 25 if( root. ltree == null && root. rtree == null ) root = null ; 26 else if ( root. ltree!= null ) { 27 root. key = root. ltree. maior (); 28 (( ArvBinPesq ) root. ltree ). retira ( root. key ); 29 } else { 30 root. key = root. rtree. menor (); 31 (( ArvBinPesq ) root. rtree ). retira ( root. key ); 32 } 33 } else if( root.key <i) root. rtree. retira (i); 34 else root. ltree. retira (i); 35 } 36 85

93 8.2 Árvores binárias de pesquisa 37 public boolean pesquisa ( int i) { 38 if( root == null ) return false ; 39 if( root. key ==i) return true ; 40 if( root.key <i) return root. rtree. pesquisa (i); 41 return root. ltree. pesquisa (i); 42 } public int maior () { 45 if( root == null ) return -1; 46 if( root. rtree == null ) return root. key ; 47 return root. rtree. maior (); 48 } public int menor () { 51 if( root == null ) return -1; 52 if( root. ltree == null ) return root. key ; 53 return root. ltree. menor (); 54 } 55 } O algoritmo está correto. Deixa-se ao cuidado do leitor encontrar e provar um invariante. Note-se que como em cada passo do ciclo a árvore é alterada para um descendente, o número máximo de iteradas que é necessário para completar o ciclo é O(h). As árvores de pesquisa podem implementar uma tabela de registos com as operações de insere! Deixa-se novamente ao cuidado do leitor encontrar do ciclo da função insere! Deixa-se ainda ao cuidado do leitor calcular a complexidade da operação de insere e repetir estas mesmas questões para o operação de apaga referida nas especificações da função. Teorema 8.2 A profundidade média de uma árvore de pesquisa cujos os elementos k 1,... k n foram inseridos por um ordem aleatória é O(log(n)). 86

94 AULA 9 B-trees Temas abordados Complexidade da pesquisa em árvores binárias; B - trees; 87

95 9.1 Complexidade da inserção aleatória em árvores binárias de pesquisa SECÇÃO 9.1 Complexidade da inserção aleatória em árvores binárias de pesquisa Recorde o conceito de árvore binária de pesquisa dado na aula anterior e relembre o enunciado do seguinte teorema que ficou por demonstrar. Para um estudo mais detalhado do tema abordado nesta secção e na secção anterior, sugere-se a leitura do Capítulo 12 do livro de texto [CSRL01]. Teorema 9.1 A profundidade média de um nó numa árvore de pesquisa cujos os elementos k 1,... k n foram inseridos por um ordem aleatória é O(log(n)). Demonstração : Seja d(x, T) a profundidade de cada nó x numa árvore binária e assuma também que a raiz tem profundidade zero. Seja P(T) = d(x, T) a soma das profundidades de todos os nós. x Fixada a árvore T a profundidade média de um nó, escolhido de forma uniforme, é E[d(x, T)] = x 1 P(T) d(x, T) = n n. Se T l e T r forem respetivamente as sub-árvores esquerda e direita é fácil inferir a seguinte recorrência: P(T) = P(T l ) + P(T r ) + n 1. Note-se que o peso da árvore é dado pelo peso das duas árvores que a compõe e a todos os nós de T l e T r (que são n 1, todos exceto a raiz) acrescem uma unidade na profundidade em T. Seja P(n) = E[P(T)] e T = n o número de nós em T. Se os elementos k 1,... k n forem inseridos de forma aleatória em T l = i e T r = n 1 i com probabilidade 1 para todo o i = 0...n 1 então n P(n) = 1 n = 2 n ( ) n 1 P(i) + P(n i 1) + n 1 i=0 ( ) n 1 P(i) + Θ(n) i=1 Esta recorrência é semelhante à recorrência que já resolvemos para o quicksort e a solução é P(n) = O(n log n) e portanto, podemos concluir que E[d(x, T)] = P(n) n = O(log n). 88

96 Capítulo 9. B-trees Apesar da inserção ser feita em tempo logarítmico existem alguns detalhes que inviabilizam este resultado. m muitos casos os dados não são inseridos aleatoriamente, isto é, são inseridos com um certo enviesamento. Na verdade, em situações práticas de utilização destas estruturas tipicamente os dados são inseridos quase de forma ordenadas (novas contas, novos números de ID etc). Assim sendo, nestes casos as árvores não ficam com nós com profundidade média O(log(n)) como seria de esperar. Colocase então a questão pertinente ao leitor: Como se pode resolver estes problemas de modo a obter uma estrutura em que as operações descritas na última aula posso ser realizadas em tempo logarítmico? A resposta é o uso de B-trees. B - trees SECÇÃO 9.2 As B - tree são uma estrutura de dados muito utilizada em aplicações que lidam e manipulam grandes quantidades de informação dado que que estas estruturas possibilitam que a inserção, remoção e pesquisa de chaves seja feita em tempo logarítmico no tamanho dos dados. A sua invenção remonta a 1971 e foi apresentada por Rudolf Bayer e Edward McCreight investigadores, à data, da Boeing Scientific Research Labs. Pensa-se que a origem do nome B-tree ainda que possa não ter sido proposto pelos seus criadores, tenha a haver com balanceamento, ou com o nome de um de seus inventores Bayer ou então com a própria Boeing. As B-trees são uma generalização das árvores binária de pesquisa, pois cada nó de uma árvore binária armazena uma única chave, enquanto as B-tree armazenam mais que uma chaves de pesquisa em cada nó. Começa-se por definir rigorosamente o que se entende por uma 2 3tree. Definição 9.1 Uma 2-3 tree é uma árvore balanceada em que: Todos os nós têm sub-árvores exatamente com o mesma profundidade; Todos os nós são de uma das seguintes formas: 2-nós: contém uma chave e dois descendentes; 3-nós: contém duas chaves e três descendentes; Os elementos da árvore estão ordenados de modo a que: 89

97 9.2 B - trees Num 2-nó, os elementos à esquerda são todos menores que a chave e à direita maiores que a chave; Num 3-nó, os elementos à esquerda são todos menores que a primeira chave, os elementos ao centro estão entre a primeira e a segunda chave e à direita são maiores que a segunda chave; Na figura que se segue apresenta-se um exemplo de uma 2-3 tree. A pesquisa é feita de maneira mais ou menos trivial, dependendo do nó ser do tipo 2 ou 3. Deixa-se ao cuidado do leitor os detalhes da pesquisa. Sugere-se que o leitor implemente a pesquisa usando Java. No que se segue explica-se o processo de inserção de uma chave x não existente na árvore. O processo de inserção funciona da seguinte forma: 90

98 in 2-3 Trees m with a key k: 1. Pesquisar a folha n onde x deveria estar; until you hit a leaf node e end of the search 2. Se a folha é um 2-nó transforma-se num 3-nó com x; Capítulo 9. B-trees 3-node s containing the items with the of: k, L s 1 st key, L s 2 nd key t up and inserted in L s parent Figura 9.1: Inserção de 14 num 2-nó 3. Se a folha é um 3-nó então separa-se em dois 2-nós com o menor e o maior valor e o valor do meio é enviado para cima Figura 9.2: Inserção de 52 num 3-nó Eventualmente se o pai é um 3-nó, este também tem de ser separado, podendo esta propagação ser feita até à raiz: 91

99 9.2 B - trees 92

100 Capítulo 9. B-trees Figura 9.3: Inserção de 92 num 3-nó Este é o único caso que leva ao aumento da profundidade da árvore. Deixa-se ao cuidado do leitor verificar com detalhe que a árvore obtida ainda é da forma pretendida, isto é, é uma 2-3 tree. 93

101 9.2 B - trees Afirmam-se, sem prova algumas das propriedades das 2 3 árvores importantes: Uma 2-3 árvore com n chaves tem profundidade log 2 (n); A pesquisa e a inserção são feitas em tempo O(log n); (O leitor deve consultar a Secção 18.2 de [CSRL01] para entender os detalhes da implementação e da justificação da complexidade destas operações.) Apagar é complicado mas também é O(log n). Este procedimento é explicado na Secção 18.3 de [CSRL01]. Não funciona bem em HD: disco estão organizados em blocos 4K 8K e os acessos aos discos são muito lentos... Pode-se generalizar a ideia de uma 2-3 tree para uma ordem superior, em que se assume que os nós podem ter um número arbitrário de chaves. A definição geral deste tipo de estruturas é: Definição 9.2 Uma B-tree de ordem m é uma árvore balanceada em que: Cada nó tem no máximo 2m chaves (e 2m + 1 descendentes); Cada nó tem no mínimo m chaves com exceção da raiz que pode ter apenas 1 chave; Os elementos da árvore estão ordenados de modo a que: Os elementos da sub-árvore mais à esquerda são todos menores que a primeira chave e os elementos da sub-árvore mais à direita são maiores que a última chave; Os elementos da i-ésima sub-árvore a contar da esquerda tem todos os elementos entre a i 1-ésima e a i-ésima chave; Apresenta-se na figura seguinte uma B-Tree de ordem 2 94

102 Capítulo 9. B-trees Example: a B-Tree of Order Order 2: at most 4 data items per node (and at most 5 children) É oportuno observar que: The above tree holds the same keys as one of our earlier 2-3 Uma trees, 2-3 which é uma B-tree is deshown ordem 1; again below: Ao escolher-se m o maior possível garante-se a minimização de acessos ao disco Para um estudo mais completo e detalhado do tema abordado nesta secção sugere-se a leitura do Capítulo 18 do livro de texto recomendado como bibliografia principal [CSRL01]. Recomenda-se ainda ao leitor que implemente em Java a estrutura de uma 2-3 tree We used the same order of insertion to create both trees: 51, 3, 40, 77, 20, 10, 34, 28, 61, 80, 68, 93, 90, 97, 87, 14 For extra practice, see if you can reproduce the trees! Similar to search in a 2-3 tree. Search in B-Trees Example: search for 87 95

103

104 AULA 10 Algoritmos em grafos Temas abordados Noção de grafos; Implementação de grafos; Problema de acessibilidade; Pesquisa em largura; Árvore de Extensão Mínima; Planaridade de grafos; 97

105 10.1 Grafos SECÇÃO 10.1 Grafos Definição 10.1 Um grafo é um par G = (N, E) onde N é um conjunto de nós e E é um conjunto de subconjuntos de N com dois elementos, dito o conjunto de arestas. Um caminho é uma sequência de arestas e 1... e k tais que e i e i+1 =. A noção de grafo orientado é semlhante à noção de grafo em que se acrescenta à noção de aresta uma orientação. Exercício Considere as operações novo:int->grafo que recebe um número de nós e constrói um grafo com esse número de nós e sem arestas; adar:grafo int int->grafo que adiciona uma aresta a um grafo. Especifique estas operações equacionalmente. No restante desta aula apresentam-se implementações do seguinte interface: 1 public interface GrafoO { 2 public void addaresta ( int i, int j); 3 public void retiraaresta ( int i, int j); 4 public boolean adjacenteq ( int i, int j); 5 public boolean acessivel ( int i, int j); 6 public boolean conexo (); 7 public boolean planar (); 8 } Há duas representação naturais de grafos em memória: Matrizes de adjacência que requer O( N 2 ) espaço; Listas de adjacência que requer O( N + E ) espaço. 98

106 Capítulo 10. Algoritmos em grafos A implementação de grafos como matriz de adjacência é descrita pelo seguinte código Java: 1 public class GOMA implements GrafoO { 2 int [][] MA; 3 4 public GOMA ( int dim ){ 5 MA=new int [ dim ][ dim ]; 6 int i=0,j; 7 while (i<dim ) { 8 j =0; 9 while (j<dim ){ 10 MA[i][j ]=0; 11 j ++; 12 } 13 i ++; 14 } 15 } public void addaresta ( int i, int j) { 18 MA[i][j ]=1; 19 } public void retiraaresta ( int i, int j) { 22 MA[i][j ]=0; 23 } public boolean adjacenteq ( int i, int j) { 26 return MA[i][j ]==1; 27 } public boolean conexo () { 30 return false ; 31 } public boolean planar () { 34 return false ; 99

107 10.1 Grafos 35 } public static void main ( String [] args ) { 38 GOMA g= new GOMA (3) ; 39 g. addaresta (1,0) ; 40 g. addaresta (0,1) ; 41 g. addaresta (2,1) ; 42 System. out. println (g. acessivel (0,2) ); 43 } 44 } Ao longo da aula completar-se-ão os métdos em falta. Como lista de adjacência um grafo pode ser descrito baseado na estrutura de filas já idealizada numa das aulas anteriores através do seguinte código: 1 public class GOLA implements GrafoO { 2 FilaP LA []; 3 4 public GOLA ( int dim ){ 5 LA=new FilaP [ dim ]; 6 int i =0; 7 while (i<dim ) { 8 LA[i]= new FilaP (); 9 i ++; 10 } 11 } public void addaresta ( int i, int j) { 14 LA[i]. insere (j); 15 } public void retiraaresta ( int i, int j) { 18 LA[i]. retira (j); 100

108 Capítulo 10. Algoritmos em grafos 19 } public boolean adjacenteq ( int i, int j) { 22 return LA[i]. pesquisa (j); 23 } public boolean acessivel ( int i, int j) { 26 return false ; 27 } public boolean conexo () { 30 return false ; 31 } public boolean planar () { 34 return false ; 35 } 36 } SECÇÃO 10.2 Problema da acessibilidade O problema de acessibilidade em grafos é descrito através da seguinte questão: Dados dois nós, existe um caminho que os contenha? Os seguintes teoremas de demonstração fácil (e que se deixa ao cuidado do leitor) ajudam a idealizar uma algoritmo para tratar este problema. Teorema 10.1 Caso exista um caminho de um nó para outro, então existe um caminho entre este nós com dimensão menor ou igual a N

109 10.2 Problema da acessibilidade Teorema 10.2 Seja A a matriz de adjacência de um grafo e B = A n com B = {b ij } i,j 1..n. O número de caminhos com comprimento n de i para j é precisamente igual a b ij. Exercício Especifique equacionalmente a função acessivelq:int int:->bool. Utilizando os resultados anteriores e a implementação do tipo grafo em matrizes de adjacência apresentase uma resolução do problema da acessibilidade. 1 private static int proxavisitar ( int [] v){ 2 int i=v. length -1; 3 while (i >=0 && v[i ]!=1) { 4 i - -; 5 } 6 return i; 7 } 8 9 public boolean acessivel ( int i, int j) { 10 int aux,next,v []= new int [MA. length ]; 11 v[i ]=1; 12 next = proxavisitar (v); 13 while ( next!= -1) { 14 if( next ==j) return true ; 15 v[ next ]=2; 16 aux =0; 17 while (aux <v. length ) { 18 if( adjacenteq (next, aux ) && v[ aux ]==0) v[ aux ]=1; 19 aux ++; 20 } 21 next = proxavisitar (v); 22 } 23 return false ; 24 } Exercício Indique a complexidade computacional da solução apresentada. 102

110 Capítulo 10. Algoritmos em grafos Exercício Implemente o tipo de dados grafo usando listas de adjacência, incluindo a operação de acessivelq. Obtenha uma implementação que seja O( N ). Busca em Largura SECÇÃO 10.3 Em teoria dos grafos, a busca em largura também conhecida por busca em amplitude e que deriva do Inglês Breadth-First Search (BFS) é um algoritmo de procura em grafos utilizado para realizar uma busca ou travessia num grafo e estrutura de dados do tipo árvore. O objectivo da busca em largura é pesquisar primeiro os nós que se encontram mais próximos do nó inicial ou seja, começando pelo vértice raiz, explorar todos os vértices vizinhos só depois passar para cada um desses vértices mais próximos, exploramos os seus vértices vizinhos ainda não explorados e assim sucessivamente até que o elemento procurado seja encontrado. Para tal utiliza-se uma fila para guardar o nós que já foram pesquisados. No exemplo seguinte mostrase intuitivamente a ordem pela qual a busca em largura procura os elementos numa árvore (um tipo particular de grafos!). Figura 10.1: Exemplo de ordem de procura do algoritmo de busca em largura 103

111 10.3 Busca em Largura O algoritmo de busca em largura necessita da seguinte classe FilaP: 1 Class FilaP { 2 boolean vaziaq (); 3 int primeiro (); 4 void retira () /* retira sempre o primeiro elemento */ 5 FilaP copia (); 6 } cuja implementação pode ser realizada por: 1 public class Filadyn implements Filap { 2 protected Node first, last ; 3 4 public filadyn (){ 5 first = null ; 6 last = null ; 7 } 8 9 public void insere ( int i) { /* insere no fim da lista */ 10 Node aux = new Node (); 11 aux. val =i; 12 aux. next = null ; 13 if( last!= null ){ 14 last. next = aux ; 15 last = aux ; 16 } else { 17 last = aux ; 18 first = aux ; 19 } 20 } public boolean vazia () { 23 return first == null ; 104

112 Capítulo 10. Algoritmos em grafos 24 } public void retira () { /* retira o primeiro da lista */ 27 Node aux = first ; 28 if(aux == null ) return ; 29 if(aux. next == null ) { 30 first = null ; 31 last = null ; 32 } 33 else { 34 aux = aux. next ; 35 first = aux ; 36 } 37 } public int ultimo () { 40 if( last!= null ) return last. val ; 41 return -1; 42 } public int primeiro () { 45 if( first!= null ) return first. val ; 46 return -1; 47 } public FilaP copia (){ 50 FilaP n= new FilaP (); 51 Node aux = first ; 52 while ( aux!= null ){ 53 n. insere ( aux. val ); 54 aux = aux. next ; 55 } 56 return n; 57 } 58 } 105

113 10.3 Busca em Largura A implementação da busca em largura é dada pelo seguinte método: 1 int [] BSF ( int v){ 2 int numnos =LA. length ; 3 int [] order = new int [ numnos ]; 4 int [] visitados = new int [ numnos ]; 5 // 0 nao visitados, 1 colocados na lista w, 2 ja visitados 6 FilaP w = new FilaP ; /* lista que guarda os nos a tratar */ 7 w. add (v); 8 FilaP ladj ; /* lista de nos ( inteiros ) auxiliar */ 9 int i=0, cur_ord =0; nextno ; 10 while (i< numnos ) { visitados [i ]=0; i ++;} 11 order [v]= cur_ord ; 12 while (!w. vaziaq ()) { 13 nextno =w. primeiro (); 14 visitados [ nextno ] =2; 15 ladj = adjacentes ( nextno ); 16 cur_ ord ++; 17 order [ nextno ]= cur_ord ; 18 while (! ladj. vaziaq ()) { 19 if( visitados [ ladj. primeiro () ]==0) { 20 w. add ( ladj. primeiro ()); 21 visitados [ ladj. primeiro () ]=1; 22 } 23 ladj. retira (); 24 } 25 w. retira (); 26 } 27 return order ; 28 } Deixa-se ao cuidado do leitor verificar que o algoritmo está correto determinando os invariantes da pesquisa em largura? Exercício Mostre que complexidade deste algoritmo é O( E ) em E é o número de arestas. 106

114 Capítulo 10. Algoritmos em grafos Em vez de se fazer uma busca em largura podemos implementar uma pesquisa em profundidade, nome que deriva do Inglês Deep First Search. A diferença é que a pesquisa é feita de explorando primeiro todos os descendentes de um nó e só quando não houver mais folhas explorar outro ramo. Para implementar a pesquise em profundidade basta: 1. substituir a fila w por uma pilha; 2. substituir a chamada de primeiro da fila para ultmio; 3. alterar o ciclo interior e o cur ord para que se coloque apenas um filho na pilha. O exemplo que se segue mostra a ordem pela qual os elementos de um grafo são marcados com a pesquisa em profundidade. Figura 10.2: Exemplo de ordem de procura do algoritmo de pesquisa em profundidade 107

Algoritmos e Modelação Computacional. Paulo Mateus MEBiom LMAC 2018

Algoritmos e Modelação Computacional. Paulo Mateus MEBiom LMAC 2018 Algoritmos e Modelação Computacional Paulo Mateus MEBiom LMAC 2018 Objetivos Edição e compilação de programas Tipos e expressões Declaração de variáveis Atribuição, composição sequencial, iterativa e alternativa

Leia mais

Algoritmos I Aula 13 Linguagem de Programação Java

Algoritmos I Aula 13 Linguagem de Programação Java Algoritmos I Aula 13 Linguagem de Programação Java Professor: Max Pereira http://paginas.unisul.br/max.pereira Ciência da Computação IDE Eclipse IDE (Integrated development environment) Criar um projeto

Leia mais

Análise de Algoritmos Estrutura de Dados II

Análise de Algoritmos Estrutura de Dados II Centro de Ciências Exatas, Naturais e de Saúde Departamento de Computação Análise de Algoritmos Estrutura de Dados II COM10078 - Estrutura de Dados II Prof. Marcelo Otone Aguiar marcelo.aguiar@ufes.br

Leia mais

Enunciados das aulas práticas de. Algoritmos e Modelação Matemática

Enunciados das aulas práticas de. Algoritmos e Modelação Matemática Enunciados das aulas práticas de Algoritmos e Modelação Matemática Paulo Mateus André Souto 2013 ii À guardiã Precaução dirigida aos alunos Os temas apresentados neste trabalho são uma versão muito preliminar

Leia mais

5. Análise de Complexidade de Algoritmos. João Pascoal Faria (versão original) Ana Paula Rocha (versão 2003/2004) Luís Paulo Reis (versão 2005/2006)

5. Análise de Complexidade de Algoritmos. João Pascoal Faria (versão original) Ana Paula Rocha (versão 2003/2004) Luís Paulo Reis (versão 2005/2006) 5. Análise de Complexidade de Algoritmos João Pascoal Faria (versão original) Ana Paula Rocha (versão 2003/2004) Luís Paulo Reis (versão 2005/2006) FEUP - MIEEC Prog 2-2006/2007 Introdução Algoritmo: conjunto

Leia mais

ALGORITMOS E ESTRUTURAS DE DADOS 2011/2012 ANÁLISE DE ALGORITMOS. Armanda Rodrigues 3 de Outubro 2011

ALGORITMOS E ESTRUTURAS DE DADOS 2011/2012 ANÁLISE DE ALGORITMOS. Armanda Rodrigues 3 de Outubro 2011 ALGORITMOS E ESTRUTURAS DE DADOS 2011/2012 ANÁLISE DE ALGORITMOS Armanda Rodrigues 3 de Outubro 2011 2 Análise de Algoritmos Temos até agora analisado soluções de problemas de forma intuitiva A análise

Leia mais

Técnicas de projeto de algoritmos: Indução

Técnicas de projeto de algoritmos: Indução Técnicas de projeto de algoritmos: Indução ACH2002 - Introdução à Ciência da Computação II Delano M. Beder Escola de Artes, Ciências e Humanidades (EACH) Universidade de São Paulo dbeder@usp.br 08/2008

Leia mais

Projeto e Análise de Algoritmos

Projeto e Análise de Algoritmos Projeto e Análise de Algoritmos Aula 01 Complexidade de Algoritmos Edirlei Soares de Lima O que é um algoritmo? Um conjunto de instruções executáveis para resolver um problema (são

Leia mais

AED2 - Aula 11 Problema da separação e quicksort

AED2 - Aula 11 Problema da separação e quicksort AED2 - Aula 11 Problema da separação e quicksort Projeto de algoritmos por divisão e conquista Dividir: o problema é dividido em subproblemas menores do mesmo tipo. Conquistar: os subproblemas são resolvidos

Leia mais

Elementos de Análise Assintótica

Elementos de Análise Assintótica Elementos de Análise Assintótica Marcelo Keese Albertini Faculdade de Computação Universidade Federal de Uberlândia 23 de Março de 2018 Aula de hoje Nesta aula veremos: Elementos de Análise Assintótica

Leia mais

1 a Lista de Exercícios

1 a Lista de Exercícios Universidade Federal de Ouro Preto Instituto de Ciências Exatas e Biológicas Programa de Pós-Graduação em Ciência da Computação Projeto e Análise de Algoritmos - 1 o semestre de 2010 Professor: David Menotti

Leia mais

Lista 1 - PMR2300. Fabio G. Cozman 3 de abril de 2013

Lista 1 - PMR2300. Fabio G. Cozman 3 de abril de 2013 Lista 1 - PMR2300 Fabio G. Cozman 3 de abril de 2013 1. Qual String é impressa pelo programa: p u b l i c c l a s s What { p u b l i c s t a t i c void f ( i n t x ) { x = 2 ; p u b l i c s t a t i c void

Leia mais

Medida do Tempo de Execução de um Programa. David Menotti Algoritmos e Estruturas de Dados II DInf UFPR

Medida do Tempo de Execução de um Programa. David Menotti Algoritmos e Estruturas de Dados II DInf UFPR Medida do Tempo de Execução de um Programa David Menotti Algoritmos e Estruturas de Dados II DInf UFPR Classes de Comportamento Assintótico Se f é uma função de complexidade para um algoritmo F, então

Leia mais

Complexidade de Algoritmos

Complexidade de Algoritmos Complexidade de Algoritmos O que é um algoritmo? Sequência bem definida e finita de cálculos que, para um dado valor de entrada, retorna uma saída desejada/esperada. Na computação: Uma descrição de como

Leia mais

Análise e Projeto de Algoritmos

Análise e Projeto de Algoritmos Análise e Projeto de Algoritmos Mestrado em Ciência da Computação Prof. Dr. Aparecido Nilceu Marana Faculdade de Ciências I think the design of efficient algorithms is somehow the core of computer science.

Leia mais

Algoritmos de Ordenação: QuickSort

Algoritmos de Ordenação: QuickSort Algoritmos de Ordenação: QuickSort ACH2002 - Introdução à Ciência da Computação II Delano M. Beder Escola de Artes, Ciências e Humanidades (EACH) Universidade de São Paulo dbeder@usp.br 10/2008 Material

Leia mais

Análise de algoritmos. Parte I

Análise de algoritmos. Parte I Análise de algoritmos Parte I 1 Recursos usados por um algoritmo Uma vez que um procedimento está pronto/disponível, é importante determinar os recursos necessários para sua execução Tempo Memória Qual

Leia mais

Variáveis primitivas e Controle de fluxo

Variáveis primitivas e Controle de fluxo Variáveis primitivas e Controle de fluxo Material baseado na apostila FJ-11: Java e Orientação a Objetos do curso Caelum, Ensino e Inovação, disponível para download em http://www.caelum.com.br/apostilas/

Leia mais

CT-234. Estruturas de Dados, Análise de Algoritmos e Complexidade Estrutural. Carlos Alberto Alonso Sanches

CT-234. Estruturas de Dados, Análise de Algoritmos e Complexidade Estrutural. Carlos Alberto Alonso Sanches CT-234 Estruturas de Dados, Análise de Algoritmos e Complexidade Estrutural Carlos Alberto Alonso Sanches Bibliografia T.H. Cormen, C.E. Leiserson and R.L. Rivest Introduction to algorithms R. Sedgewick

Leia mais

Análise de programas imperativos

Análise de programas imperativos Análise de programas imperativos AMC 2011/12 ì Paulo Mateus Departamento de Matemática IST 2012 Objectivos ì Noção de invariante e variante de um ciclo ì Prova (informal) da correção de algoritmos imperativos

Leia mais

ANÁLISE DE ALGORITMOS: PARTE 3

ANÁLISE DE ALGORITMOS: PARTE 3 ANÁLISE DE ALGORITMOS: PARTE 3 Prof. André Backes 2 A notação grande-o é a forma mais conhecida e utilizada de análise Complexidade do nosso algoritmo no pior caso Seja de tempo ou de espaço É o caso mais

Leia mais

Técnicas de análise de algoritmos

Técnicas de análise de algoritmos CENTRO FEDERAL DE EDUCAÇÃO TECNOLÓGICA DE MINAS GERAIS Técnicas de análise de algoritmos Algoritmos e Estruturas de Dados I Natália Batista https://sites.google.com/site/nataliacefetmg/ nataliabatista@decom.cefetmg.br

Leia mais

Aula 1. Teoria da Computação III

Aula 1. Teoria da Computação III Aula 1 Teoria da Computação III Complexidade de Algoritmos Um problema pode ser resolvido através de diversos algoritmos; O fato de um algoritmo resolver um dado problema não significa que seja aceitável

Leia mais

Grupo 2 - Implementação de uma Classe Simples

Grupo 2 - Implementação de uma Classe Simples Estruturas de Dados 2017/2018 Época Normal (15 de Junho de 2018) Versão A Duração: 2h30m + 30m Número mecanográco: Nome completo do estudante: Grupo 1 - Fundamentos de Java 1.1. Escreva pequenos excertos

Leia mais

Análise de algoritmos

Análise de algoritmos Análise de algoritmos SCE-181 Introdução à Ciência da Computação II Alneu Lopes Thiago A. S. Pardo 1 Algoritmo Noção geral: conjunto de instruções que devem ser seguidas para solucionar um determinado

Leia mais

Algoritmo. Exemplo. Definição. Programação de Computadores Comparando Algoritmos. Alan de Freitas

Algoritmo. Exemplo. Definição. Programação de Computadores Comparando Algoritmos. Alan de Freitas Algoritmos Programação de Computadores Comparando Algoritmos Um algoritmo é um procedimento de passos para cálculos. Este procedimento é composto de instruções que definem uma função Até o momento, vimos

Leia mais

Projeto e Análise de Algoritmos

Projeto e Análise de Algoritmos Projeto e Algoritmos Pontifícia Universidade Católica de Minas Gerais harison@pucpcaldas.br 26 de Maio de 2017 Sumário A complexidade no desempenho de Quando utilizamos uma máquina boa, ela tende a ter

Leia mais

Lista 2 - PMR2300/3200

Lista 2 - PMR2300/3200 Lista 2 - PMR2300/3200 Fabio G. Cozman, Thiago Martins 8 de março de 2015 1. Qual String é impressa pelo programa: p u b l i c c l a s s What { p u b l i c s t a t i c void f ( i n t x ) { x = 2 ; p u

Leia mais

PROGRAMAÇÃO ESTRUTURADA E ORIENTADA A OBJETOS

PROGRAMAÇÃO ESTRUTURADA E ORIENTADA A OBJETOS INSTITUTO FEDERAL DE EDUCAÇÃO, CIÊNCIA E TECNOLOGIA DO RIO GRANDE DO NORTE PROGRAMAÇÃO ESTRUTURADA E ORIENTADA A OBJETOS Docente: Éberton da Silva Marinho e-mail: ebertonsm@gmail.com eberton.marinho@gmail.com

Leia mais

Algoritmos e Estrutura de Dados. Algoritmos Prof. Tiago A. E. Ferreira

Algoritmos e Estrutura de Dados. Algoritmos Prof. Tiago A. E. Ferreira Algoritmos e Estrutura de Dados Aula 3 Conceitos Básicos de Algoritmos Prof. Tiago A. E. Ferreira Definição de Algoritmo Informalmente... Um Algoritmo é qualquer procedimento computacional bem definido

Leia mais

Conceitos básicos de programação

Conceitos básicos de programação Constantes class Exemplo { static void Main() { float fahr, celsius; int lower, upper, step; lower = 0; /* limite inferior da tabela de temperaturas */ upper = 300; /* limite superior */ step = 20; /*

Leia mais

Paradigmas de Programação. Java First-Tier: Aplicações. Orientação a Objetos em Java (I) Nomenclatura. Paradigma OO. Nomenclatura

Paradigmas de Programação. Java First-Tier: Aplicações. Orientação a Objetos em Java (I) Nomenclatura. Paradigma OO. Nomenclatura Java First-Tier: Aplicações Orientação a Objetos em Java (I) Paradigmas de Programação Programação Funcional Programação Procedural Programação Orientada por Objetos Grupo de Linguagens de Programação

Leia mais

Introdução à Programação em C. Prof. Ricardo Teixeira Tecnologia em Mecatrônica Industrial SENAI

Introdução à Programação em C. Prof. Ricardo Teixeira Tecnologia em Mecatrônica Industrial SENAI Introdução à Programação em C Prof. Ricardo Teixeira Tecnologia em Mecatrônica Industrial SENAI Linguagem C Criada em 1972 para uso no LINUX; Sintaxe base para diversas outras (Java, JavaScript, PHP, C++,

Leia mais

Análise de complexidade

Análise de complexidade Introdução Algoritmo: sequência de instruções necessárias para a resolução de um problema bem formulado (passíveis de implementação em computador) Estratégia: especificar (definir propriedades) arquitectura

Leia mais

É interessante comparar algoritmos para valores grandes de n. Para valores pequenos de n, mesmo um algoritmo ineficiente não custa muito para ser

É interessante comparar algoritmos para valores grandes de n. Para valores pequenos de n, mesmo um algoritmo ineficiente não custa muito para ser É interessante comparar algoritmos para valores grandes de n. Para valores pequenos de n, mesmo um algoritmo ineficiente não custa muito para ser executado 1 Fazendo estimativas e simplificações... O número

Leia mais

INF 1010 Estruturas de Dados Avançadas

INF 1010 Estruturas de Dados Avançadas INF 1010 Estruturas de Dados Avançadas Complexidade de Algoritmos 2012 DI, PUC-Rio Estruturas de Dados Avançadas 2012.2 1 Introdução Complexidade computacional Termo criado por Hartmanis e Stearns (1965)

Leia mais

Complexidade de algoritmos Notação Big-O

Complexidade de algoritmos Notação Big-O Complexidade de algoritmos Notação Big-O Prof. Byron Leite Prof. Tiago Massoni Engenharia da Computação Poli - UPE Motivação O projeto de algoritmos é influenciado pelo estudo de seus comportamentos Problema

Leia mais

Complexidade de Algoritmos

Complexidade de Algoritmos Complexidade de Algoritmos! Uma característica importante de qualquer algoritmo é seu tempo de execução! é possível determiná-lo através de métodos empíricos, considerando-se entradas diversas! é também

Leia mais

Introdução à Ciência da Computação II

Introdução à Ciência da Computação II Introdução à Ciência da Computação II Análise de Algoritmos: Parte I Prof. Ricardo J. G. B. Campello Este material consiste de adaptações e extensões de slides disponíveis em http://ww3.datastructures.net

Leia mais

Linguagem de Programação I Prof. Tiago Eugenio de Melo.

Linguagem de Programação I Prof. Tiago Eugenio de Melo. Linguagem de Programação I Prof. Tiago Eugenio de Melo tmelo@uea.edu.br www.tiagodemelo.info 1 Sumário Introdução Conceitos preliminares Introdução Variáveis Comandos Condicionais 2 Por que aprender a

Leia mais

Instituto Superior de Engenharia de Lisboa

Instituto Superior de Engenharia de Lisboa Instituto Superior de Engenharia de Lisboa Introdução à Programação (PG) Docente: Pedro Viçoso Fazenda (pfazenda@cedet.isel.ipl.pt) Professor Responsável: Pedro Alexandre Pereira (palex@cc.isel.ipl.pt)

Leia mais

Análise de algoritmos. Parte I

Análise de algoritmos. Parte I Análise de algoritmos Parte I 1 Procedimento X Algoritmo Procedimento: sequência finita de instruções, que são operações claramente descritas, e que podem ser executadas mecanicamente, em tempo finito.

Leia mais

Marcelo Keese Albertini Faculdade de Computação Universidade Federal de Uberlândia

Marcelo Keese Albertini Faculdade de Computação Universidade Federal de Uberlândia Introdução à Análise de Algoritmos Marcelo Keese Albertini Faculdade de Computação Universidade Federal de Uberlândia Aula de hoje Nesta aula veremos: Sobre a disciplina Exemplo: ordenação Sobre a disciplina

Leia mais

Algoritmos e Estruturas de Dados 2005/2006. Algoritmo: conjunto claramente especificado de instruções a seguir para resolver um problema

Algoritmos e Estruturas de Dados 2005/2006. Algoritmo: conjunto claramente especificado de instruções a seguir para resolver um problema Vectores: Algoritmos de Pesquisa Algoritmos e Estruturas de Dados 2005/2006 Introdução Algoritmo: conjunto claramente especificado de instruções a seguir para resolver um problema noção de algoritmo muito

Leia mais

Recursividade. Objetivos do módulo. O que é recursividade

Recursividade. Objetivos do módulo. O que é recursividade Recursividade Objetivos do módulo Discutir o conceito de recursividade Mostrar exemplos de situações onde recursividade é importante Discutir a diferença entre recursividade e iteração O que é recursividade

Leia mais

André Vignatti DINF- UFPR

André Vignatti DINF- UFPR Notação Assintótica: Ω, Θ André Vignatti DINF- UFPR Limitantes Inferiores Considere o seguinte trecho de código: void main () { /* trecho que le N da entrada padrao */ for (i = 0 ; i< N; i++) puzzle(i);

Leia mais

7. Introdução à Complexidade de Algoritmos

7. Introdução à Complexidade de Algoritmos 7. Introdução à Complexidade de Algoritmos Fernando Silva DCC-FCUP Estruturas de Dados Fernando Silva (DCC-FCUP) 7. Introdução à Complexidade de Algoritmos Estruturas de Dados 1 / 1 Análise de Algoritmos

Leia mais

4. Constantes. Constantes pré-definidas

4. Constantes. Constantes pré-definidas 4. Constantes Constantes pré-definidas O PHP possui algumas constantes pré-definidas, indicando a versão do PHP, o Sistema Operacional do servidor, o arquivo em execução, e diversas outras informações.

Leia mais

Estruturas de Dados, Análise de Algoritmos e Complexidade Estrutural. Carlos Alberto Alonso Sanches

Estruturas de Dados, Análise de Algoritmos e Complexidade Estrutural. Carlos Alberto Alonso Sanches CT-234 Estruturas de Dados, Análise de Algoritmos e Complexidade Estrutural Carlos Alberto Alonso Sanches CT-234 2) Algoritmos recursivos Indução matemática, recursão, recorrências Indução matemática Uma

Leia mais

Introdução à Ciência da Computação II

Introdução à Ciência da Computação II Introdução à Ciência da Computação II 2semestre/200 Prof Alneu de Andrade Lopes Apresentação com material gentilmente cedido pelas profas Renata Pontin Mattos Fortes http://wwwicmcuspbr/~renata e Graça

Leia mais

Teoria da Computação Aula 9 Noções de Complexidade

Teoria da Computação Aula 9 Noções de Complexidade Teoria da Computação Aula 9 Noções de Complexidade Prof. Esp. Pedro Luís Antonelli Anhanguera Educacional Análise de um Algoritmo em particular Qual é o custo de usar um dado algoritmo para resolver um

Leia mais

CES-11. Noções de complexidade de algoritmos. Complexidade de algoritmos. Avaliação do tempo de execução. Razão de crescimento desse tempo.

CES-11. Noções de complexidade de algoritmos. Complexidade de algoritmos. Avaliação do tempo de execução. Razão de crescimento desse tempo. CES-11 Noções de complexidade de algoritmos Complexidade de algoritmos Avaliação do tempo de execução Razão de crescimento desse tempo Notação O Exercícios COMPLEXIDADE DE ALGORITMOS Importância de análise

Leia mais

Introdução à Programação

Introdução à Programação Introdução à Programação 1.Ano LCC-MIERSI DCC - FCUP Nelma Moreira Aula 2 Etapas para o desenvolvimento dum programa 1. Perceber o problema 2. Encontrar um procedimento algorítmico para o resolver. Estratégias:

Leia mais

Estruturas de Dados Algoritmos

Estruturas de Dados Algoritmos Estruturas de Dados Algoritmos Prof. Eduardo Alchieri Algoritmos (definição) Sequência finita de instruções para executar uma tarefa Bem definidas e não ambíguas Executáveis com uma quantidade de esforço

Leia mais

Algoritmos de Ordenação

Algoritmos de Ordenação Algoritmos de Ordenação! Problema: encontrar um número de telefone em uma lista telefônica! simplificado pelo fato dos nomes estarem em ordem alfabética! e se estivesse sem uma ordem?! Problema: busca

Leia mais

Análise de algoritmos

Análise de algoritmos Análise de algoritmos Introdução à Ciência da Computação 2 Baseado nos slides do Prof. Thiago A. S. Pardo Algoritmo Noção geral: conjunto de instruções que devem ser seguidas para solucionar um determinado

Leia mais

Compilador de LP3 para C3E e P3

Compilador de LP3 para C3E e P3 Compilador de LP3 para C3E e P3 Luís Gil 1 13 de Junho de 2005 1 estudante de Ciências Informáticas no Instituto Superior Técnico 1 Introdução Este relatório descreve a sintaxe e a semântica da Linguagem

Leia mais

TÉCNICO DE INFORMÁTICA - SISTEMAS

TÉCNICO DE INFORMÁTICA - SISTEMAS 782 - Programação em C/C++ - estrutura básica e conceitos fundamentais Linguagens de programação Linguagem de programação são conjuntos de palavras formais, utilizadas na escrita de programas, para enunciar

Leia mais

AULA TEÓRICA 3 Tema 2. Introdução a programação em Java (JVM, JDK)

AULA TEÓRICA 3 Tema 2. Introdução a programação em Java (JVM, JDK) AULA TEÓRICA 3 Tema 2. Introdução a programação em Java (JVM, JDK) Ø LP Java. Estrutura de um programa em Java. Ø Conjunto de caracteres utilizado. Ø Identificadores. Ø Variáveis e constantes. Ø Tipos

Leia mais

Fabiano Moreira.

Fabiano Moreira. Fabiano Moreira professor@fabianomoreira.com.br Um pouco de história Java 1.02 (250 classes, lenta) Java 1.1 (500 classes, um pouco mais rápida) Java 2, versões 1.2-1.4 (2300 classes, muito mais rápida)

Leia mais

Projeto e Análise de Algoritmos Aula 4: Dividir para Conquistar ou Divisão e Conquista ( )

Projeto e Análise de Algoritmos Aula 4: Dividir para Conquistar ou Divisão e Conquista ( ) Projeto e Análise de Algoritmos Aula 4: Dividir para Conquistar ou Divisão e Conquista (2.1-2.2) DECOM/UFOP 2013/1 5º. Período Anderson Almeida Ferreira Adaptado do material desenvolvido por Andréa Iabrudi

Leia mais

Centro Federal de Educação Tecnológica de Minas Gerais Programa de Pós-Graduação em Modelagem Matemática e Computacional

Centro Federal de Educação Tecnológica de Minas Gerais Programa de Pós-Graduação em Modelagem Matemática e Computacional Centro Federal de Educação Tecnológica de Minas Gerais Programa de Pós-Graduação em Modelagem Matemática e Computacional Disciplina: Algoritmos e Estruturas de Dados Professor: Flávio Cardeal Lista de

Leia mais

Departamento de Ciência de Computadores Estruturas de Dados (CC114)

Departamento de Ciência de Computadores Estruturas de Dados (CC114) 1. Cotação de cada pergunta: 1. 30 / 2. 40 / 3. 30 (Total: 100 pontos) 2. Responda às questões de forma clara e concisa nas folhas de exame distribuídas. 1. (Valorização: 30%) Responda às seguintes questões:

Leia mais

Disciplina: Introdução à Engenharia da Computação

Disciplina: Introdução à Engenharia da Computação Colegiado de Engenharia de Computação Disciplina: Introdução à Engenharia da Computação Aula 07 (semestre 2011.2) Prof. Rosalvo Ferreira de Oliveira Neto, M.Sc. rosalvo.oliveira@univasf.edu.br 2 Representação

Leia mais

Prova 2 PMR2300 1o. semestre 2015 Prof. Thiago Martins

Prova 2 PMR2300 1o. semestre 2015 Prof. Thiago Martins Prova PMR00 o. semestre 0 Prof. Thiago Martins Instruções: Escreva o nome e o número USP na folha de papel almaço.. ( pontos) Um heap binário é uma árvore binária completa (todos os níveis exceto o último

Leia mais

ALGORITMOS E ESTRUTURAS DE DADOS CES-11 Prof. Paulo André Castro Sala 110 Prédio da Computação IECE - ITA

ALGORITMOS E ESTRUTURAS DE DADOS CES-11 Prof. Paulo André Castro Sala 110 Prédio da Computação   IECE - ITA ALGORITMOS E ESTRUTURAS DE DADOS CES-11 Prof. Paulo André Castro pauloac@ita.br Sala 110 Prédio da Computação www.comp.ita.br/~pauloac IECE - ITA MÉTODOS MAIS EFICIENTES QUE O(N 2 ) Método Quicksort Método

Leia mais

EXPRESSÕES BOOLEANAS. Ex: boolean b = false; // declara uma variável do tipo boolean e atribui false

EXPRESSÕES BOOLEANAS. Ex: boolean b = false; // declara uma variável do tipo boolean e atribui false Cursos: Análise, Ciência da Computação e Sistemas de Informação Programação I - Prof. Aníbal Notas de aula 4 EXPRESSÕES BOOLEANAS O tipo primitivo boolean É um tipo de dados primitivo em Java que possui

Leia mais

Introdução a JAVA. Variaveis, tipos, expressões, comandos e blocos

Introdução a JAVA. Variaveis, tipos, expressões, comandos e blocos Introdução a JAVA Variaveis, tipos, expressões, comandos e blocos Roteiro Variáveis e tipos Operadores aritméticos, lógicos, relacionais e bit-a-bit Atribuição Comandos básicos Ler, Escrever, Condicional,

Leia mais

Prova 1 PMR2300 / PMR3201 1o. semestre 2015 Prof. Thiago Martins

Prova 1 PMR2300 / PMR3201 1o. semestre 2015 Prof. Thiago Martins Prova 1 PMR2300 / PMR3201 1o. semestre 2015 Prof. Thiago Martins Instruções: Escreva o nome e o número USP na folha de papel almaço. Indique na linha seguinte quatro das cinco questões abaixo que devem

Leia mais

Estruturas de Dados Algoritmos de Ordenação

Estruturas de Dados Algoritmos de Ordenação Estruturas de Dados Prof. Eduardo Alchieri (introdução) Considere uma sequência arbitrária S = {s1, s2, s3,...sn} composta por n 0 elementos retirados do conjunto universo U O objetivo da ordenação é arrumar

Leia mais

Projeto e Análise de Algoritmos

Projeto e Análise de Algoritmos Projeto e Análise de Algoritmos Aula 09 Algoritmos de Ordenação Edirlei Soares de Lima Ordenação Problema: Entrada: conjunto de itens a 1, a 2,..., a n ; Saída: conjunto de itens

Leia mais

7. Introdução à Complexidade de Algoritmos

7. Introdução à Complexidade de Algoritmos 7. Introdução à Complexidade de Algoritmos Fernando Silva DCC-FCUP Estruturas de Dados Fernando Silva (DCC-FCUP) 7. Introdução à Complexidade de Algoritmos Estruturas de Dados 1 / 1 Análise de Algoritmos

Leia mais

Estrutura de Dados Conceitos Iniciais

Estrutura de Dados Conceitos Iniciais Engenharia de CONTROLE e AUTOMAÇÃO Estrutura de Dados Conceitos Iniciais Aula 01 DPEE 1038 Estrutura de Dados para Automação Curso de Engenharia de Controle e Automação Universidade Federal de Santa Maria

Leia mais

ESQUEMA AULA PRÁTICA 0 Familiarização com o Ambiente de Desenvolvimento NetBeans Construção do primeiro programa em java.

ESQUEMA AULA PRÁTICA 0 Familiarização com o Ambiente de Desenvolvimento NetBeans Construção do primeiro programa em java. P. Fazendeiro & P. Prata POO FP0/1 ESQUEMA AULA PRÁTICA 0 Familiarização com o Ambiente de Desenvolvimento NetBeans Construção do primeiro programa em java. 0 Iniciar o ambiente de desenvolvimento integrado

Leia mais

Métodos Computacionais. Operadores, Expressões Aritméticas e Entrada/Saída de Dados

Métodos Computacionais. Operadores, Expressões Aritméticas e Entrada/Saída de Dados Métodos Computacionais Operadores, Expressões Aritméticas e Entrada/Saída de Dados Tópicos da Aula Hoje aprenderemos a escrever um programa em C que pode realizar cálculos Conceito de expressão Tipos de

Leia mais

MÉTODOS DE CLASSIFICAÇÃO EM MEMÓRIA PRIMÁRIA. George Gomes Cabral

MÉTODOS DE CLASSIFICAÇÃO EM MEMÓRIA PRIMÁRIA. George Gomes Cabral MÉTODOS DE CLASSIFICAÇÃO EM MEMÓRIA PRIMÁRIA George Gomes Cabral MÉTODOS DE CLASSIFICAÇÃO EM MEMÓRIA PRIMÁRIA Métodos Elementares Classificação por Trocas Método da Bolha Bubblesort Método de Partição

Leia mais

Preliminares. Profa. Sheila Morais de Almeida. agosto

Preliminares. Profa. Sheila Morais de Almeida. agosto Preliminares Profa. Sheila Morais de Almeida DAINF-UTFPR-PG agosto - 2016 Algoritmos Definição - Skiena Algoritmo é a ideia por trás dos programas de computador. É aquilo que permanece igual se o programa

Leia mais

Estruturas de Dados 2

Estruturas de Dados 2 Estruturas de Dados 2 Técnicas de Projeto de Algoritmos Dividir e Conquistar IF64C Estruturas de Dados 2 Engenharia da Computação Prof. João Alberto Fabro - Slide 1/83 Projeto de Algoritmos por Divisão

Leia mais

Técnicas de projeto de algoritmos: Indução

Técnicas de projeto de algoritmos: Indução Técnicas de projeto de algoritmos: Indução ACH2002 - Introdução à Ciência da Computação II Delano M. Beder Escola de Artes, Ciências e Humanidades (EACH) Universidade de São Paulo dbeder@usp.br 08/2008

Leia mais

Algoritmos de ordenação Quicksort

Algoritmos de ordenação Quicksort Algoritmos de ordenação Quicksort Sumário Introdução Descrição do quicksort Desempenho do quicksort Pior caso Melhor caso Particionamento balanceado Versão aleatória do quicksort Análise do quicksort Pior

Leia mais

Filas de prioridade. Marcelo K. Albertini. 3 de Dezembro de 2013

Filas de prioridade. Marcelo K. Albertini. 3 de Dezembro de 2013 Filas de prioridade Marcelo K. Albertini de Dezembro de / Filas de prioridade O que é uma fila de prioridade? Estrutura de dados que generaliza a ideia de ordenação. Coleções de elementos: inserir e remover

Leia mais

Programação de Computadores I Introdução ao C PROFESSORA CINTIA CAETANO

Programação de Computadores I Introdução ao C PROFESSORA CINTIA CAETANO Programação de Computadores I Introdução ao C PROFESSORA CINTIA CAETANO Introdução Criada em 1972, por Dennis Ritchie; Centro de Pesquisas da Bell Laboratories; Para utilização no S.O. UNIX; C é uma linguagem

Leia mais

Aula 3: Algoritmos: Formalização e Construção

Aula 3: Algoritmos: Formalização e Construção Aula 3: Algoritmos: Formalização e Construção Fernanda Passos Universidade Federal Fluminense Programação de Computadores IV Fernanda Passos (UFF) Algoritmos: Formalização e Pseudo-Código Programação de

Leia mais

Introdução à linguagem C

Introdução à linguagem C Introdução à linguagem C Luís Charneca luis.charneca@gmail.com Introdução ao C O C nasceu na década de 70. O seu inventor, Dennis Ritchie, implementou-o pela primeira vez usando um DEC PDP-11 correndo

Leia mais

PROGRAMAÇÃO I A LINGUAGEM DE PROGRAMAÇÃO JAVA I

PROGRAMAÇÃO I A LINGUAGEM DE PROGRAMAÇÃO JAVA I PROGRAMAÇÃO I A LINGUAGEM DE PROGRAMAÇÃO JAVA I Prof. Dr. Daniel Caetano 2017-1 Objetivos Conhecer os tipos de dados, constantes e como declarar variáveis Conhecer os operadores e expressões Conhecer e

Leia mais

Algoritmos e Programação

Algoritmos e Programação Algoritmos e Programação Aula 3 Introdução a Linguagem C Profa. Marina Gomes marinagomes@unipampa.edu.br 1 Aula de Hoje - Criar programas simples em C utilizando a estrutura básica; - Declarar variáveis;

Leia mais

Projeto e Análise de Algoritmos

Projeto e Análise de Algoritmos Projeto e Análise de Algoritmos A. G. Silva Baseado nos materiais de Souza, Silva, Lee, Rezende, Miyazawa Unicamp Ribeiro FCUP 18 de agosto de 2017 Conteúdo programático Introdução (4 horas/aula) Notação

Leia mais

Capítulo 7. Expressões e Sentenças de Atribuição

Capítulo 7. Expressões e Sentenças de Atribuição Capítulo 7 Expressões e Sentenças de Atribuição Introdução Expressões são os meios fundamentais de especificar computações em uma linguagem de programação Para entender a avaliação de expressões, é necessário

Leia mais

Árvores. Thiago Martins, Fabio Gagliardi Cozman. PMR2300 / PMR3201 Escola Politécnica da Universidade de São Paulo

Árvores. Thiago Martins, Fabio Gagliardi Cozman. PMR2300 / PMR3201 Escola Politécnica da Universidade de São Paulo PMR2300 / PMR3201 Escola Politécnica da Universidade de São Paulo Árvore: estrutura composta por nós e arestas entre nós. As arestas são direcionadas ( setas ) e: um nó (e apenas um) é a raiz; todo nó

Leia mais

Divisão e Conquista. Fernando Lobo. Algoritmos e Estrutura de Dados II. É uma técnica para resolver problemas (veremos outras técnicas mais adiante).

Divisão e Conquista. Fernando Lobo. Algoritmos e Estrutura de Dados II. É uma técnica para resolver problemas (veremos outras técnicas mais adiante). Divisão e Conquista Fernando Lobo Algoritmos e Estrutura de Dados II 1 / 27 Divisão e Conquista É uma técnica para resolver problemas (veremos outras técnicas mais adiante). Consiste em 3 passos: Dividir

Leia mais

Teoria da Computação. Aula 3 Comportamento Assintótico 5COP096. Aula 3 Prof. Dr. Sylvio Barbon Junior. Sylvio Barbon Jr

Teoria da Computação. Aula 3 Comportamento Assintótico 5COP096. Aula 3 Prof. Dr. Sylvio Barbon Junior. Sylvio Barbon Jr 5COP096 Teoria da Computação Aula 3 Prof. Dr. Sylvio Barbon Junior 1 Sumário 1) Exercícios Medida de Tempo de Execução. 2) Comportamento Assintótico de Funções. 3) Exercícios sobre Comportamento Assintótico

Leia mais

Lista de exercícios sobre contagem de operações Prof. João B. Oliveira

Lista de exercícios sobre contagem de operações Prof. João B. Oliveira Lista de exercícios sobre contagem de operações Prof. João B. Oliveira 1. metodo m ( Vetor V ) int i, res = 0; para i de 1 a V.size res = res + V[i]; return res; Soma de elementos de um vetor, O( ). 2.

Leia mais

Prova 1 PMR3201 Computação para Automação 1o. semestre 2016 Prof. Thiago de Castro Martins

Prova 1 PMR3201 Computação para Automação 1o. semestre 2016 Prof. Thiago de Castro Martins Prova 1 PMR3201 Computação para Automação 1o. semestre 2016 Prof. Thiago de Castro Martins 1. (25 pontos) A listagem a seguir mostra o código de uma função que converte uma cadeia de caracteres com a representação

Leia mais

Aula 06: Análise matemática de algoritmos recursivos

Aula 06: Análise matemática de algoritmos recursivos Aula 06: Análise matemática de algoritmos recursivos David Déharbe Programa de Pós-graduação em Sistemas e Computação Universidade Federal do Rio Grande do Norte Centro de Ciências Exatas e da Terra Departamento

Leia mais

Quantidade de memória necessária

Quantidade de memória necessária Tempo de processamento Um algoritmo que realiza uma tarefa em 10 horas é melhor que outro que realiza em 10 dias Quantidade de memória necessária Um algoritmo que usa 1MB de memória RAM é melhor que outro

Leia mais

LINGUAGEM C: FUNÇÕES FUNÇÃO 08/01/2018. Funções são blocos de código que podem ser nomeados e chamados de dentro de um programa.

LINGUAGEM C: FUNÇÕES FUNÇÃO 08/01/2018. Funções são blocos de código que podem ser nomeados e chamados de dentro de um programa. LINGUAGEM C: FUNÇÕES Prof. André Backes FUNÇÃO Funções são blocos de código que podem ser nomeados e chamados de dentro de um programa. printf(): função que escreve na tela scanf(): função que lê o teclado

Leia mais

Departamento de Ciência de Computadores Estruturas de Dados (CC114)

Departamento de Ciência de Computadores Estruturas de Dados (CC114) 1. Cotação de cada pergunta: 1. 32 / 2. 36 / 3. 32 (Total: 100 pontos) 2. Responda às questões de forma clara e concisa nas folhas de exame distribuídas. 1. (Valorização: 32%) Responda às seguintes questões:

Leia mais

Filas de Prioridade. Uma fila de prioridade pode ser vista como uma generalização das filas com as seguintes duas operações:

Filas de Prioridade. Uma fila de prioridade pode ser vista como uma generalização das filas com as seguintes duas operações: Filas de Prioridade e Heaps 9.1 Filas de Prioridade Uma fila de prioridade pode ser vista como uma generalização das filas com as seguintes duas operações: inserir um elemento na fila; remover o elemento

Leia mais

Nosso Primeiro Programa Java

Nosso Primeiro Programa Java Java linguagem, jvm, jdk, jre, ide Nosso Primeiro Programa Java Professoras: Ariane Machado Lima Fátima L. S. Nunes 1 Lembrando os objetivos desta disciplina Aprender a programar. Para isso precisamos

Leia mais

Algoritmos e Estruturas de Dados I Prof. Tiago Eugenio de Melo

Algoritmos e Estruturas de Dados I Prof. Tiago Eugenio de Melo Algoritmos e Estruturas de Dados I Prof. Tiago Eugenio de Melo tmelo@uea.edu.br www.tiagodemelo.info Observações O conteúdo dessa aula é parcialmente proveniente do Capítulo 11 do livro Fundamentals of

Leia mais