Esta é uma pré-visualização de arquivo. Entre para ver o arquivo original
AEDSII_aula_001_linguagem_C.pdf Algoritmos e Estruturas de Dados II Aplicação de Algoritmos Ordenação Pesquisa Referências N. Ziviani. Projeto de Algoritmos com Implementações em Pascal e C. Cengage Learning, São Paulo, SP, 2011. R. Sedgewick. Algorithms in C, Parts 1-4 (Fundamental Algorithms, Data Structures, Sorting, Searching). Addison- Wesley, 1997. Ementa • Tipos Abstratos de Dados (TADs) • Análise de Algoritmos O(n), O(n log n), )(n!), ... • Estruturas de dados listas, filas e pilhas • Métodos de ordenação quick, heap, merge, select, etc • Métodos de pesquisa hash, árvores, árvores binárias, árvores digitais Parte 1 Parte 2 Parte 3 Avaliação • 3 provas (total 60 pontos) • trabalhos práticos – 40 pontos (TP0 + 2 TPs) • Implementação • Documentação • Teste Regras Gerais Exame especial (nota >= 40 && frequencia >= 75%). Prova de reposição Apenas para aqueles que perderam uma prova. Monitoria - horário de atendimento: (em breve) Presença: Verificada por meio de chamada Moddle / Website • Todas informações relacionadas ao curso, incluindo notas de aulas, estarão disponíveis no Moodle, fórum • PRATICO: Sistema de submissão de trabalhos práticos http://aeds.dcc.ufmg.br Detalhes • Linguagem: C • Software Recomendado: CodeBlocks (www.codeblocks.org) • http://wiki.codeblocks.org/index.php?title=Creating_a_new_project • Alta carga extra classe • Não utilizar bibliotecas do windows • Manual de instalação: • http://wiki.codeblocks.org/index.php? title=Installing_Code::Blocks Code::blocks Alternativa Sempre pode usar gcc + vi (emacs) + gdb Tópicos • Indentação • Comentários • Modularização • Compilação e Debug • Entrada e saída • Vetores e Strings • Passagem de parâmetros • Structs Linguagem C Boas Prá)cas • Com um pequeno esforço extra, programas escritos em C podem se tornar mais legíveis e mais facilmente “debugáveis” • No caso de disciplinas de programação, isso ainda ajuda no entendimento das idéias e na correção de trabalhos Indentação • É usual empregar TABS para indicar blocos de programação – Em geral, 1 tab equivale a 8 espaços, MAS NÃO USAR ESPAÇOS para alinhar • Há vários es)los • Quando o bloco é longo, é usual colocar um pequeno comentário após o fechamento indicando o que está sendo fechado Indentação • K&R: Kernighan & Ritchie Indentação • 1TBS: One True Brace Style Indentação • Allman Comentários • Importantes para compreensão do código • Mais importantes em código mais complexo • Úteis para explicar o conteúdo de variáveis, mas não subs)tuem um bom critério de atribuição de nomes • Não exagerar! Comentários • No início de cada módulo de código (arquivos .c, .h) • Uma linha em cada função, explicando o que ela faz – Não é necessário explicar COMO ela faz, o código deve ser claro o bastante para permi)r esse entendimento em uma função razoavelmente pequena – Se necessário, considerar a quebra em outras funções • Comentário na linha da declaração, se necessário, para esclarecer o significado/o uso de uma variável • Comentário na linha do fecha-‐chave, para ajudar a entender blocos e loops Comentários • No início de um bloco/arquivo fonte Comentários • Em função (simples) Comentários • Em funções (arquivo .h) Comentários • Em variáveis • Em structs Comentários • No fim de um bloco de programa • No código Modularização • Planejar a quebra de um programa em módulos – Um módulo não significa necessariamente um arquivo fonte; muitas vezes, é um par de arquivos .c / .h – Existe sempre um arquivo fonte para o programa principal (main), e outros para funções adicionais ou componentes • Montar módulos especificamente para )pos abstratos de dados [aula: TAD] • Procurar dar independência aos módulos, para que possam eventualmente ser reaproveitados Modularização Modularização Modularização Modularização Atenção: incluir timer.h e timer.c no projeto Modularização transformação em biblioteca )mer.lib Copiar timer.h para o diretório “includes” do compilador Copiar timer.lib para o diretório lib do compilador Constantes e #define • Não usar “números mágicos” no código • Algumas constantes usadas no programa podem ter que ser modificadas, e é mais fácil fazer isso em um lugar só • Sempre que for tornar o código mais legível, usar #define para dar um nome à constante – Em geral, nomes de constantes são em maiúsculas #define PI 3.141592 #define MAX_VETOR 1000 Nomes de variáveis • Algumas variáveis merecem nomes significa)vos: MAX_VETOR, numClientes, listaAlunos • Variáveis auxiliares em geral recebem nomes curtos: i, j, aux, x – Cuidado para não fazer confusão – Não abusar: i, ii, iii, aux1, aux2, aux3... – Variáveis inteiras: i, j, k – Variáveis reais: x, y, z – Strings: s1, s2 – Booleanas: nome do teste (existe, valido, ocorre) Nomes de variáveis • Es)los variados: – Só minúsculas (i, num, conta) – Só maiúsculas (constantes: PI, E, MAX) – CamelCase (numMat, anguloEntrada) – Indicação do )po no início do nome (iNum, iValor, fRaio, fAltura, dVolume) • Há quem prefira inserir comentários e usar nomes de variáveis em inglês, por ficar mais próximo da linguagem de programação Organização e limpeza • Procurar dar um aspecto organizado ao código, ajuda na compreensão • Entender o código fonte como um instrumento de comunicação • Comentar excessivamente código mal escrito não ajuda • Dar nomes adequados a variáveis ajuda bastante Parênteses e espaçamento • Usar espaços antes de parênteses, depois de vírgulas, ao redor de operadores binários if (x == 10) y = 5; for (i = 0; i < 10; i++) { x += a; a = f(b); } • Cuidado com notações compactas demais, e com comandos embu)dos em outros if (x++ == b) y = 5; Correção e robustez • Testes: prever todo )po de problema e variações na entrada de dados – Limites de vetores – Valores inteiros e de ponto flutuante – Contadores e incremento – Testes de fim de arquivo – Teste de disponibilidade de memória para alocação Compilação • LER as mensagens de erro e ENTENDER a origem do problema • Warnings: indicam problemas potenciais, devem ser resolvidos • Muitas vezes a mensagem de erro não reflete o que está ocorrendo – Observar a linha em que o erro foi indicado, a linha anterior, o bloco de código em que ocorreu, e o corpo da função em que ocorreu Debugger • Ajuda a acompanhar os valores das variáveis ao longo da execução – Observar o valor de variáveis (watches) – Posicionar pontos de interrupção (breakpoints) – Executar passo a passo • Vide http://wiki.codeblocks.org/index.php?title=Debugging_with_Code::Blocks • Documentação do CodeBlocks http://wiki.codeblocks.org/index.php?title=Main_Page AEDSII_aula_002_linguagem_C-entrada_e_saida.pdf ENTRADA E SAÍDA Obje%vos • Entrada e saída formatada • Uso de arquivos • Escrita e leitura de blocos de dados • Movimentação em arquivos I/O em C • Formalmente, ro%nas de entrada e saída não fazem parte da linguagem, e sim de bibliotecas que acompanham os compiladores – Felizmente, são padronizadas – Exige-‐se a linha #include <stdio.h> para usá-‐las I/O em C • int printf(const char *format, ...); – Retorna o número de caracteres impressos – O string contém uma “máscara” (template) com lacunas reservadas para os valores que serão impressos – Pode não exis%r lacuna printf(“O valor de x eh %d”, x); printf(“Area: %f\n”, PI*d*d/4); printf(“Nome: %s”, nomeAluno); prinT • Especificadores de formato – %c (char) – %s (string) – %d (int) – %ld (long int) – %f (float) – %lf (double) – %e (float notação cienXfica) – %g (e ou f, ou seja, notação cienXfica se necessário) prinT • %[flags][width][.precision][length]specifier Caracteres de escape • Acrescentados à máscara para provocar reposicionamento do cursor – \n: nova linha – \t: tabulação – \r: backspace – \\: caractere da barra inver%da prinT int main() { int ret; ret = printf("Hello world!\n"); printf("ret: %d\n", ret); printf("%p %p\n", &ret, (int *) ret); return 0; } Entrada • Com máscara: scanf(string, *var [, *var, ...]); – Mesmos especificadores de formato do prinT – A função precisa receber o endereço da variável à qual o valor digitado será atribuído scanf(“%d”, &num) scanf(“%c%d”, &letra, &num); scanf(“%c”, &ch); scanf(“%s”, s); // string scanf(“%13c”, s); //le 13 caracteres scanf(“ %c”, &ch); //pula brancos Entrada Entrada • Observe que scanf interrompe a leitura de um string quando encontra um branco, se usado com %s • Uso de %[]: – %[aeiou]: apenas as vogais são permi%das – %[^aeiou]: as vogais não são permi%das – %[^\n]: interrompe quando recebe um [enter] – %60[^\n]: admite até 60 caracteres, e para quando encontra um enter Entrada • Linhas inteiras: char *gets(char *); – Lê uma linha inteira, excluindo \n, e coloca \0 no final – Com limite de tamanho: • fgets(string, tamMax, stdin) Entrada int main() { char str[10]; char str2[10]; char str3[10]; gets(str); gets(str2); fgets(str3, 10, stdin); printf("str: %s\nstr2: %s\nstr3: %s", str, str2,str3); return 0; } Entrada • Caracteres individuais: int getchar(void); – O caractere digitado é o valor de retorno da função ret = getchar(); printf("entrada: %c %d\n", ret, ret); > 1 > entrada: 1 49 Exemplo (POSCOMP 2009) #include<stdio.h> #include<string.h> int main (void) { char texto[]= "foi muito facil"; int i; for (i = 0; i < strlen(texto); i++){ if (texto[i] == ' ') break; } i++; for ( ; i < strlen(texto); i++) printf("%c", texto[i]); return 0; } O que será impresso quando o programa for executado? I/O para arquivos • A entrada do teclado e saída para o monitor são realizadas internamente considerando três disposi%vos virtuais: stdin, stdout e stderr – Como são disposi%vos padrão, referências a stdin e stdout eles são omi%das dos comandos – fgets(string, tamMax, stdin); • I/O para arquivos é muito semelhante à operação com stdin e stdout, mas um handle ao arquivo tem que ser fornecido I/O para arquivos // abre ou cria um arquivo // retorno: handle para arquivo criado FILE *fopen(const char *name, const char *type); // fecha um arquivo // retorna 0 se executado com sucesso int fclose(FILE *); // remove dados do buffer e envia para o arquivo // retorna 0 se executado com sucesso int fflush(FILE *); // testa final de arquivo // Retorna diferente de zero se fim do arquivo foi atingido int feof(FILE *); I/O para arquivos • fopen: modos de abertura I/O para arquivos • O handle é ob%do no momento da abertura do arquivo FILE *inFile; // variável handle FILE *outfile; ... //abre o arquivo para leitura (r) ou gravacao (w) inFile = fopen(“arquivo.txt”, “r”); outFile = fopen(“saida.txt”, “w”); ... fscanf(inFile, “%d”, &num); ... fprintf(outFile, “O valor lido eh %8.2d\n”, num); ... fclose(inFile); fclose(outFile); I/O para arquivos • Se o handle retornar nulo do comando fopen, então ocorreu erro (arquivo não encontrado, arquivo travado contra gravação, permissão negada pelo sistema operacional, etc.) • Testar: if ((inFile = fopen(“arquivo.txt”, “r”)) == NULL) { printf(“Nao consegui abrir.\n”); exit(1); } I/O para arquivos • Equivalências • gets() à fgets(arq, tamMax, string); • getchar() à fgetc(arq); • putc(ch) à fputc(arq, ch) • prinT à fprinT(arq, string, valor) • scanf à fscanf(arq, string, endereço) I/O para arquivos #include <stdio.h> #include <stdlib.h> int main() { FILE *f; char ch; f = fopen("main.c","r"); while ((ch = fgetc(f)) != EOF) printf("%c", ch); fclose(f); return 0; } I/O para arquivos (variação) #include <stdio.h> #include <stdlib.h> int main() { FILE *f; char ch; f = fopen("main.c","r"); while (!feof(f)) { ch = fgetc(f); printf("%c", ch); } fclose(f); return 0; } I/O para arquivos • Movimentação em arquivos // retorna a posição atual do arquivo long ftell(FILE *f); // seta posição no arquivo, com relação à variável origin // retorna 0 se bem sucedido (offset em bytes). int fseek(FILE *f, long offset, int origin); origin Macro Valor deslocamento relativo SEEK_SET 0 Início do arquivo SEEK_CUR 1 Posição atual SEEK_END 2 Fim do arquivo int main() { FILE *f; char arquivo[] = "main.c"; long int pos; char str[512]; f = fopen(arquivo,"r"); pos = ftell(f); fgets(str, 512, f); printf("[posicao: %ld] %s\n", pos, str); fseek(f, 10, SEEK_SET); pos = ftell(f); fgets(str, 512, f); printf("[posicao: %ld] %s\n", pos, str); I/O para arquivos fseek(f, -15, SEEK_END); pos = ftell(f); fgets(str, 512, f); printf("[posicao: %ld] %s\n", pos, str); fclose(f); return 0; } Numerando linhas int main() { FILE *f; char ch; int nlinha = 1; f = fopen("main.c","r"); printf("%d: ", nlinha++); while (!feof(f)) { ch = fgetc(f); printf("%c", ch); if (ch == '\n') printf("%d: ", nlinha++); } fclose(f); return 0; } Entrada da Linha de Comando • Comunicação pela linha de comando int main(int argc, char *arg[]) { int i, vezes; vezes = atoi(argv[2]); for (i = 0; i < vezes; i++) printf(“%s\n”, argv[1]); return 0; } ./exec teste 10 I/O para arquivos • Leitura e escrita de blocos de dados // lê n objetos com tamanho de size bytes cada // retorno: número de objetos lidos size_t fread(void *ptr, size_t size, size_t n, FIE *f); // escreve n objetos com tamanho de siz bytes cada // retorno: número de objetos escritos size_t fwrite(void *ptr, size_t size, size_t n, FILE *f); #include <stdlib.h> #include <stdio.h> typedef struct { string nome; int matricula; char conceito; } TipoAluno; main() { TipoAluno al; al.nome = “Pedro” al.matricula = 200712; al.conceito = ‘A’; } I/O para arquivos main() { TipoAluno al; FILE *f; int c; al.nome = “Pedro” al.matricula = 200712; al.conceito = ‘A’; f = fopen(“arquivo.dat”, “w”); c = fwrite(&al, sizeof(TipoAluno), 1, f); fclose(f); } I/O para arquivos • Outras funções úteis // cria um arquivo temporário (removido quando fechado) // retorno: handle para arquivo criado FILE *tmpfile(void); // gera um nome único que pode ser usado com nome de arquivo char *tmpnam(char *); Exercícios 1. Reescreva o programa para numerar linhas u%lizando a função fgets(). 2. Crie um programa para contar o número de ocorrências de cada caractere con%do em um arquivo. Imprima uma tabela mostrando o número de ocorrências. 3. Implemente um programa para contar o número de linhas em um arquivo. Exercícios 3. Crie um programa que salve os dados a seguir em arquivo. typedef struct { string nome; int matricula; char conceito; } TipoAluno; int main(){ TipoAluno alunos[75]; ... 4. Crie um programa para ler apenas os registros ímpares, salvos pelo código do exercício 3. Exercício 1 char arquivo[] = "main.c"; FILE *f; int ocorrencias[256]; int valor, i; memset(ocorrencias, 0, 256 * sizeof(int)); f = fopen(arquivo, "r"); while (!feof(f)) { valor = fgetc(f); ocorrencias[valor]++; } for (i = 0; i < 256; i++) { printf("%d [%c]: %d\n", i, i, ocorrencias[i]); } fclose(f); Exercício 2 char arquivo[] = "main.c"; char str[1024]; FILE *f; int nlinhas = 0; f = fopen(arquivo, "r"); while (!feof(f)) { fgets(str, 1024, f); nlinhas++; } printf("numero de linhas: %d\n", nlinhas); fclose(f); return 0; AEDSII_aula_003_linguagem_C-TAD.pdf The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the red x still appears, you may have to delete the image and then insert it again. The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have Tipos Abstratos de Dados Luiz Chaimowicz, Raquel O. Prates e Gisele L. Pappa (versão adaptada) Livro “Projeto de Algoritmos” Capítulo 1 © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. n Algoritmo e Programa n Tipo Abstrato de Dados q Qual papel do programador e do usuário do TAD n Conceitos de typedef e struct Algoritmos e Estrutura de Dados II 3 Pontos Principais © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Qual a diferença entre um algoritmo e um programa? © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmo ou Programa? Algoritmos e Estrutura de Dados II © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmo ou Programa? Algoritmos e Estrutura de Dados II © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Algoritmos e Estruturas de Dados n Algoritmo: q Sequência de ações executáveis para a solução de um determinado tipo de problema q Exemplo: “Receita de Bolo” q Algoritmos trabalham sobre Estruturas de Dados n Estrutura de Dados: q Conjunto de dados que representa uma situação real (estado do mundo) q Modo eficiente de armazenamento para facilitar seu acesso e modificação. © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Representação dos Dados n Os dados podem estar representados (estruturados) de diferentes maneiras n Normalmente, a escolha da representação é determinada pelas operações que serão utilizadas sobre eles n Exemplo: números inteiros q Representação por palitinhos: II + IIII = IIIIII q Representação decimal: 1278 + 321 = 1599 q Boa para pequenos números (operação simples) q Boa para números maiores (operação complexa) © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Programas n Um programa é uma formulação concreta de um algoritmo abstrato, baseado em representações de dados específicas n Os programas são feitos em alguma linguagem que pode ser entendida e seguida pelo computador q Linguagem de máquina q Linguagem de alto nível (uso de compilador) q Aqui vamos utilizar a Linguagem C © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Linguagem C n Criada em no início da década de 70 para a programação do sistema operacional Unix n Uma das linguagens mais utilizadas no mundo, e serviu como base para outras como C++, C#, etc. © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Tipos Abstratos de Dados (TADs) n Agrupa a estrutura de dados juntamente com as operações que podem ser feitas sobre esses dados n O TAD encapsula a estrutura de dados. Os usuários do TAD só tem acesso a algumas operações disponibilizadas sobre esses dados n Usuário do TAD x Programador do TAD q Usuário só “enxerga” a interface, não a implementação © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Tipos Abstratos de Dados (TADs) n Dessa forma, o usuário pode abstrair da implementação específica. n Qualquer modificação nessa implementação fica restrita ao TAD n A escolha de uma representação específica é fortemente influenciada pelas operações a serem executadas © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Tipos Abstratos de Dados (TADs) Algoritmos e Estrutura de Dados II Aplicação Especificação Implementação Usuário do TAD Programador do TAD © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Exemplo: Lista de números inteiros n Operações q Faz Lista Vazia q Insere número no começo da lista q Remove de uma posição i 20 13 02 30 Implementação por Vetores: void Insere(int x, Lista L) { for(i=0;...) {...} L[0] = x; } 20 13 02 30 Implementação por Listas Encadeadas void Insere(int x, Lista L) { p = CriaNovaCelula(x); L.primeiro = p; ... } Programa usuário do TAD: int main() { Lista L; int x; x = 20; FazListaVazia(L); Insere(x,L); ... } © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Implementação de TADs n Em linguagens orientadas por objeto (C++, Java) a implementação é feita através de classes n Em linguagens estruturadas (C, pascal) a implementação é feita pela definição de tipos juntamente com a implementação de funções n Vamos utilizar os conceitos em C (typedef e struct) © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Estruturas (Structs) em C n Uma estrutura é uma coleção de uma ou mais variáveis, possivelmente de tipos diferentes, colocadas juntas sob um único nome para manipulação conveniente n Exemplo: q para representar um aluno são necessárias as informações nome, matrícula, conceito q Ao invés de criar três variáveis, é possível criar uma única variável contendo três campos. n Em C, usa-se a construção struct para representar esse tipo de dado © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Estruturas (Structs) em C n Sintaxe: struct nome { [tipo nome_da_variável] ; ... } [variável] ; struct Aluno { string nome; int matricula; char conceito; }; © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Estruturas (Structs) em C #include<iostream> #include<string> struct Aluno { string nome; int matricula; char conceito; }; main() { struct Aluno al, aux; al.nome = “Pedro” al.matricula = 200712; al.conceito = ‘A’; aux = al; } Pedro 200712 A al: Pedro 200712 A aux: © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Estruturas (Structs) em C struct Aluno { string nome; int matricula; char conceito; }; struct Professor{ string nome; int matricula; string classes[3]; }; main() { struct Aluno al; struct Professor pr; al.nome = “Pedro”; pr.nome = “José”; } main() { string alunoNome; int alunoMatricula; Char alunoConceito; string alunoNome2; int alunoMatricula2; Char alunoConceito2; string professorNome; int professorMatricula; string professoClasses[3]; alunoNome = “Pedro” alunoMatricula = 200712; alunoConceito = ‘A’; alunoNome2 = alunoNome; alunoMatricula2 = alunoMatricula; alunoConceito2 = alunoConceito; } © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Declaração de Tipos n Para simplificar, uma estrutura ou mesmo outros tipos de dados podem ser definidos como um novo tipo n Uso da construção typedef n Sintaxe: typedef tipo identificador; typedef struct { string nome; int matricula; char conceito; } TipoAluno; typedef int Vetor[10]; int main() { TipoAluno al; Vetor v; ... } © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II TADs em C n Para implementar um Tipo Abstrato de Dados em C, usa-se a definição de tipos juntamente com a implementação de funções que agem sobre aquele tipo n Como boa regra de programação, evita-se acessar o dado diretamente, fazendo o acesso só através das funções q Mas, diferentemente de C++ e Java, não há uma forma de proibir o acesso. © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II n Uma boa técnica de programação é implementar os TADs em arquivos separados do programa principal n Para isso geralmente separa-se a declaração e a implementação do TAD em dois arquivos: q NomeDoTAD.h : com a declaração q NomeDoTAD.c : com a implementação n O programa ou outros TADs que utilizam o seu TAD devem dar um #include no arquivo .h TADs em C © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. TADs em C Algoritmos e Estrutura de Dados II Aplicação Especificação Implementação Usuário do TAD Programador do TAD arquivo .c arquivo .h © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Exemplo n Implemente um TAD ContaBancaria, com os campos número e saldo onde os clientes podem fazer as seguintes operações: q Iniciar uma conta com um número e saldo inicial q Depositar um valor q Sacar um valor q Imprimir o saldo n Faça um pequeno programa para testar o seu TAD © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II ContaBancaria.h // definição do tipo typedef struct { int numero; double saldo; } ContaBancaria; // cabeçalho das funções ContaBancaria Inicializa (int, double); void Deposito (ContaBancaria *, double); void Saque (ContaBancaria *, double); void Imprime (ContaBancaria); © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II ContaBancaria.c #include<stdio.h> #include"Contabancaria.h" ContaBancaria Inicializa(int numero, double saldo) { ContaBancaria conta; conta.numero = numero; conta.saldo = saldo; return conta; } void Deposito (ContaBancaria *conta, double valor) { conta->saldo += valor; } void Saque (ContaBancaria *conta, double valor) { conta->saldo -= valor; } void Imprime (ContaBancaria conta) { printf("Numero: %d – saldo: %f\n", conta.numero, conta.saldo); } © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Main.c #include<stdio.h> #include<stdlib.h> #include "ContaBancaria.h" int main (void) { ContaBancaria conta1; conta1 = Inicializa(918556, 300.00); printf("\nAntes da movimentacao:\n "); Imprime(conta1); Deposito(&conta1, 50.00); Saque(&conta1, 70.00); printf("\nDepois da movimentacao:\n "); Imprime(conta1); return(0); } © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. TADs em C Algoritmos e Estrutura de Dados II Aplicação Especificação Implementação Usuário do TAD Programador do TAD ContaBancaria.c ContaBancaria.h main.c © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. TADs em C n Acesso direto dos dados (errado!) Algoritmos e Estrutura de Dados II 20 13 02 30 Implementação por Vetores: typedef struct { int dado[100]; } Lista; 20 13 02 30 Implementação por Listas Encadeadas typedef struct item { int dado; struct item *prox; } Item; typedef struct { Item *inicio; } Lista; Programa usuário do TAD: int main() { Lista L; L.dado[0] = 20; } © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Exercício n Implemente um TAD Número Complexo q cada número possui os campos real e imaginário q Implemente as operações: q Inicializa: atribui valores para os campos q Imprime: imprime o número da forma “R + Ci” q Copia: Copia o valor de um número para outro q Soma: Soma dois números complexos q EhReal: testa se um número é real n Faça um pequeno programa para testar o seu TAD © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II NumeroComplexo.h typedef struct { int real; int img; } NumeroComplexo ; NumeroComplexo Inicializa(int, int); void Imprime(NumeroComplexo); void Copia(NumeroComplexo*, NumeroComplexo); NumeroComplexo Soma(NumeroComplexo, NumeroComplexo); int EhReal(NumeroComplexo); ! ! © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II NumeroComplexo.c #include <stdio.h> #include "NumeroComplexo.h" NumeroComplexo Inicializa(int real, int img) { NumeroComplexo num; num.real = real; num.img = img; return num; } void Imprime(NumeroComplexo num) { printf("%d + %di\n", num.real, num.img); } ! © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II NumeroComplexo.cpp NumeroComplexo Soma(NumeroComplexo a, NumeroComplexo b) { NumeroComplexo temp; temp.real = a.real + b.real; temp.img = a.img + b.img; return temp; } int EhReal(NumeroComplexo num) { return num.img == 0; } void Copia(NumeroComplexo *dest, NumeroComplexo src) { dest->real = src.real; dest->img = src.img; } ! © R. O. Prates, L. Chaimowicz e Gisele L. Pappa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II #include <stdio.h> #include <stdlib.h> #include "NumeroComplexo.h" int main() { NumeroComplexo a,b,c,d; a = Inicializa(2,5); Imprime(a); b = Inicializa(1,2); Imprime(b); c = Soma(a,b); Imprime(c); d = Inicializa(5,0); Imprime(d); if (EhReal(d)) Copia(&d,a); Imprime(d); return 0; }! AEDSII_aula_004_linguagem_C-vetores_e_strings.pdf VETORES E STRINGS Alocação está-ca de memória • Ao se declarar uma variável qualquer, o compilador deixa reservado um espaço suficiente na memória para armazená-‐la int a; // 4 bytes float x; // 4 bytes double y; // 8 bytes char c; // 1 byte char *c; // 4 bytes Alocação está-ca de memória • Ao fazer a alocação está-ca, apenas o espaço necessário na memória é reservado • O conteúdo de cada posição não é alterado, e portanto uma variável apenas declarada pode conter qualquer coisa • Inicializar as variáveis, atribuindo valores, antes do uso – Inclusive vetores, matrizes e strings Vetores • Quando se declara um vetor, o valor entre colchetes indica quantas vezes o espaço de memória necessário para o -po básico será alocado char v[100]; // 100 * sizeof(char) = 100 bytes int v[100]; // 100 * sizeof(int) = 400 bytes float vf[200]; // 200 * sizeof(float) = 800 bytes double z[1000]; // 1000 * sizeof(double) = 8000 bytes Pode-‐se u-lizar: sizeof(v) Pode ser u-lizado para descobrir o tamanho de uma string? Vetores • A referência a uma posição de um vetor indica o cálculo de uma posição de memória a par-r do início do vetor float x[1000]; // x[20] está na posição x + 20*sizeof(float) • Na verdade, o símbolo x é um apontador para o início da região de memória reservada para o vetor Vetores • C NÃO AVISA NEM PRODUZ ERRO QUANDO O LIMITE DE UM VETOR OU MATRIZ FOR EXCEDIDO float x[1000]; y = x[2000]; // não dá erro, mas vai acessar // uma parte inesperada da memória – Erro mais comum (run-me): segmentation fault • É responsabilidade do programador verificar os limites, e garan-r que não sejam excedidos Matrizes = Vetores de mais de uma dimensão • Na verdade, a alocação na memória é linear • Muda apenas o cálculo da posição de memória do elemento referenciado int M[5][5]; ... M[0][0] = 15; M[2][3] = 2; // posicao: M + (2*5 + 3)*sizeof(int) Matrizes a b c d e f g h i j k l m n o char M[3][5]; v = M[1][4]; M a b c d e f g h i j k l m n o M[1][4] M[1][4] char *M; M = (char *) malloc(3 * 5 * sizeof(char)); // acesso a linha i e coluna j em matriz l x c // v = M[i * c + j] v = M[1 * 5 + 4]; Strings • Um string é um vetor do -po char • Para manipulação do string, atribuindo e recuperando valores, e realizando operações sobre ele (funções no string.h), é importante entender essa caracterís-ca • Quando o conteúdo de um string não ocupa todo o espaço disponível, usa-‐se um caractere ‘\0’ (ou NULL, código ASCII 0) como delimitador Strings • Strings constantes aparecem no código entre aspas printf(“%s”, “Bom dia!”); • O delimitador \0 está incluído: • Tamanho da string: size_t strlen(char *str); Strings • Não é possível fazer atribuições diretas para strings • Usar a função strcpy ou a função strncpy char *strcpy(char *DST, char *SRC); Strings • Tamanho da string (não conta o \0): size_t strlen(char *str); char s[10]; int tamanho; strcpy(s, “Bom dia!\n”); tamanho = strlen(s); // 9 tamanho = sizeof(s); // 10 (Não usar!) Strings • Na inicialização, pode-‐se usar o mesmo recurso disponível para vetores • char nome[] = {‘A’, ‘n’, ‘a’, ‘\0’}; Ou • char nome[] = “Ana”; Strings • Como o nome do string representa o endereço onde começa o string, é possível manipulá-‐lo diretamente – Cuidado! • Exemplo char nome[] = “Alexandre”; printf(“%s\n”, nome + 3); // imprime “xandre” Strings • Funções – strcat(s1, s2): concatena o s2 no s1 (s1 tem que ter espaço) – strcmp(s1, s2): retorna – < 0 se s1 é menor que s2, – 0 se s1 é igual a s2 – > 0 se s1 é maior que s2 (ordem alfabé-ca) • A comparação entre strings também tem que ser feita caractere a caractere, • Não se pode usar s1==s2; isso só compara os endereços Strings • Comparação de strings char str1[] = "abcd "; char str2[] = "abcd "; int cmp; cmp = strcmp(str1, str2); // retorna 0 str2[4] = 'e'; str1[4] = 'a'; cmp = strcmp(str1, str2); // retorna -1 str2[4] = 0; cmp = strcmp(str1, str2); // retorna 1 Strings • Impressão para uma string (sprinj) char str1[10]; sprintf(str1, “%d“, 33); Strings • strncat, strncmp, strncpy: idem aos anteriores, mas especifica o número de caracteres que serão considerados Strings • strtok: extrai “tokens”, substrings delimitados char *strtok(char *str, char *delim); Exemplo: str = “algumas palavras”; delim = “ \n”; token = strtok(str, delim); printf(“%s\n”, token); // stdout: algumas token = strtok(NULL, delim); printf(“%s\n”, token); // stdout: palavras // memória: str: algumas\0palavras\0 Strings • Exemplo int num = 0; char linha[256]; ... while (!feof(infile)) { fgets(linha, 256, infile); p1 = strtok(linha, " \n"); //delim: branco ou fim de linha while ((p1 != NULL) && (!feof(infile))) { num++; printf("%s\n", p1); p1 = strtok(NULL, " \n"); } } Strings • Vetores de strings podem ser criados • Exemplo char DiaSemana[7][14] = {“Domingo”, “Segunda”, “Terça”, “Quarta”, “Quinta”, “Sexta”, “Sabado”}; ... printf(“%s\n”, DiaSemana[3]); Algoritmos e Estrutura de Dados II Passagem de Parâmetros • Em C++, parâmetros para função podem ser passados por valor ou por referência – Por valor: o parâmetro formal (recebido no procedimento) é uma cópia do parâmetro real (passado na chamada) – Por referência: o parâmetro formal (recebido no procedimento) é uma referência para o parâmetro real (passado na chamada) • As modificações efetuadas acontecem no parâmetro real • Em C só existe passagem por valor, logo deve-‐se implementar a passagem por referência u-lizando-‐se apontadores Algoritmos e Estrutura de Dados II Passagem de Parâmetros (C) void SomaUm(int x, int *y) { x = x + 1; *y = (*y) + 1; printf("Funcao SomaUm: %d %d\n", x, *y); } int main() { int a=0, b=0; SomaUm(a, &b); printf("Programa principal: %d %d\n", a, b); } 1 1 0 1 Passagem de parâmetros para o programa • Chamada: C:\> prog.exe arg1 arg2 arg3 • Declaração completa da função main int main(int argc, char *argv[]) • argc: número de parâmetros passados na linha de comando • argv: um vetor de argc strings – argv[0]: nome do programa – argv[1]: primeiro parâmetro – argv[argc – 1]: úl-mo parâmetro – argv[argc] é sempre NULL AEDSII_aula_005_analise_de_complexidade.pdf Medida do Tempo de Execução de um Programa Livro “Projeto de Algoritmos” – Nívio Ziviani Capítulo 1 – Seção 1.3 http://www2.dcc.ufmg.br/livros/algoritmos/ Algoritmos e Estrutura de Dados II Projeto de Algoritmos n Projeto de algoritmos 1. Análise do problema 2. Decisões de projeto 3. Algoritmo a ser utilizado de acordo com seu comportamento. n Comportamento depende de n tempo de execução n espaço ocupado. Algoritmos e Estrutura de Dados II Análise de Algoritmos n Análise de um algoritmo particular. q Qual é o custo de usar um dado algoritmo para resolver um problema específico? Algoritmos e Estrutura de Dados II Tipos de Problemas na Análise de Algoritmos n Análise de um algoritmo particular. q Qual é o custo de usar um dado algoritmo para resolver um problema específico? q Características que devem ser investigadas: q Análise do número de vezes que cada parte do algoritmo deve ser executada, q Estudo da quantidade de memória necessária. Algoritmos e Estrutura de Dados II Tipos de Problemas na Análise de Algoritmos n Análise de uma classe de algoritmos. q Qual é o algoritmo de menor custo possível para resolver um problema particular? q Toda uma família de algoritmos é investigada. q Procura-se identificar um que seja o melhor possível. q Coloca-se limites para a complexidade computacional dos algoritmos pertencentes à classe. Algoritmos e Estrutura de Dados II Custo de um Algoritmo n Determinando o menor custo possível para resolver problemas de uma dada classe, temos a medida da dificuldade inerente para resolver o problema. n Quando o custo de um algoritmo é igual ao menor custo possível, o algoritmo é ótimo para a medida de custo considerada. n Podem existir vários algoritmos para resolver o mesmo problema. n Se a mesma medida de custo é aplicada a diferentes algoritmos, então é possível compará-los e escolher o mais adequado. Algoritmos e Estrutura de Dados II Medida do Custo pela Execução do Programa n Medidas são bastante inadequadas : q os resultados são dependentes do compilador; q os resultados dependem do hardware; q quando grandes quantidades de memória são utilizadas, as medidas de tempo podem depender deste aspecto. n Apesar disso, há argumentos a favor de se obterem medidas reais de tempo. q Ex.: quando há vários algoritmos distintos para resolver um mesmo tipo de problema, todos com um custo de execução dentro de uma mesma ordem de grandeza. q Nesse caso, tanto os custos reais das operações como os custos não aparentes, tais como alocação de memória, indexação, carga, são considerados. Algoritmos e Estrutura de Dados II Medida do Custo por meio de um Modelo Matemático n Usa um modelo matemático baseado em um computador idealizado. n Deve ser especificado o conjunto de operações e seus custos de execuções. n É mais usual ignorar o custo de algumas das operações e considerar apenas as operações mais significativas. n Ex.: algoritmos de ordenação. Consideramos o número de comparações entre os elementos do conjunto a ser ordenado e ignoramos as operações aritméticas, de atribuição e manipulações de índices, caso existam. Algoritmos e Estrutura de Dados II Função de Complexidade n O custo de execução de um algoritmo é dado por função de custo ou função de complexidade f. n f(n) é a medida do tempo necessário para executar um algoritmo para um problema de tamanho n. n Função de complexidade de tempo: n f(n) mede o tempo necessário para executar um algoritmo em um problema de tamanho n. n Função de complexidade de espaço n f(n) mede a memória necessária para executar um algoritmo em um problema de tamanho n. Algoritmos e Estrutura de Dados II Função de Complexidade n Nas aulas, f denota uma função de complexidade de tempo n Apesar do nome, ela não representa tempo diretamente n Representa o número de vezes que determinada operação considerada relevante é executada. Algoritmos e Estrutura de Dados II Exemplo: maior elemento n Considere o algoritmo para encontrar o maior elemento de um vetor de inteiros A[n]; n ≥ 1. n Seja f uma função de complexidade tal que f(n) é o número de comparações entre os elementos de A, se A contiver n elementos. n Qual a função f(n)? int Max(int A[n]) { int i, Temp; Temp = A[0]; for (i = 1; i < n; i++) if (Temp < A[i]) Temp = A[i]; return Temp; } Algoritmos e Estrutura de Dados II Exemplo: maior elemento n Considere o algoritmo para encontrar o maior elemento de um vetor de inteiros A[n]; n ≥ 1. n Seja f uma função de complexidade tal que f(n) é o número de comparações entre os elementos de A, se A contiver n elementos. n Logo f(n) = n -1 int Max(int A[n]) { int i, Temp; Temp = A[0]; for (i = 1; i < n; i++) if (Temp < A[i]) Temp = A[i]; return Temp; } Algoritmos e Estrutura de Dados II Exemplo: maior elemento n Teorema: Qualquer algoritmo para encontrar o maior elemento de um conjunto com n elementos, n ≥ 1, faz pelo menos n -1 comparações. Algoritmos e Estrutura de Dados II Exemplo: maior elemento n Teorema: Qualquer algoritmo para encontrar o maior elemento de um conjunto com n elementos, n ≥ 1, faz pelo menos n -1 comparações. n Prova: Cada um dos n - 1 elementos tem de ser testado, por meio de comparações, se é menor do que algum outro elemento. Algoritmos e Estrutura de Dados II Exemplo: maior elemento n Teorema: Qualquer algoritmo para encontrar o maior elemento de um conjunto com n elementos, n ≥ 1, faz pelo menos n -1 comparações. n Prova: Cada um dos n - 1 elementos tem de ser testado, por meio de comparações, se é menor do que algum outro elemento. q Logo, n-1 comparações são necessárias Algoritmos e Estrutura de Dados II Exemplo: maior elemento n Teorema: Qualquer algoritmo para encontrar o maior elemento de um conjunto com n elementos, n ≥ 1, faz pelo menos n -1 comparações. n Prova: Cada um dos n - 1 elementos tem de ser testado, por meio de comparações, se é menor do que algum outro elemento. q Logo, n-1 comparações são necessárias O teorema acima nos diz que, se o número de comparações for utilizado como medida de custo, então a função Max do programa anterior é ótima. Algoritmos e Estrutura de Dados II Tamanho da Entrada de Dados n A medida do custo de execução de um algoritmo depende principalmente do tamanho da entrada dos dados. n Para alguns algoritmos, o custo de execução é uma função da entrada particular dos dados, não apenas do tamanho da entrada. Algoritmos e Estrutura de Dados II Tamanho da Entrada de Dados n A medida do custo de execução de um algoritmo depende principalmente do tamanho da entrada dos dados. n Para alguns algoritmos, o custo de execução é uma função da entrada particular dos dados, não apenas do tamanho da entrada. n No caso da função Max do programa do exemplo, o custo é uniforme sobre todos os problemas de tamanho n. n Para um algoritmo de ordenação isso não ocorre Algoritmos e Estrutura de Dados II Tamanho da Entrada de Dados n A medida do custo de execução de um algoritmo depende principalmente do tamanho da entrada dos dados. n Para alguns algoritmos, o custo de execução é uma função da entrada particular dos dados, não apenas do tamanho da entrada. n No caso da função Max do programa do exemplo, o custo é uniforme sobre todos os problemas de tamanho n. n Para um algoritmo de ordenação isso não ocorre n se os dados de entrada já estiverem quase ordenados, então o algoritmo pode ter que trabalhar menos. Algoritmos e Estrutura de Dados II Melhor Caso, Pior Caso e Caso Médio n Melhor caso: menor tempo de execução sobre todas as entradas de tamanho n. n Pior caso: maior tempo de execução sobre todas as entradas de tamanho n. q Se f é uma função de complexidade baseada na análise de pior caso, o custo de aplicar o algoritmo nunca é maior do que f(n). n Caso médio (ou caso esperado): média dos tempos de execução de todas as entradas de tamanho n. Algoritmos e Estrutura de Dados II Análise de Melhor Caso, Pior Caso e Caso Médio n Na análise do caso médio esperado, supõe-se uma distribuição de probabilidades sobre o conjunto de entradas de tamanho n e o custo médio é obtido com base nessa distribuição. n A análise do caso médio é geralmente muito mais difícil de obter do que as análises do melhor e do pior caso. n É comum supor uma distribuição de probabilidades em que todas as entradas possíveis são igualmente prováveis. n Na prática isso nem sempre é verdade. Algoritmos e Estrutura de Dados II Exemplo - Registros de um Arquivo n Considere o problema de acessar os registros de um arquivo. n Cada registro contém uma chave única que é utilizada para recuperar registros do arquivo. n O problema: dada uma chave qualquer, localize o registro que contenha esta chave. n O algoritmo de pesquisa mais simples é o que faz a pesquisa sequencial. Algoritmos e Estrutura de Dados II Exemplo - Registros de um Arquivo n Seja f uma função de complexidade tal que f(n) é o número de registros consultados no arquivo (número de vezes que a chave de consulta é comparada com a chave de cada registro). q melhor caso: q pior caso: q caso médio: Algoritmos e Estrutura de Dados II Exemplo - Registros de um Arquivo n Seja f uma função de complexidade tal que f(n) é o número de registros consultados no arquivo (número de vezes que a chave de consulta é comparada com a chave de cada registro). q melhor caso: q registro procurado é o primeiro consultado q pior caso: q caso médio: Algoritmos e Estrutura de Dados II Exemplo - Registros de um Arquivo n Seja f uma função de complexidade tal que f(n) é o número de registros consultados no arquivo (número de vezes que a chave de consulta é comparada com a chave de cada registro). q melhor caso: q registro procurado é o primeiro consultado q f(n) = 1 q pior caso: q caso médio: Algoritmos e Estrutura de Dados II Exemplo - Registros de um Arquivo n Seja f uma função de complexidade tal que f(n) é o número de registros consultados no arquivo (número de vezes que a chave de consulta é comparada com a chave de cada registro). q melhor caso: q registro procurado é o primeiro consultado q f(n) = 1 q pior caso: q registro procurado é o último consultado ou não está presente no arquivo; q caso médio: Algoritmos e Estrutura de Dados II Exemplo - Registros de um Arquivo n Seja f uma função de complexidade tal que f(n) é o número de registros consultados no arquivo (número de vezes que a chave de consulta é comparada com a chave de cada registro). q melhor caso: q registro procurado é o primeiro consultado q f(n) = 1 q pior caso: q registro procurado é o último consultado ou não está presente no arquivo; q f(n) = n q caso médio: Algoritmos e Estrutura de Dados II Exemplo - Registros de um Arquivo n No estudo do caso médio, vamos considerar que toda pesquisa recupera um registro. n Se pi for a probabilidade de que o i-ésimo registro seja procurado, e considerando que para recuperar o i-ésimo registro são necessárias i comparações, então: f(n) = 1 x p1 + 2 x p2 + 3 x p3 + ... + n x pn Algoritmos e Estrutura de Dados II Exemplo - Registros de um Arquivo n Para calcular f(n) basta conhecer a distribuição de probabilidades pi. n Se cada registro tiver a mesma probabilidade de ser acessado que todos os outros, então pi = 1/n, 1 ≤ i ≤ n Algoritmos e Estrutura de Dados II Exemplo - Registros de um Arquivo n Para calcular f(n) basta conhecer a distribuição de probabilidades pi. n Se cada registro tiver a mesma probabilidade de ser acessado que todos os outros, então n Nesse caso: n A análise do caso esperado revela que uma pesquisa com sucesso examina aproximadamente metade dos registros. pi = 1/n, 1 ≤ i ≤ n Algoritmos e Estrutura de Dados II Exemplo - Registros de um Arquivo n Seja f uma função de complexidade tal que f(n) é o número de registros consultados no arquivo (número de vezes que a chave de consulta é comparada com a chave de cada registro). q melhor caso: q f(n) = 1 q pior caso: q f(n) = n q caso médio: q f(n) = (n + 1)/2. Algoritmos e Estrutura de Dados II Exemplo - Maior e Menor Elemento (1) n Problema: encontrar o maior e o menor elemento de um vetor de inteiros A[n]; n ≥ 1. Algoritmos e Estrutura de Dados II Exemplo - Maior e Menor Elemento (1) n Problema: encontrar o maior e o menor elemento de um vetor de inteiros A[n]; n ≥ 1. n Um algoritmo simples pode ser derivado do algoritmo apresentado no programa para achar o maior elemento. void MaxMin1(int A[n], int *Max, int *Min) { int i; *Max = A[0]; *Min = A[0]; for (i = 1; i < n; i++) { if (A[i] > *Max) *Max = A[i]; if (A[i] < *Min) *Min = A[i]; } } Algoritmos e Estrutura de Dados II Qual a função de complexidade para MaxMin1? void MaxMin1(int A[n], int *Max, int *Min) { int i; *Max = A[0]; *Min = A[0]; for (i = 1; i < n; i++) { if (A[i] > *Max) *Max = A[i]; if (A[i] < *Min) *Min = A[i]; } } 2*(n-1) Algoritmos e Estrutura de Dados II Qual a função de complexidade para MaxMin1? n Seja f(n) o número de comparações entre os elementos de A, se A contiver n elementos. n Logo f(n) = 2(n-1) para n > 1, para o melhor caso, pior caso e caso médio. void MaxMin1(int A[n], int *Max, int *Min) { int i; *Max = A[0]; *Min = A[0]; for (i = 1; i < n; i++) { if (A[i] > *Max) *Max = A[i]; if (A[i] < *Min) *Min = A[i]; } } Algoritmos e Estrutura de Dados II Exemplo - Maior e Menor Elemento (2) n MaxMin1 pode ser facilmente melhorado: a comparação A[i] < Min só é necessária quando a comparação A[i] > Max dá falso. void MaxMin2(int A[n], int *Max, int *Min) { int i; *Max = A[0]; *Min = A[0]; for (i = 1; i < n; i++) { if (A[i] > *Max) *Max = A[i]; else if (A[i] < *Min) *Min = A[i]; } } Algoritmos e Estrutura de Dados II Qual a função de complexidade para MaxMin2? void MaxMin2(int A[n], int *Max, int *Min) { int i; *Max = A[0]; *Min = A[0]; for (i = 1; i < n; i++) { if (A[i] > *Max) *Max = A[i]; else if (A[i] < *Min) *Min = A[i]; } } Algoritmos e Estrutura de Dados II Qual a função de complexidade para MaxMin2? Melhor caso: Pior caso: Caso médio: void MaxMin2(int A[n], int *Max, int *Min) { int i; *Max = A[0]; *Min = A[0]; for (i = 1; i < n; i++) { if (A[i] > *Max) *Max = A[i]; else if (A[i] < *Min) *Min = A[i]; } } Algoritmos e Estrutura de Dados II Qual a função de complexidade para MaxMin2? void MaxMin2(int A[n], int *Max, int *Min) { int i; *Max = A[0]; *Min = A[0]; for (i = 1; i < n; i++) { if (A[i] > *Max) *Max = A[i]; else if (A[i] < *Min) *Min = A[i]; } } Melhor caso: q quando os elementos estão em ordem crescente; Pior caso: Caso médio: Algoritmos e Estrutura de Dados II Qual a função de complexidade para MaxMin2? void MaxMin2(int A[n], int *Max, int *Min) { int i; *Max = A[0]; *Min = A[0]; for (i = 1; i < n; i++) { if (A[i] > *Max) *Max = A[i]; else if (A[i] < *Min) *Min = A[i]; } } Melhor caso: q quando os elementos estão em ordem crescente; q f(n) = n – 1 Pior caso: Caso médio: (n-1) Algoritmos e Estrutura de Dados II Qual a função de complexidade para MaxMin2? void MaxMin2(int A[n], int *Max, int *Min) { int i; *Max = A[0]; *Min = A[0]; for (i = 1; i < n; i++) { if (A[i] > *Max) *Max = A[i]; else if (A[i] < *Min) *Min = A[i]; } } Melhor caso: q quando os elementos estão em ordem crescente; q f(n) = n – 1 Pior caso: q quando os elementos estão em ordem decrescente; Caso médio: Algoritmos e Estrutura de Dados II Qual a função de complexidade para MaxMin2? void MaxMin2(int A[n], int *Max, int *Min) { int i; *Max = A[0]; *Min = A[0]; for (i = 1; i < n; i++) { if (A[i] > *Max) *Max = A[i]; else if (A[i] < *Min) *Min = A[i]; } } Melhor caso: q quando os elementos estão em ordem crescente; q f(n) = n – 1 Pior caso: q quando os elementos estão em ordem decrescente; q f(n) = 2(n – 1) Caso médio: (n-1) (n-1) Algoritmos e Estrutura de Dados II Qual a função de complexidade para MaxMin2? void MaxMin2(int A[n], int *Max, int *Min) { int i; *Max = A[0]; *Min = A[0]; for (i = 1; i < n; i++) { if (A[i] > *Max) *Max = A[i]; else if (A[i] < *Min) *Min = A[i]; } } Melhor caso: q quando os elementos estão em ordem crescente; q f(n) = n – 1 Pior caso: q quando os elementos estão em ordem decrescente; q f(n) = 2(n – 1) Caso médio: q No caso médio, A[i] é maior do que Max a metade das vezes. Algoritmos e Estrutura de Dados II Qual a função de complexidade para MaxMin2? void MaxMin2(int A[n], int *Max, int *Min) { int i; *Max = A[0]; *Min = A[0]; for (i = 1; i < n; i++) { if (A[i] > *Max) *Max = A[i]; else if (A[i] < *Min) *Min = A[i]; } } Melhor caso: q quando os elementos estão em ordem crescente; q f(n) = n – 1 Pior caso: q quando os elementos estão em ordem decrescente; q f(n) = 2(n – 1) Caso médio: q No caso médio, A[i] é maior do que Max a metade das vezes. q f(n) = n – 1 + (n – 1)/2 = 3n/2 – 3/2 (n-1) (n-1)/2 Algoritmos e Estrutura de Dados II Exemplo - Maior e Menor Elemento (3) n Podemos fazer melhor ainda para encontrar o mínimo e o máximo? 10 30 5 68 12 67 22 11 . . . Algoritmos e Estrutura de Dados II Exemplo - Maior e Menor Elemento (3) n Considerando o número de comparações realizadas, existe a possibilidade de obter um algoritmo mais eficiente: 1. Compare os elementos de A aos pares, separando-os em dois subconjuntos (maiores em um e menores em outro), a um custo de ⎡n/2⎤ comparações. 2. O máximo é obtido do subconjunto que contém os maiores elementos, a um custo de ⎡n/2⎤ -1 comparações 3. O mínimo é obtido do subconjunto que contém os menores elementos, a um custo de ⎡n/2⎤ -1 comparações 10 30 5 68 12 67 22 11 . . . Algoritmos e Estrutura de Dados II Exemplo - Maior e Menor Elemento (3) 10 30 5 68 12 67 22 11 . . . Algoritmos e Estrutura de Dados II Exemplo - Maior e Menor Elemento (3) 10 30 5 68 12 67 22 11 . . . 10 30 5 12 68 67 22 11 Algoritmos e Estrutura de Dados II Qual a função de complexidade para este novo algoritmo? n Os elementos de A são comparados dois a dois. Os elementos maiores são comparados com Max e os elementos menores são comparados com Min. n Quando n é ímpar, o elemento que está na posição A[n-1] é duplicado na posição A[n] para evitar um tratamento de exceção. n Para esta implementação: no pior caso, melhor caso e caso médio Algoritmos e Estrutura de Dados II Exemplo - Maior e Menor Elemento (3) void MaxMin3(int n, Vetor A, int *Max, int *Min) { int i, FimDoAnel; if ((n % 2) > 0) { A[n] = A[n - 1]; FimDoAnel = n; } else FimDoAnel = n - 1; if (A[0] > A[1]) { *Max = A[0]; *Min = A[1]; } else { *Max = A[1]; *Min = A[0]; } i = 3; while (i <= FimDoAnel) { if (A[i - 1] > A[i]) { if (A[i - 1] > *Max) *Max = A[i - 1]; if (A[i] < *Min) *Min = A[i]; } else { if (A[i - 1] < *Min) *Min = A[i - 1]; if (A[i] > *Max) *Max = A[i]; } i += 2; } } Algoritmos e Estrutura de Dados II Exemplo - Maior e Menor Elemento (3) void MaxMin3(int n, Vetor A, int *Max, int *Min) { int i, FimDoAnel; if ((n % 2) > 0) { A[n] = A[n - 1]; FimDoAnel = n; } else FimDoAnel = n - 1; if (A[0] > A[1]) { *Max = A[0]; *Min = A[1]; } else { *Max = A[1]; *Min = A[0]; } i = 3; while (i <= FimDoAnel) { if (A[i - 1] > A[i]) { if (A[i - 1] > *Max) *Max = A[i - 1]; if (A[i] < *Min) *Min = A[i]; } else { if (A[i - 1] < *Min) *Min = A[i - 1]; if (A[i] > *Max) *Max = A[i]; } i += 2; } } 10 30 5 68 12 67 . . . Algoritmos e Estrutura de Dados II Exemplo - Maior e Menor Elemento (3) void MaxMin3(int n, Vetor A, int *Max, int *Min) { int i, FimDoAnel; if ((n % 2) > 0) { A[n] = A[n - 1]; FimDoAnel = n; } else FimDoAnel = n - 1; if (A[0] > A[1]) { *Max = A[0]; *Min = A[1]; } else { *Max = A[1]; *Min = A[0]; } i = 3; while (i <= FimDoAnel) { if (A[i - 1] > A[i]) { if (A[i - 1] > *Max) *Max = A[i - 1]; if (A[i] < *Min) *Min = A[i]; } else { if (A[i - 1] < *Min) *Min = A[i - 1]; if (A[i] > *Max) *Max = A[i]; } i += 2; } } Comparação 1 Comparação 2 Comparação 3 Comparação 4 Comparação 3 Comparação 4 10 30 5 68 12 67 . . . 1 (n/2) - 1 (n/2) - 1 (n/2) - 1 Algoritmos e Estrutura de Dados II Qual a função de complexidade para MaxMin3? n Quantas comparações são feitas em MaxMin3? Algoritmos e Estrutura de Dados II Qual a função de complexidade para MaxMin3? n Quantas comparações são feitas em MaxMin3? q 1ª. comparação feita 1 vez q 2ª. comparação feita n/2 - 1 vezes q 3ª. e 4ª. comparações feitas n/2 – 1 vezes Algoritmos e Estrutura de Dados II Qual a função de complexidade para MaxMin3? n Quantas comparações são feitas em MaxMin3? q 1ª. comparação feita 1 vez q 2ª. comparação feita n/2 - 1 vezes q 3ª. e 4ª. comparações feitas n/2 – 1 vezes f(n) = 1 + n/2 – 1 + 2 * (n/2 – 1) f(n) = (3n – 6)/2 + 1 f(n) = 3n/2 – 3 + 1 = 3n/2 - 2 Algoritmos e Estrutura de Dados II Comparação entre os Algoritmos n A tabela apresenta uma comparação entre os algoritmos dos programas MaxMin1, MaxMin2 e MaxMin3, considerando o número de comparações como medida de complexidade. n Os algoritmos MaxMin2 e MaxMin3 são superiores ao algoritmo MaxMin1 de forma geral. n O algoritmo MaxMin3 é superior ao algoritmo MaxMin2 com relação ao pior caso e bastante próximo quanto ao caso médio. Algoritmos e Estrutura de Dados II Exemplo n Qual é a função de complexidade f(n) para o algoritmo abaixo? Void funcao(int A[n], int B[n]) { int i, j; for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { if (A[i] > B[j]) A[i] = A[i] + B[j]; else B[j] = B[j] – A[i]; } } } Algoritmos e Estrutura de Dados II Limite Inferior - Uso de um Oráculo n Existe possibilidade de obter um algoritmo MaxMin mais eficiente? n Para responder temos de conhecer o limite inferior para essa classe de algoritmos. n Como? Uso de um oráculo. n Dado um modelo de computação que expresse o comportamento do algoritmo, o oráculo informa o resultado de cada passo possível (no caso, o resultado de cada comparação). n Para derivar o limite inferior, o oráculo procura sempre fazer com que o algoritmo trabalhe o máximo, escolhendo como resultado da próxima comparação aquele que cause o maior trabalho possível necessário para determinar a resposta final. Algoritmos e Estrutura de Dados II Exemplo de Uso de um Oráculo n Teorema: Qualquer algoritmo para encontrar o maior e o menor elemento de um conjunto com n elementos não ordenados, n>1, faz pelo menos 3n/2 - 2 comparações. n Prova: A técnica utilizada define um oráculo que descreve o comportamento do algoritmo utilizando: n um conjunto de n–tuplas, n um conjunto de regras associadas que mostram as tuplas possíveis (estados) que um algoritmo pode assumir a partir de uma dada tupla e uma única comparação. Algoritmos e Estrutura de Dados II Exemplo de Uso de um Oráculo n Para o problema do maior e menor elemento, utilizamos uma 4–tupla, representada por (a; b; c; d), onde os elementos de: q a: número de elementos nunca comparados; q b: foram vencedores e nunca perderam em comparações realizadas (máximo); q c: foram perdedores e nunca venceramem comparações realizadas (mínimo); q d: foram vencedores e perdedores em comparações realizadas (elementos intermediários). n O algoritmo inicia no estado (n, 0, 0, 0) e termina com (0, 1, 1, n - 2). Algoritmos e Estrutura de Dados II Exemplo de Uso de um Oráculo n Após cada comparação, (a; b; c; d) assume um dentre os 6 estados possíveis abaixo: q (a - 2, b + 1, c + 1, d) se a ≥ 2 (2 elementos de a são comparados) q (a - 1, b + 1, c, d) ou (a - 1, b, c + 1, d) ou (a - 1, b, c, d + 1) se a ≥ 1 (1 elemento de a comparado com 1 de b ou 1 de c) q (a, b - 1, c, d + 1) se b ≥ 2 (2 elementos de b são comparados) q (a, b, c - 1, d + 1) se c ≥ 2 (2 elementos de c são comparados) Algoritmos e Estrutura de Dados II Exemplo de Uso de um Oráculo (a, b, c, d) (n, 0, 0, 0) (0, 1, 1, n-2) Algoritmos e Estrutura de Dados II Exemplo de Uso de um Oráculo (a, b, c, d) (n, 0, 0, 0) comparação de 2 a 2 elementos de a (caminho mais rápido para zerar a). (0, n/2, n/2, 0) (0, 1, 1, n-2) Algoritmos e Estrutura de Dados II Exemplo de Uso de um Oráculo (a, b, c, d) (n, 0, 0, 0) comparação de 2 a 2 elementos de a (caminho mais rápido para zerar a). (0, n/2, n/2, 0) comparação de elementos em b para encontrar o máximo (0, 1, n/2, n/2-1) (0, 1, 1, n-2) Algoritmos e Estrutura de Dados II Exemplo de Uso de um Oráculo (a, b, c, d) (n, 0, 0, 0) comparação de 2 a 2 elementos de a (caminho mais rápido para zerar a). (0, n/2, n/2, 0) comparação de elementos em b para encontrar o máximo (0, 1, n/2, n/2-1) comparação de elementos em c para encontrar o mínimo (0, 1, 1, n-2) Algoritmos e Estrutura de Dados II Exemplo de Uso de um Oráculo (a, b, c, d) (n, 0, 0, 0) comparação de 2 a 2 elementos de a (caminho mais rápido para zerar a). (0, n/2, n/2, 0) comparação de elementos em b para encontrar o máximo (0, 1, n/2, n/2-1) comparação de elementos em c para encontrar o mínimo (0, 1, 1, n-2) n/2 n/2-1 n/2-1 Algoritmos e Estrutura de Dados II Exemplo de Uso de um Oráculo n O passo 1 requer necessariamente a manipulação do componente a. q O caminho mais rápido para levar a até zero requer n/2 mudanças de estado e termina com a tupla (0, n/2, n/2, 0) (por meio de comparação dos elementos de a dois a dois). n A seguir, para reduzir o componente b até um são necessárias n/2 - 1 e mudanças de estado (mínimo de comparações necessárias para obter o maior elemento de b). n Idem para c, com n/2 - 1 mudanças de estado. n Logo, para obter o estado (0, 1, 1, n - 2) a partir do estado (n, 0, 0, 0) são necessárias n/2 + n/2 - 1 + n/2 - 1= 3n/2 – 2 comparações. Algoritmos e Estrutura de Dados II Exemplo de Uso de um Oráculo n O teorema nos diz que se o número de comparações entre os elementos de um vetor for utilizado como medida de custo, então o algoritmo MaxMin3 é ótimo. Algoritmos e Estrutura de Dados II Solução Algoritmos e Estrutura de Dados II Solução (cont) __MACOSX/._AEDSII_aula_005_analise_de_complexidade.pdf AEDSII_aula_006_comportamento_assintotico.pdf Medida do Tempo de Execução de um Programa Livro “Projeto de Algoritmos” – Nívio Ziviani Capítulo 1 – Subseção 1.3.1 http://www2.dcc.ufmg.br/livros/algoritmos/ (slides adaptados) Algoritmos e Estrutura de Dados II Objetivos n Comportamento Assintótico n Notações Ο, Ω, Θ n Principais Classes de Problemas n Algoritmos Polinomiais vs. Exponenciais Algoritmos e Estrutura de Dados II Comportamento Assintótico de Funções n O parâmetro n fornece uma medida da dificuldade para se resolver o problema. n Algoritmos são parecidos para n pequenos – escolha não é crítica. n Análise de algoritmos é realizada para valores grandes de n. n Comportamento assintótico de f(n): limite do comportamento do custo quando n cresce. n Interesse: como o custo aumenta quando n vai para o limite. Algoritmos e Estrutura de Dados II Dominação Assintótica n Definição: Uma função f(n) domina assintoticamente outra função g(n) se existem duas constantes positivas c e m tais que, para n ≥ m, temos |g(n)| ≤ c|f(n)|. Algoritmos e Estrutura de Dados II Dominação Assintótica Exemplo 1: n Sejam f(n) = n2 e g(n) = n n f(n) domina assintoticamente g(n) desde que |g(n)| ≤ c|f(n)| para n ≥ m e c > 0 c = 1 e m = 1 n |g(n)| |f(n)| 0 0 0 1 1 1 2 2 4 3 3 9 ... ... ... f(n) domina assintóticamente g(n) se |g(n)| ≤ c|f(n)| para n ≥ m e c > 0 Algoritmos e Estrutura de Dados II Dominação Assintótica Exemplo 2: n Sejam f(n) = n2 e g(n) = (n+1)2 n f(n) domina assintoticamente g(n)? c = 4 e n ≥ 1 g(n) ≤ cf(n) (n+1)2 ≤ 4n2 n2 + 2n + 1 ≤ 4n2 n + 2 + 1/n ≤ 4n f(n) domina assintóticamente g(n) se |g(n)| ≤ c|f(n)| para n ≥ m e c > 0 Algoritmos e Estrutura de Dados II Dominação Assintótica Exemplo 2: n Sejam f(n) = n2 e g(n) = (n+1)2 n g(n) domina assintoticamente f(n): c = 1 e n ≥ 1 n2 ≤ (n+1)2 n2 ≤ n2 + 2n + 1 1 ≤ 1 + 2/n + 1/n2 f(n) e g(n) dominam assintoticamente uma a outra f(n) domina assintóticamente g(n) se |g(n)| ≤ c|f(n)| para n ≥ m e c > 0 Algoritmos e Estrutura de Dados II Dominação Assintótica Exemplo 3: n Sejam f(n) = n e g(n) = n2 n f(n) domina assintoticamente g(n) desde que |g(n)| ≤ c|f(n)| para n ≥ m e c > 0 n2 ≤ cn n ≤ c (x) (ou seja, f(n) não domina assintoticamente g(n)) f(n) domina assintóticamente g(n) se |g(n)| ≤ c|f(n)| para n ≥ m e c > 0 Notação Assintótica n Ο - especifica um limite superior para g(n). g(n) = O(f(n)) n Ω - especifica um limite inferior para g(n). g(n) = Ω (f(n)) n Θ - especifica um limite firme para g(n). g(n) = Θ(f(n)) Algoritmos e Estrutura de Dados II Notação O n Escrevemos g(n) = O(f(n)) se n f(n) domina assintoticamente g(n). Lê-se g(n) é da ordem no máximo f(n). n Exemplo: quando dizemos que o tempo de execução f(n) de um programa é O(n²), significa que existem constantes c e m tais que, para valores de n≥m, f(n) ≤ cn². Algoritmos e Estrutura de Dados II Notação O n Definição: Uma função g(n) é O(f(n)) se existem duas constantes positivas c e m tais que g(n) ≤ cf(n), para todo n ≥ m. Algoritmos e Estrutura de Dados II Exemplos de Notação O n Exemplo 4: g(n) = (n + 1)2. – Logo g(n) é O(n2), quando m = 1 e c = 4. – Isto porque (n + 1)2 ≤ 4n2 para n ≥ 1. g(n) é O(f(n)) se g(n) ≤ cf(n), para todo n ≥ m, onde m e c são positivas Algoritmos e Estrutura de Dados II Exemplos de Notação O n Exemplo 4: g(n) = (n + 1)2. – Logo g(n) é O(n2), quando m = 1 e c = 4. – Isto porque (n + 1)2 ≤ 4n2 para n ≥ 1. n Exemplo 5: g(n) = n e f(n) = n2. – Sabemos que g(n) é O(n2), pois para n ≥ 0, n ≤ n2. (n+1) 2 ≤ cn2 n2 + 2n + 1 ≤ cn2 1 + 2/n + 1/n2 ≤ c g(n) é O(f(n)) se g(n) ≤ cf(n), para todo n ≥ m, onde m e c são positivas Algoritmos e Estrutura de Dados II Exemplos de Notação O n Exemplo 4: g(n) = (n + 1)2. – Logo g(n) é O(n2), quando m = 1 e c = 4. – Isto porque (n + 1)2 ≤ 4n2 para n ≥ 1. n Exemplo 5: g(n) = n e f(n) = n2. – Sabemos que g(n) é O(n2), pois para n ≥ 0, n ≤ n2. – Entretanto f(n) não é O(n). (n+1) 2 ≤ cn2 n2 + 2n + 1 ≤ cn2 1 + 2/n + 1/n2 ≤ c g(n) é O(f(n)) se g(n) ≤ cf(n), para todo n ≥ m, onde m e c são positivas Algoritmos e Estrutura de Dados II Exemplos de Notação O n Exemplo 4: g(n) = (n + 1)2. – Logo g(n) é O(n2), quando m = 1 e c = 4. – Isto porque (n + 1)2 ≤ 4n2 para n ≥ 1. n Exemplo 5: g(n) = n e f(n) = n2. – Sabemos que g(n) é O(n2), pois para n ≥ 0, n ≤ n2. – Entretanto f(n) não é O(n). – Suponha que existam constantes c e m tais que para todo n ≥ m, n2 ≤ cn. – Logo, c ≥ n para qualquer n ≥ m, e não existe uma constante c que possa ser maior ou igual a n para todo n. (n+1) 2 ≤ cn2 n2 + 2n + 1 ≤ cn2 1 + 2/n + 1/n2 ≤ c g(n) é O(f(n)) se g(n) ≤ cf(n), para todo n ≥ m, onde m e c são positivas Algoritmos e Estrutura de Dados II Exemplos de Notação O n Exemplo 6: g(n) = 3n3 + 2n2 + n é O(n3). – Basta mostrar que 3n3 + 2n2 + n ≤ 6n3, para n ≥ 1. – A função g(n) = 3n3 + 2n2 + n é também O(n4), entretanto esta afirmação é mais fraca do que dizer que g(n) é O(n3). 3n3 + 2n2 + n ≤ cn4 3 + 2/n + 1/n2 ≤ cn, c=6, m=1 n Exemplo 7: 2n+1 é O(2n)? g(n) é O(f(n)) se g(n) ≤ cf(n), para todo n ≥ m, onde m e c são positivas Algoritmos e Estrutura de Dados II Operações com a Notação O Operações com a Notação O Exemplo 8: regra da soma O(f(n)) + O(g(n)). n Suponha três trechos cujos tempos de execução são O(n), O(n2) e O(n log n). n O tempo de execução dos dois primeiros trechos é O(max(n, n2)), que é O(n2). n O tempo de execução de todos os três trechos é então O(max(n2, n log n)), que é O(n2). Operações com a Notação O Exemplo 9: [n + O(1)][n + O(log n) + O(1)] n2 + O(n log n) + O(n) + O(n) + O(log n) + O(1) O(n2) Algoritmos e Estrutura de Dados II Notação Ω n Especifica um limite inferior para g(n). n Definição: Uma função g(n) é Ω(f(n)) se existirem duas constantes c e m tais que g(n) ≥ cf(n), para todo n ≥ m. Algoritmos e Estrutura de Dados II Notação Ω n Exemplo 10: Mostrar que g(n) = 3n3 + 2n2 é Ω(n3) basta fazer c = 1, e então 3n3 + 2n2 ≥ n3 para n ≥ 1. 3 + 2/n ≥ 1 para n ≥ 1 . g(n) é Ω(f(n)) se g(n) ≥ cf(n), para todo n ≥ m, onde m e c são positivas Algoritmos e Estrutura de Dados II Notação Θ n Definição: Uma função g(n) é Ө(f(n)) se existirem constantes positivas c1, c2 e m tais que 0 ≤ c1f(n) ≤ g(n) ≤ c2f(n), para todo n ≥ m n Para todo n ≥ m, a função g(n) é igual a f(n) a menos de uma constante. n Neste caso, f(n) é um limite assintótico firme. Algoritmos e Estrutura de Dados II Notação Θ Exemplo 11: n Mostre que g(n) = n2/2 - 3n é Θ(n2). c1n2 ≤ n2/2 - 3n ≤ c2n2 c1 ≤ 1/2 - 3/n ≤ c2 O lado direito é verdadeiro para N ≥ 1 com c2 ≥ 1/2 e o lado esquerdo para n ≥ 7 com c1 ≤ 1/14. Portanto, escolhendo c1 = 1/14, c2 = 1/2 e m = 7, g(n) = Θ(n2). G(n) é Θ(f(n)) se 0 ≤ c1f(n) ≤ g(n) ≤ c2f(n), para todo n ≥ m, onde c1 e c2 são positivas. Algoritmos e Estrutura de Dados II Classes de Comportamento Assintótico • Se f é uma função de complexidade para um algoritmo F, então O(f) é considerada a complexidade assintótica. • Dominação assintótica permite comparar funções de complexidade • Se as funções f e g dominam assintoticamente uma a outra, então os algoritmos associados são equivalentes. • O comportamento assintótico não serve para comparar esses algoritmos Exemplo: f(n) = n2 e g(n) = (n+1)2 Algoritmos e Estrutura de Dados II Classes de Comportamento Assintótico n Por exemplo, considere dois algoritmos F e G aplicados à mesma classe de problemas, sendo que F leva três vezes o tempo de G ao serem executados, isto é, f(n) = 3g(n), sendo que O(f(n)) = O(g(n)). n Logo, o comportamento assintótico não serve para comparar os algoritmos F e G, porque eles diferem apenas por uma constante. n Podemos avaliar programas comparando as funções de complexidade, negligenciando as constantes de proporcionalidade. Algoritmos e Estrutura de Dados II Comparação de Programas n Um programa com tempo de execução O(n) é melhor que outro com tempo O(n2). n Porém, as constantes de proporcionalidade podem alterar esta consideração. n Exemplo: um programa leva 100n unidades de tempo para ser executado e outro leva 2n2. Qual dos dois programas é melhor? - Depende do tamanho do problema: - Para n < 50, o programa com tempo 2n2 é melhor do que o que possui tempo 100n. Algoritmos e Estrutura de Dados II Principais Classes de Problemas n f(n) = O(1) q Complexidade constante. q Uso do algoritmo independe de n. q As instruções do algoritmo são executadas um número fixo de vezes. void algoritmo1(int *v, int n){ int i, j, aux; for(i = 0; i < 10; i++){ for(j = 0; j < 9; j++){ if(v[j]>v[j+1]) { aux=v[j]; v[j]=v[j+1]; v[j+1]=aux; } } } } Algoritmos e Estrutura de Dados II n f(n) = O(n) q Complexidade linear. q Em geral, um pequeno trabalho é realizado sobre cada elemento de entrada q Cada vez que n dobra de tamanho, o tempo de execução dobra. Principais Classes de Problemas int algoritmo2(int *v, int n, int k){ int i; for(i = 0; i < n; i++){ if(v[i] == k) return i; } return -1; } Algoritmos e Estrutura de Dados II n f(n) = O(log n) q Complexidade logarítmica. q Típico em algoritmos que transformam um problema em outros menores. q Quando n é mil, log2n 10, quando n é 1 milhão, log2n 20. q Exemplo: pesquisa binária Principais Classes de Problemas Algoritmos e Estrutura de Dados II n f(n) = O(n log n) q Típico em algoritmos que quebram um problema em outros menores, resolvem cada um deles independentemente e ajuntando as soluções depois. q Exemplo: algoritmos de ordenação (mergesort, heapsort) Principais Classes de Problemas Algoritmos e Estrutura de Dados II n f(n) = O(n2) q Complexidade quadrática. q Ocorrem quando os itens de dados são processados aos pares, muitas vezes em um anel dentro de outro. q Úteis para problemas de tamanhos pequenos. Principais Classes de Problemas void algoritmo(int *v, int n){ int i, j, aux; for(i = 0; i < n; i++){ for(j = 0; j < n-1; j++){ if(v[j]>v[j+1]) { aux=v[j]; v[j]=v[j+1]; v[j+1]=aux; } } } } Algoritmos e Estrutura de Dados II n f(n) = O(n3) q Complexidade cúbica. q Úteis apenas para resolver pequenos problemas. q Quando n é 100, o número de operações é da ordem de 1 milhão. q Exemplo: multiplicação de matrizes (algoritmo simples) Principais Classes de Problemas Algoritmos e Estrutura de Dados II n f(n) = O(2n) q Complexidade exponencial. q Geralmente não são úteis sob o ponto de vista prático. q Ocorrem na solução de problemas quando se usa força bruta para resolvê-los. q Quando n é 20, o tempo de execução é cerca de 1 milhão. Quando n dobra, o tempo fica elevado ao quadrado. Principais Classes de Problemas Algoritmos e Estrutura de Dados II n f(n) = O(n!) q Um algoritmo de complexidade O(n!) é dito ter complexidade exponencial, apesar de O(n!) ter comportamento muito pior do que O(2n). q Geralmente ocorrem quando se usa força bruta para na solução do problema. q n = 20 → 20! = 2432902008176640000, um número com 19 dígitos. q n = 40 → um número com 48 dígitos. Principais Classes de Problemas Algoritmos e Estrutura de Dados II Comparação de Funções de Complexidade Algoritmos e Estrutura de Dados II Comparação de Funções de Complexidade (tamanho) Algoritmos e Estrutura de Dados II Algoritmos Exponenciais x Polinomiais n Algoritmo exponencial no tempo de execução tem função de complexidade O(cn); c > 1. n Algoritmo polinomial no tempo de execução tem função de complexidade O(p(n)), onde p(n) é um polinômio. n A distinção entre estes dois tipos de algoritmos torna-se significativa quando o tamanho do problema a ser resolvido cresce. n Por isso, os algoritmos polinomiais são muito mais úteis na prática do que os exponenciais. Algoritmos e Estrutura de Dados II Algoritmos Exponenciais x Polinomiais n Algoritmos exponenciais são geralmente simples variações de pesquisa exaustiva. n Algoritmos polinomiais são geralmente obtidos mediante entendimento mais profundo da estrutura do problema. n Um problema é considerado: q intratável: se não existe um algoritmo polinomial para resolvê-lo. q bem resolvido: quando existe um algoritmo polinomial para resolvê-lo. Algoritmos e Estrutura de Dados II Algoritmos Exponenciais x Polinomiais - Exceções n A distinção entre algoritmos polinomiais eficientes e algoritmos exponenciais ineficientes possui várias exceções. n Exemplo: um algoritmo com função de complexidade f(n) = 2n é mais rápido que um algoritmo g(n) = n5 para valores de n menores ou iguais a 20. Algoritmos e Estrutura de Dados II Exemplo de Algoritmo Exponencial n Um caixeiro viajante deseja visitar n cidades de tal forma que sua viagem inicie e termine em uma mesma cidade, e cada cidade deve ser visitada uma única vez n Supondo que sempre há uma estrada entre duas cidades quaisquer, o problema é encontrar a menor rota para a viagem. Algoritmos e Estrutura de Dados II Exemplo de Algoritmo Exponencial n A figura ilustra o exemplo para quatro cidades c1, c2, c3, c4, em que os números nos arcos indicam a distância entre duas cidades. n O percurso < c1, c3, c4, c2, c1> é uma solução para o problema, cujo percurso total tem distância 24. Algoritmos e Estrutura de Dados II Exemplo de Algoritmo Exponencial n Algoritmo simples: verificar todas as rotas e escolher a menor delas. n Há (n - 1)! rotas possíveis e a distância total percorrida em cada rota envolve n adições, logo o número total de adições é n!. n No exemplo anterior teríamos 24 adições. n Suponha agora 50 cidades: o número de adições seria 50! ≈ 1064. n Em um computador que executa 109 adições por segundo, o tempo total para resolver o problema com 50 cidades seria maior do que 1045 séculos só para executar as adições. Algoritmos e Estrutura de Dados II Exercícios Exercício 1: n Mostre que g(n) = log5 n é O(log n). log5 n ≤ clog n (temos que log5n = log n / log 5) log n / log 5 ≤ c log n 1/log 5 ≤ c, c = 1 e m = 1 g(n) é O(f(n)) se g(n) ≤ cf(n), para todo n ≥ m, onde m e c são positivas AEDSII_aula_007_analise_de_algoritmos.pdf Algoritmos e Estrutura de Dados II Técnicas de Análise de Algoritmos n Determinar o tempo de execução de um programa pode ser um problema matemático complexo; n Determinar a ordem do tempo de execução, sem preocupação com o valor da constante envolvida, pode ser uma tarefa mais simples. q manipulação de somas q produtos, q permutações, q fatoriais, q coeficientes binomiais, q solução de equações de recorrência Algoritmos e Estrutura de Dados II Análise do Tempo de Execução n Comando de atribuição, de leitura ou de escrita: O(1). int a; int v[15]; a = 0; v[0] = 12; Algoritmos e Estrutura de Dados II Análise do Tempo de Execução n Sequencia de comandos: n Determinado pelo maior tempo de execução de qualquer comando da sequencia. sum = 0; for (i = 0; i < sqrt(n)/2; i++) sum++; for (j = 0; j < sqrt(n)/4; j++) sum++; for (k = 0; k < 8+j; k++) sum++; Algoritmos e Estrutura de Dados II Análise do Tempo de Execução n Comando de decisão: n Tempo dos comandos dentro do condicional, mais tempo para avaliar a condição, que é O(1). if ( A[j] < A[min] ) min = j; Algoritmos e Estrutura de Dados II Análise do Tempo de Execução n Laço: n Soma do tempo de execução do corpo do laço + o tempo de avaliar a condição de parada (geralmente O(1)) X número de iterações. sum = 0; for (i = 0; i < sqrt(n)/2; i++) { if ( A[j] < A[min] ) min = j; sum++; } Algoritmos e Estrutura de Dados II Análise do Tempo de Execução n Procedimentos não recursivos: n Cada um deve ser computado separadamente, iniciando pelos que não chamam outros procedimentos. Avalia-se então os que são chamam os já avaliados (utilizando os tempos desses). n O processo é repetido até chegar no programa principal. Algoritmos e Estrutura de Dados II Exemplo 1 void exemplo1 (int n) { int i, a; a=0; for (i = 0; i < n; i++) a += i; } Qual a Função de Complexidade para o número de atribuições à variável a? Qual a Ordem de Complexidade da função exemplo1 ? Algoritmos e Estrutura de Dados II Exemplo 2 void exemplo2 (int n) { int i, j, a; a = 0; for (i = 0; i < n; i++) for (j = n; j > i; j--) a += i + j; exemplo1(n); } Algoritmos e Estrutura de Dados II Exemplo Algoritmo de Ordenação n Seleciona o menor elemento do conjunto. n Troca este com o primeiro elemento A[0]. n Repita as duas operações acima com os n - 1 elementos restantes, depois com os n - 2, até que reste apenas um. Algoritmos e Estrutura de Dados II Procedimento não Recursivo Algoritmo para ordenar os n elementos de um conjunto A em ordem ascendente. )1( )2( )3( )4( )5( )6( )7( )8( void Ordena(int* A, int n) { /*ordena o vetor A em ordem ascendente*/ int i, j, min,x; for (i = 0; i < n-1; i++) { min = i; for (j = i; j < n; j++) if ( A[j] < A[min] ) min = j; /* troca A[min] e A[i] */ x = A[min]; A[min] = A[i]; A[i] = x; } } Qual a complexidade em termos do número de comparações com elementos de A? Algoritmos e Estrutura de Dados II Procedimento não Recursivo )1( )2( )3( )4( )5( )6( )7( )8( void Ordena(int* A, int n) { int i, j, min,x; for (i = 0; i < n-1; i++) { min = i; for (j = i; j < n; j++) if ( A[j] < A[min] ) min = j; /* troca A[min] e A[i] */ x = A[min]; A[min] = A[i]; A[i] = x; } } Laço Interno n Contém um comando de decisão, com um comando de atribuição (tempo constante). n Quanto ao corpo do comando de decisão, devemos considerar o pior caso, assumindo que será sempre executado. n O tempo para incrementar o índice do laço e avaliar a condição de terminação é O(1). n O tempo combinado para executar uma vez o laço é O(max(1, 1, 1)) = O(1), conforme regra da soma para a notação O n Como o número de iterações é n - i, o tempo gasto no laço é O((n - i) x 1) = O(n - i), conforme regra do produto para a notação O. Algoritmos e Estrutura de Dados II Procedimento não Recursivo )1( )2( )3( )4( )5( )6( )7( )8( void Ordena(int* A, int n) { int i, j, min,x; for (i = 0; i < n-1; i++) { min = i; for (j = i; j < n; j++) if ( A[j] < A[min] ) min = j; /* troca A[min] e A[i] */ x = A[min]; A[min] = A[i]; A[i] = x; } } Laço Externo n Contém, além do laço interno, quatro comandos de atribuição. O(max(1, (n - i), 1, 1, 1)) = O(n - i). n A linha (1) é executada n - 1 vezes, e o tempo total para executar o programa está limitado ao produto de uma constante pelo somatório de (n - i): n Se considerarmos o número de comparações como a medida de custo relevante, o programa faz (n2)/2 - n/2 comparações para ordenar n elementos. n Se considerarmos o número de trocas, o programa realiza exatamente n - 1 trocas. Algoritmos e Estrutura de Dados II Exercício 1 // Considere A, B e C vetores globais void p1 (int n) { int i, j, k; for (i=0; i<n; i++) for (j=0; j<n; j++) { C[i][j]=0; for (k=n-1; k>=0; k--) C[i][j]=C[i][j]+A[i][k]*B[k][j]; } } O que faz essa função ? Qual a sua ordem de complexidade? Algoritmos e Estrutura de Dados II Exercício 2 void p2 (int n) { int i, j, x, y; x = y = 0; for (i = 1; i <= n; i++) { for (j = i; j <= n; j++) x = x + 1; for (j = 1; j < i; j++) y = y + 1; } } Qual a ordem de complexidade da função p2? Algoritmos e Estrutura de Dados II Exercício 3 void Exercicio3(n){ int i, j, a; for (i=0; i<n; i++){ if (x[i] > 10) for (j=i+1; j<n; j++) x[j] = x[j] + 2; else x[i] = 1; j = n-1; while (j >= 0) { x[j] = x[j] - 2; j = j - 1; } } } Qual a Função de Complexidade para o número de atribuições ao vetor x? AEDSII_aula_008_recursividade.pdf Recursividade Seções 2.2 e 1.4 do livro Projeto e Análise de Algoritmos Algoritmos e Estrutura de Dados II Recursividade n Um procedimento que chama a si mesmo, direta ou indiretamente, é dito ser recursivo. n Recursividade permite descrever algoritmos de forma mais clara e concisa, especialmente problemas recursivos por natureza ou que utilizam estruturas recursivas. n Exemplos q Algoritmos de “Dividir para Conquistar” q Árvores Algoritmos e Estrutura de Dados II Exemplo n Fatorial: n! = n*(n-1)! p/ n>0 0! = 1 n Em C int Fat (int n) { if (n <= 0) return 1; else return n * Fat(n-1); } Algoritmos e Estrutura de Dados II Estrutura n Normalmente, as funções recursivas são divididas em duas partes q Chamada Recursiva q Condição de Parada n A chamada recursiva pode ser direta (mais comum) ou indireta (A chama B que chama A novamente) n A condição de parada é fundamental para evitar a execução de loops infinitos Algoritmos e Estrutura de Dados II Execução n Internamente, quando qualquer chamada de função é feita dentro de um programa, é criado um Registro de Ativação na Pilha de Execução do programa n O registro de ativação armazena os parâmetros e variáveis locais da função bem como o “ponto de retorno” no programa ou subprograma que chamou essa função. n Ao final da execução dessa função, o registro é desempilhado e a execução volta ao subprograma que chamou a função Algoritmos e Estrutura de Dados II Execução pilha de execução topo da pilha registro de ativação Algoritmos e Estrutura de Dados II Exemplo Fat (int n) { if (n<=0) return 1; else return n * Fat(n-1); } main() { int f; f = fat(4); printf(“%d”,f); } pilha de execução Algoritmos e Estrutura de Dados II Fat (int n) { if (n<=0) return 1; else return n * Fat(n-1); } main() { int f; f = fat(4); printf(“%d”,f); } pilha de execução fat(4) Exemplo Algoritmos e Estrutura de Dados II Fat (int n) { if (n<=0) return 1; else return n * Fat(n-1); } main() { int f; f = fat(4); printf(“%d”,f); } pilha de execução fat(4) fat(3) Exemplo Algoritmos e Estrutura de Dados II Fat (int n) { if (n<=0) return 1; else return n * Fat(n-1); } main() { int f; f = fat(4); printf(“%d”,f); } pilha de execução fat(4) fat(3) fat(2) fat(1) fat(0) Exemplo Algoritmos e Estrutura de Dados II Complexidade n A complexidade de tempo do fatorial recursivo é O(n). (Em breve iremos ver a maneira de calcular isso usando equações de recorrência) n Mas a complexidade de espaço também é O(n), devido a pilha de execução n Já no fatorial não recursivo a complexidade de espaço é O(1) Algoritmos e Estrutura de Dados II Complexidade n A complexidade de tempo do fatorial recursivo é O(n). (Em breve iremos ver a maneira de calcular isso usando equações de recorrência) n Mas a complexidade de espaço também é O(n), devido a pilha de execução n Já no fatorial não recursivo a complexidade de espaço é O(1) Fat (int n) { int f; f = 1; while(n > 0){ f = f * n; n = n – 1; } return f; } Algoritmos e Estrutura de Dados II Recursividade n Portanto, a recursividade nem sempre é a melhor solução, mesmo quando a definição matemática do problema é feita em termos recursivos Algoritmos e Estrutura de Dados II Fibonacci n Outro exemplo: Série de Fibonacci: q Fn = Fn-1 + Fn-2 n > 2, q F1 = F2 = 1 q 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89... Fib(int n) { if (n < 3) return 1; else return Fib(n-1) + Fib(n-2); } Algoritmos e Estrutura de Dados II Análise da função Fibonacci n Ineficiência em Fibonacci q Termos Fn-1 e Fn-2 são computados independentemente q Custo para cálculo de Fn q O(φn) onde φ = (1 + √5)/2 = 1,61803... q Golden ratio q Exponencial!!! Algoritmos e Estrutura de Dados II Fibonacci não recursivo n Complexidade: O(n) n Conclusão: não usar recursividade cegamente! int FibIter(int n) { int fn1 = 1, fn2 = 1; int fn, i; if (n < 3) return 1; for (i = 3; i <= n; i++) { fn += fn2 + fn1; fn2 = fn1; fn1 = fn; } return fn; } Algoritmos e Estrutura de Dados II Quando vale a pena usar recursividade n Recursividade vale a pena para Algoritmos complexos, cuja a implementação iterativa é complexa e normalmente requer o uso explícito de uma pilha q Dividir para Conquistar (Ex. Quicksort) q Caminhamento em Árvores (pesquisa) Algoritmos e Estrutura de Dados II Dividir para Conquistar n Duas chamadas recursivas q Cada uma resolvendo a metade do problema n Muito usado na prática q Solução eficiente de problemas q Decomposição n Não produz recomputação excessiva como fibonacci q Porções diferentes do problema Algoritmos e Estrutura de Dados II Exemplo: encontrar o máximo void max(int *v, int e, int d){ int u, v; int m = (e+d)/2; if (e == d) return v[e]; u = max(v, e, m); v = max(v, m+1, d); if (u > v) return u; else return v; } max(v, 0, n-1); E X E M P L O 0 1 2 3 4 5 6 Algoritmos e Estrutura de Dados II Exemplo: régua void regua(int l, r, h){ int m; if (h > 0) { m = (l + r) / 2; marca(m, h); regua(l, m, h – 1); regua(m, r, h – 1); } } Algoritmos e Estrutura de Dados II Execução: régua regua(0, 8, 3) marca(4, 3) regua(0, 4, 2) marca(2, 2) regua(0, 2, 1) marca(1, 1) regua(0, 1, 0) regua(1, 2, 0) regua(2, 4, 1) marca(3, 1) regua(2, 3, 0) regua(3, 4, 0) regua(4, 8, 2) marca(6, 2) regua(4, 6, 1) marca(5, 1) regua(4, 5, 0) regua(5, 6, 0) regua(6, 8, 1) marca(7, 1) regua(6, 7, 0) regua(7, 8, 0) Algoritmos e Estrutura de Dados II Representação por árvore 0, 8, 3 0, 4, 2 4, 8, 2 0, 2, 1 2, 4, 1 4, 6, 1 6, 8, 1 0, 1, 0 1, 2, 0 2, 3, 0 3, 4, 0 4, 5, 0 5, 6, 0 6, 7, 0 7, 8, 0 Algoritmos e Estrutura de Dados II Outros exemplos de recursividade void estrela(int x, y, r) { if (r > 0) { estrela(x-r, y+r, r / 2); estrela(x+r, y+r, r / 2); estrela(x-r, y-r, r / 2); estrela(x+r, y-r, r / 2); box(x, y, r); } } x e y são as coordenadas do centro. r o valor da metade do lado Algoritmos e Estrutura de Dados II Outros exemplos de recursividade void estrela(int x, y, r) { if (r > 0) { estrela(x-r, y+r, r / 2); estrela(x+r, y+r, r / 2); estrela(x-r, y-r, r / 2); estrela(x+r, y-r, r / 2); box(x, y, r); } } x e y são as coordenadas do centro. r o valor da metade do lado Algoritmos e Estrutura de Dados II Exercícios 1. Implemente uma função recursiva para computar o valor de 2n 2. O que faz a função abaixo? int f(int a, int b) { // considere a > b if (b == 0) return a; else return f(b, a % b); } Algoritmos e Estrutura de Dados II Respostas 1. Pot(int n) { if (n==0) return 1; else return 2 * Pot(n-1); } Algoritmos e Estrutura de Dados II Respostas 2. Algoritmo de Euclides. Calcula o MDC (máximo divisor comum) de dois números a e b f(18, 12) f(12, 6) f(6, 0) AEDSII_aula_009_analise_algoritmos_recursivos.pdf Algoritmos e Estrutura de Dados II Análise de Algoritmos Recursivos n Para cada procedimento recursivo é associada uma função de complexidade f(n) desconhecida, onde n mede o tamanho dos argumentos para o procedimento. n Por se tratar de um algoritmo recursivo, f(n) vai ser obtida através de uma equação de recorrência. n Equação de recorrência: maneira de definir uma função por uma expressão envolvendo a mesma função. Algoritmos e Estrutura de Dados II Análise de Algoritmos Recursivos n Tempo de execução de um algoritmo recursivo: tamanho, número de subproblemas e o tempo para decomposição. n Capturado pela equação de recorrência. ⎩ ⎨ ⎧ ≤= >+= cnknT cn nfbnaTnT para ,)( para ),()/()( Algoritmos e Estrutura de Dados II Equação de Recorrência Fat (int n) { if (n <= 0) return 1; else return n * Fat(n-1); } Algoritmos e Estrutura de Dados II Fat (int n) { if (n <= 0) return 1; else return n * Fat(n-1); } n Equação de recorrência para o Fatorial T(n) = c + T(n-1), para n > 0 T(n) = d. para n ≤ 0 Equação de Recorrência Algoritmos e Estrutura de Dados II Equação de Recorrência void max(int *v, int e, int d){ int u, v; int m = (e+d)/2; if (e == d) return v[e]; u = max(v, e, m); v = max(v, m+1, d); if (u > v) return u; else return v; } Algoritmos e Estrutura de Dados II Equação de Recorrência void max(int *v, int e, int d){ int u, v; int m = (e+d)/2; if (e == d) return v[e]; u = max(v, e, m); v = max(v, m+1, d); if (u > v) return u; else return v; } T(n) = 2T(n/2) + 1, para n > 1 T(n) = 0. para n ≤ 1 Algoritmos e Estrutura de Dados II Exercício 1 n Determine a equação de recorrência para a função abaixo (operação relevante: atribuição para vetor X). void ex(vetor X, int n){ int i; if (n > 0) { for(i=0; i<n-1; i++) X[i] = 0; X[n] = n; ex(X,n-1); } } Algoritmos e Estrutura de Dados II Exercício 1 n Determine a equação de recorrência para a função abaixo (operação relevante: atribuição para vetor X). void ex(vetor X, int n){ int i; if (n > 0) { for(i=0; i<n-1; i++) X[i] = 0; X[n] = n; ex(X,n-1); } } ⎩ ⎨ ⎧ ≤= >−+= 0 para ,0)( 0 para ),1()( nnT n nTnnT Algoritmos e Estrutura de Dados II Outro Exemplo n Considere a seguinte função: Pesquisa(n) { (1) if (n <= 1) (2) ‘ inspecione elemento ’ e termine; else { (3) para cada um dos n elementos ‘ inspecione elemento’; (4) Pesquisa(n/3) ; } } Algoritmos e Estrutura de Dados II Outro Exemplo n Considere a seguinte função: Pesquisa(n) { (1) if (n <= 1) (2) ‘ inspecione elemento ’ e termine; else { (3) para cada um dos n elementos ‘ inspecione elemento’; (4) Pesquisa(n/3) ; } } ⎩ ⎨ ⎧ ≤= >+= 1 para ,1)( 1 para ),3/()( nnT n nTnnT Algoritmos e Estrutura de Dados II Análise de Algoritmos Recursivos n Abordagem para solução da recorrência: q Expande a árvore de recursão q Calcula o custo em cada nível da árvore q Soma os custos de todos os níveis q Calcula a fórmula fechada do somatório Algoritmos e Estrutura de Dados II Fat (int n) { if (n <= 0) return 1; else return n * Fat(n-1); } n Equação de recorrência para o Fatorial T(n) = c + T(n-1), para n > 0 T(n) = d. para n ≤ 0 Equação de Recorrência Algoritmos e Estrutura de Dados II Equação de Recorrência Fat (int n) { if (n <= 0) return 1; else return n * Fat(n-1); } n Se n <= 0 faz uma operação de custo constante n Se n > 0 faz uma operação de custo constante e chama recursivamente a função Algoritmos e Estrutura de Dados II Equação de Recorrência Fat (int n) { if (n <= 0) return 1; else return n * Fat(n-1); } n Equação de recorrência para o Fatorial T(n) = c + T(n-1), para n > 0 T(n) = d. para n ≤ 0 Algoritmos e Estrutura de Dados II Resolvendo a Equação de Recorrência n Existem várias formas de se resolver uma equação de recorrência n A mais simples é expandir a equação e depois fazer uma substituição dos termos n Ex. Fatorial: T(n) = c + T(n-1) T(n-1) = c + T(n-2) T(n-2) = c + T(n-3) ... T(1) = c + T(0) T(0) = d T(n) = c + c + c + ... + c + d n vezes T(n) = n.c + d à O(n) Algoritmos e Estrutura de Dados II Equação de Recorrência void max(int *v, int e, int d){ int u, v; int m = (e+d)/2; if (e == d) return v[e]; u = max(v, e, m); v = max(v, m+1, d); if (u > v) return u; else return v; } T(n) = 2T(n/2) + 1, para n > 1 T(n) = 0. para n ≤ 1 Algoritmos e Estrutura de Dados II Exercício 1 n Determine a equação de recorrência para a função abaixo (operação relevante: atribuição para vetor X). Qual a sua complexidade? void ex(vetor X, int n){ int i; if (n > 0) { for(i=0; i<n-1; i++) X[i] = 0; X[n] = n; ex(X,n-1); } } ⎩ ⎨ ⎧ ≤= >−+= 0 para ,0)( 0 para ),1()( nnT n nTnnT Algoritmos e Estrutura de Dados II Outro Exemplo n Equação : n Essa equação lembra que tipo de problema? n Como resolver essa equação? T(n) = 2T(n/2) + n; T(1) = 1; T(n) = 2T(n/2) + n; T(1) = 1; Algoritmos e Estrutura de Dados II Resolvendo a Equação )log.(log.).(log1.2.)2(2)( :Logo log12 )1()2( :Parada de Condição acolocar Para .)2(2)( : termosos doSubstituin )2(2)2(2 )8(8)4(4 )4(4)2(2 )2(2)( :equação a Expandindo 1)1( )2(2)( 222 log 2 11 2 nnOnnnnnninTnT nin TnT ninTnT nnTnT nnTnT nnTnT nnTnT T nnTnT nii i i ii iiii =+=+=+= =→= → += += += += += = += −− Algoritmos e Estrutura de Dados II Exercício n Considere a seguinte função: Pesquisa(n) { (1) if (n <= 1) (2) ‘ inspecione elemento ’ e termine; else { (3) para cada um dos n elementos ‘ inspecione elemento’; (4) Pesquisa(n/3) ; } } Algoritmos e Estrutura de Dados II Análise da Função Recursiva n Equação de recorrência n Resolva a equação de recorrência q Dicas: q Pode fazer a simplificação de n será sempre divisível por 3 q Somatório de uma PG finita: ∑ = + − − = n i n i r rr 0 1 1 1 ⎩ ⎨ ⎧ ≤= >+= 1 para ,1)( 1 para ),3/()( nnT n nTnnT Algoritmos e Estrutura de Dados II Resolvendo a equação n Substitui-se os termos T(k), k < n, até que todos os termos T(k), k > 1, tenham sido substituídos por fórmulas contendo apenas T(1). 1 → n/3K = 1 → n = 3K 1 Algoritmos e Estrutura de Dados II Resolvendo a equação Considerando que T(n/3K) = T(1) temos: Aplicando a fórmula do somatório de uma PG finita temos: O(n) ∑ = + − − = n i n i r rr 0 1 1 1 1 3 1)1( 3 )( 1 0 1 0 +=+= ∑∑ − = − = k i i k i i nT nnT 2123 )3/2()1(1 )3/2())/1(1(1 )3/11())3/1(1(1 − −+ −+ −−+ n n nn n k Algoritmos e Estrutura de Dados II Teorema Mestre n Método “receita de bolo” para resolver recorrências do tipo n Este tipo de recorrência é típico de algoritmos “dividir para conquistar” q Dividem um problema em a subproblemas q Cada subproblema tem tamanho n/b q Custo para dividir e combinar os resultados é f(n) positiva )( e , onde )()/()( nfba nfbnaTnT 11 >≥ += Algoritmos e Estrutura de Dados II Teorema Mestre n Seja )()/()( nfbnaTnT += )).((Θ)( temos , para )()/( se e para )(Ω)( Se ).log(Θ)( temos ,)(Θ)( Se ).(Θ)( temos , com ,)()( Se log log log log log nfnT cncfbnafnnf nnnT nnf nnT nOnf a a a a a b b b b b = <≤>= = = = >= + − 10 0 � � � � Algoritmos e Estrutura de Dados II Teorema Mestre n O resultado da recorrência depende da comparação entre n Note que a diferença entre tem que ser polinomial, isto é, tem que ser pelo menos abnnf log e )( abnnf log e )( �n Teorema Mestre (1) n Resolva a recorrência usando o teorema mestre n Caso 1 se aplica, temos nnTnT += )/()( 39 )(Θ)( 2nnT = )).(()( temos ,1 para )()/( se e 0 para )()( Se 3. ).log()( temos ,)()( Se 2. ).()( temos ,0 com ,)()( Se 1. log log log log log nfnT cncfbnafnnf nnnT nnf nnT nOnf a a a a a b b b b b Θ= <≤>Ω= Θ= Θ= Θ= >= + − ε ε ε ε )()/()( nfbnaTnT += • g(n) é O(f(n)): g(n) ≤ cf(n), para todo n ≥ m • g(n) é Ω(f(n)): g(n) ≥ cf(n), para todo n ≥ m • g(n) é Ө(f(n)): 0 ≤ c1f(n) ≤ g(n) ≤ c2f(n), para todo n ≥ m Teorema Mestre (2) n Resolva a recorrência usando o teorema mestre n Caso 2 se aplica, temos 132 += )/()( nTnT ))(log(Θ)( nnT = )).(()( temos ,1 para )()/( se e 0 para )()( Se 3. ).log()( temos ,)()( Se 2. ).()( temos ,0 com ,)()( Se 1. log log log log log nfnT cncfbnafnnf nnnT nnf nnT nOnf a a a a a b b b b b Θ= <≤>Ω= Θ= Θ= Θ= >= + − ε ε ε ε )()/()( nfbnaTnT += • g(n) é O(f(n)): g(n) ≤ cf(n), para todo n ≥ m • g(n) é Ω(f(n)): g(n) ≥ cf(n), para todo n ≥ m • g(n) é Ө(f(n)): 0 ≤ c1f(n) ≤ g(n) ≤ c2f(n), para todo n ≥ m Teorema Mestre (3) n Resolva a recorrência usando o teorema mestre n Caso 3 se aplica, temos nnnTnT log)4/(3)( += )log()( nnnT Θ= )).(()( temos ,1 para )()/( se e 0 para )()( Se 3. ).log()( temos ,)()( Se 2. ).()( temos ,0 com ,)()( Se 1. log log log log log nfnT cncfbnafnnf nnnT nnf nnT nOnf a a a a a b b b b b Θ= <≤>Ω= Θ= Θ= Θ= >= + − ε ε ε ε )()/()( nfbnaTnT += • g(n) é O(f(n)): g(n) ≤ cf(n), para todo n ≥ m • g(n) é Ω(f(n)): g(n) ≥ cf(n), para todo n ≥ m • g(n) é Ө(f(n)): 0 ≤ c1f(n) ≤ g(n) ≤ c2f(n), para todo n ≥ m Teorema Mestre (4) n Novamente a equação de recorrência: T(n) = 2T(n/2) + n; T(1) = 1; T(n) = 2T(n/2) + n; T(1) = 1; )).(()( temos ,1 para )()/( se e 0 para )()( Se 3. ).log()( temos ,)()( Se 2. ).()( temos ,0 com ,)()( Se 1. log log log log log nfnT cncfbnafnnf nnnT nnf nnT nOnf a a a a a b b b b b Θ= <≤>Ω= Θ= Θ= Θ= >= + − ε ε ε ε )()/()( nfbnaTnT += • g(n) é O(f(n)): g(n) ≤ cf(n), para todo n ≥ m • g(n) é Ω(f(n)): g(n) ≥ cf(n), para todo n ≥ m • g(n) é Ө(f(n)): 0 ≤ c1f(n) ≤ g(n) ≤ c2f(n), para todo n ≥ m Algoritmos e Estrutura de Dados II Teorema Mestre (5) n Resolva a recorrência n Nenhum caso do teorema mestre de aplica nnnTnT log)/()( += 22 T(n) = 2T(n/2) + n; T(1) = 1; )).(()( temos ,1 para )()/( se e 0 para )()( Se 3. ).log()( temos ,)()( Se 2. ).()( temos ,0 com ,)()( Se 1. log log log log log nfnT cncfbnafnnf nnnT nnf nnT nOnf a a a a a b b b b b Θ= <≤>Ω= Θ= Θ= Θ= >= + − ε ε ε ε )()/()( nfbnaTnT += • g(n) é O(f(n)): g(n) ≤ cf(n), para todo n ≥ m • g(n) é Ω(f(n)): g(n) ≥ cf(n), para todo n ≥ m • g(n) é Ө(f(n)): 0 ≤ c1f(n) ≤ g(n) ≤ c2f(n), para todo n ≥ m AEDSII_aula_010_exercicios_complexidade.pdf Complexidade de algoritmos Exemplos e exercícios Exemplo 1 int find ( int a[], int n, int x ) { int i; for ( i = 0; i < n; i++ ) { if ( a[i] == x ) return i; } return 0; } Exemplo 2 int find ( int a[], int n, int x ) { int i = 0, mid; while ( i < n ) { mid = ( n + i ) / 2; if ( a[mid] < x ) n = mid; else if ( a[mid] > x ) i = mid + 1; else return mid; } return 0; } Exemplo 3 void bubbleSort(int *array, int size) { int swapped = 0; int x; do { swapped = 0; for (x = 0; x < size-1;x++) { if (array[x]>array[x+1]) { swap(&array[x], &array[x+1]); swapped = 1; } } } while (swapped); } Exemplo 4 void insert(int *array, int pos, int value) { pos--; while (pos >= 0 && array[pos] > value) { array[pos+1] = array[pos]; pos--; } array[pos+1] = value; } void insertionSort(int *array, int size) { int x; for (x = 0; x < size; x++) { insert(array, x, array[x]); } } Exemplo 5 int Exemplo5(int N, int M) { int result=0, x=0, i, j, k; for (i = 0; i < N; i++) for (j = i; j < N; j++) { for (k = 0; k < M; k++) { x=0; while (x<N) { result++; x+=3; } } for (k = 0; k < 2*M; k++) if (k % 7 == 4) result++; } return result; } Exemplo 6 #define X 0 #define Y 1 int Area2(Point a, b, c) { return (a[X]*b[Y] – a[Y]*b[X] + a[Y]*c[X] – a[X]*c[Y] + b[X]*c[Y] - c[X]*b[Y]); } int AreaPoligono2(Polygon P) { int i, sum = 0; for (i = 1; i < P.numvertices - 1; i++) sum += Area2(P[0], P[i], P[i+1]); return sum; } Exemplo 7 #define MAX 1000 int Escolhe(int k); // funcao com custo O(n^2) int Transfere(int k); // funcao com custo O(log n) void main() { int i, n, sum = 0; int vetor[MAX]; scanf(“%d”, &n); for (i = 0; i < n; i++) vetor[i] = rand() % 50; // inteiro entre 0 e 49 for (i = 0; i < n; i++) { for (j = 0; j < vetor[i]; j++) { if (vetor[j] < vetor[i]) vetor[j] = Escolhe(vetor[i]); else vetor[j] = vetor[i]; } k = vetor[i]; while (k > 0) { Transfere(vetor[k]); k--; } } } Algoritmos e Estrutura de Dados II Teorema Mestre T(n) = 2T(n/2) + n; T(1) = 1; )).(()( temos ,1 para )()/( se e 0 para )()( Se 3. ).log()( temos ,)()( Se 2. ).()( temos ,0 com ,)()( Se 1. log log log log log nfnT cncfbnafnnf nnnT nnf nnT nOnf a a a a a b b b b b Θ= <≤>Ω= Θ= Θ= Θ= >= + − ε ε ε ε )()/()( nfbnaTnT += • g(n) é O(f(n)): g(n) ≤ cf(n), para todo n ≥ m • g(n) é Ω(f(n)): g(n) ≥ cf(n), para todo n ≥ m • g(n) é Ө(f(n)): 0 ≤ c1f(n) ≤ g(n) ≤ c2f(n), para todo n ≥ m Algoritmos e Estrutura de Dados II Ex. 1 T(n) = 2T(n/2) + n; T(1) = 1; int main() { int i, ia, ib, x, n; int A[10]={ . . . }; int B[10]={ . . . }; A[11] = -∞; B[11] = -∞; cout<<"\nEntre com o valor de n p/ achar o n-esimo maior elemento:"; cin>>n; if (n <= |A| + |B|) { ia = 1; ib = 1; for (i = 0; i < n; i++) if (A[ia] > B[ib]) { x = A[ia]; ia++; } else { x = B[ib]; ib++; } } cout<<i<<"-esimo maior elemento = "<<x; } Algoritmos e Estrutura de Dados II Ex. 2 T(n) = 2T(n/2) + n; T(1) = 1; esq = 1; dir = n; achou = false; while (!achou && esq < dir){ i = (esq + dir)/2; if (A[i] >= x) if (A[i+1] <= x) achou = true; else esq = i+1; else dir = i; } if (achou) cout<<"\nSera inserido na posicao "<<i+1; else { i = (esq + dir)/2; if (x > A[i]) cout<<"\nSera inserido na posicao "<<i; else cout<<"\nSera inserido na posicao "<<i+1; } Algoritmos e Estrutura de Dados II Ex. 3 T(n) = 2T(n/2) + n; T(1) = 1; int somatorio(int a) { int soma, i; soma = 0; for (i = 1; i <= a; i++) soma := soma + i; return soma; end; Algoritmos e Estrutura de Dados II Ex. 5 T(n) = 2T(n/2) + n; T(1) = 1; typedef char TipoTexto[MaxTexto]; typedef char TipoPadrao[MaxPadrao]; void ForcaBruta (TipoTexto T, int n, TipoPadrao P, int m) //-- Pesquisa o padrao P[0..m-1] no texto T[0..n-1] – { int i, j, k; for (i = 0; i < n; i++) { k = i; j = 0; while ((j < m) && (T[k] == P[j])) { k++; j++; } if (j == m) { printf("Casamento na posicao %d\n", i); break; // sai do for } } } AEDSII_aula_011_alocacao_dinamica.pdf The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the red x still appears, you may have to delete the image and then insert it again. The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have Alocação Dinâmica de Memória Gisele L. Pappa Algoritmos e Estruturas de Dados II DCC – UFMG The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Alocação Estática x Dinâmica n C: dois tipos de alocação de memória: Estática e Dinâmica n Na alocação estática, o espaço para as variáveis é reservado no início da execução, não podendo ser alterado depois q int a; int b[20]; n Na alocação dinâmica, o espaço para as variáveis pode ser alocado dinamicamente durante a execução do programa The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Organização da Memória Algoritmos e Estrutura de Dados II Informação sobre funções Código compilado Memória Dinâmica The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Alocação Dinâmica n As variáveis alocadas dinamicamente são chamadas de Apontadores (pointers) pois na verdade elas armazenam o endereço de memória de uma variável n A memória alocada dinamicamente faz parte de uma área de memória chamada heap q Basicamente, o programa aloca e desaloca porções de memória do heap durante a execução The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Esquema de Memória Memória Estática 0x016 a 0x020 b 10 0X234 10 a é um int b é um apontador para um int Heap 0X214 0X218 0X222 0X226 0X230 0X234 0X238 0X240 The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Acesso a partir de Apontadores n Acessar o valor da variável: endereço de memória armazenado n Acessar o conteúdo que associado ao endereço de memória armazenado The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Liberação de Memória n A memória deve ser liberada após o término de seu uso n A liberação deve ser feita por quem fez a alocação: q Estática: compilador q Dinâmica: programador The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Apontadores – Notação n definição de p como um apontador para uma variável do tipo Tipo q Tipo *p; n Alocação de memória para uma variável apontada por p q p = (Tipo*) malloc(sizeof(Tipo)); n Liberação de memória q free(p); n Conteudo da variável apontada por P q *p; n Valor nulo para um apontador q NULL; n Endereço de uma variável a q &a; The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Alocação Dinâmica int *a, b; ... b = 10; a = (int *) malloc(sizeof(int)); *a = 20; a = &b; a 20 b Heap Alocação Estática 10 X The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Alocação Dinâmica int b; int *a; b = 10; a = (int *) malloc(sizeof(int)); *a = 20; printf("%d\n", a[0]); a = &b; printf("%d\n", a[0]); The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Erros Comuns n Esquecer de alocar memória e tentar acessar o conteúdo da variável n Copiar o valor do apontador ao invés do valor da variável apontada n Esquecer de desalocar memória q Ela é desalocada ao fim do programa ou procedimento função onde a variável está declarada, mas pode ser um problema em loops n Tentar acessar o conteúdo da variável depois de desalocá-la The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Exercício: C double a; double *p; a = 3.14; printf("%f\n", a); p = &a; *p = 2.718; printf("%f\n", a); a = 5; printf("%f\n", *p); p = NULL; p = (double *)malloc(sizeof(double)); *p = 20; printf("%f\n", *p); printf("%f\n", a); free(p); printf("%f\n", *p); The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Pergunta que não quer calar... int *a não é a declaração de um vetor de int? n Em C, todo vetor é um apontador. n Portanto pode-se fazer coisas como: int a[10], *b; b = a; b[5] = 100; printf(“%d\n”, a[5]); printf(“%d\n”, b[5]); int a[10], *b; b = (int *) malloc(10*sizeof(int)); b[5] = 100; printf(“%d\n”, a[5]); Printf(“%d\n”, b[5]); 100 100 42657 100 Obs. Não se pode fazer a = b no exemplo acima The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Apontadores para Tipos Estruturados n Apontadores são normalmente utilizados com tipos estruturados Typedef struct { int idade; double salario; } TRegistro TRegistro *a; ... a = (TRegistro *) malloc(sizeof(TRegistro)) a->idade = 30; /* *a.idade = 30 */ a->salario = 80; The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Passagem de Parâmetros n Em pascal, parâmetros para função podem ser passados por valor ou por referência q Por valor: o parâmetro formal (recebido no procedimento) é uma cópia do parâmetro real (passado na chamada) q Por referência: o parâmetro formal (recebido no procedimento) é uma referência para o parâmetro real (passado na chamada) q Usa-se o termo var precedendo o parâmetro formal n Em C só existe passagem por valor, logo deve-se implementar a passagem por referência utilizando- se apontadores The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Passagem de Parâmetros (C) void SomaUm(int x, int *y) { x = x + 1; *y = (*y) + 1; printf("Funcao SomaUm: %d %d\n", x, *y); } int main() { int a=0, b=0; SomaUm(a, &b); printf("Programa principal: %d %d\n", a, b); } 1 1 0 1 The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Passagem de Parâmetros n E para alocar memória dentro de um procedimento? q Em pascal, basta passar a variável (apontador) como referência. q Em C também, mas como não há passagem por referência as coisas são um pouco mais complicadas void aloca(int *x, int n) { x=(int *)malloc(n*sizeof(int)); x[0] = 20; } int main() { int *a; aloca(a, 10); a[1] = 40; } Error! Access Violation! void aloca(int **x, int n) { *x=(int *)malloc(n*sizeof(int)); *x[0] = 20; } int main() { int *a; aloca(&a, 10); a[1] = 40; } OK The image cannot be displayed. Your computer may not have enough memory to open the image, or the image may have been corrupted. Restart your computer, and then open the file again. If the The ima ge can not be disp laye d. Algoritmos e Estrutura de Dados II Exercício n Criar um tipo que é uma estrutura que represente uma pessoa, contendo nome, data de nascimento e CPF. n Criar uma variável que é um ponteiro para esta estrutura (no programa principal) n Criar uma função que recebe este ponteiro e preenche os dados da estrutura n Criar uma função que recebe este ponteiro e imprime os dados da estrutura n Fazer a chamada a esta função na função principal AEDSII_aula_012_listas_lineares.pdf Listas Lineares Livro “Projeto de Algoritmos” – Nívio Ziviani Capítulo 3 – Seção 3.1 http://www2.dcc.ufmg.br/livros/algoritmos/ (adaptado) Algoritmos e Estrutura de Dados II Agenda n Listas lineares n TAD para listas lineares n Alocação sequencial n Alocação encadeada Algoritmos e Estrutura de Dados II Listas Lineares n Maneira de representar elementos de um conjunto. n Itens podem ser acessados, inseridos ou retirados de uma lista. n Podem crescer ou diminuir de tamanho durante a execução. n Adequadas quando não é possível prever a demanda por memória Algoritmos e Estrutura de Dados II Definição de Listas Lineares n Sequência de zero ou mais itens q x1 ,x2 ,···,xn , na qual xi é de um determinado tipo e n representa o tamanho da lista linear. n Sua principal propriedade estrutural envolve as posições relativas dos itens em uma dimensão. q Assumindo n ≥ 1, x1 é o primeiro item da lista e xn é o último item da lista. q xi precede xi+1 para i = 1,2,···,n – 1 q xi sucede xi-1 para i = 2,3,···,n q o elemento xi é dito estar na i-ésima posição da lista. Algoritmos e Estrutura de Dados II Listas Lineares NULL struct Aluno { string nome; int matricula; char conceito; }; Algoritmos e Estrutura de Dados II TAD Listas Lineares n TAD: Agrupa a estrutura de dados juntamente com as operações que podem ser feitas sobre esses dados. n Operações para listas lineares. struct Aluno { string nome; int matricula; char conceito; }; NULL Algoritmos e Estrutura de Dados II n O conjunto de operações a ser definido depende de cada aplicação. n Um conjunto de operações necessário a uma maioria de aplicações é: 1) Criar uma lista linear vazia. 2) Inserir um novo item imediatamente após o i-ésimo item. 3) Retirar o i-ésimo item. 4) Localizar o i-ésimo item para examinar e/ou alterar o conteúdo de seus componentes. 5) Combinar duas ou mais listas lineares em uma lista única. 6) Partir uma lista linear em duas ou mais listas. 7) Fazer uma cópia da lista linear. 8) Ordenar os itens da lista em ordem ascendente ou descendente, de acordo com alguns de seus componentes. 9) Pesquisar a ocorrência de um item com um valor particular em algum componente. TAD Listas Lineares Algoritmos e Estrutura de Dados II Implementações de Listas Lineares n As duas representações mais utilizadas são as implementações q Alocação sequencial (arranjo) e encadeada. n Exemplo de Conjunto de Operações: 1) FLVazia(Lista). Faz a lista ficar vazia. 2) Insere(x, Lista). Insere x após o último item da lista. 3) Retira(p, Lista, x). Retorna o item x que está na posição p da lista, retirando-o da lista e deslocando os itens a partir da posição p+1 para as posições anteriores. 4) Vazia(Lista). Esta função retorna true se lista vazia; senão retorna false. 5) Imprime(Lista). Imprime os itens da lista na ordem de ocorrência. Algoritmos e Estrutura de Dados II Alocação Sequencial n Localização na memória: q Posições contíguas. n Visita: q Pode ser percorrida em qualquer direção. q Permite acesso aleatório. n Inserção: q Realizada após o último item com custo constante. q Um novo item no meio da lista custo não constante. n Remoção: q Final da lista: custo constante q Meio ou início: requer deslocamento de itens Algoritmos e Estrutura de Dados II Alocação Sequencial (estrutura) n Os itens armazenados em um array. n MaxTam define o tamanho máximo permitido para a lista. n O campo Último aponta para a posição seguinte a do último elemento da lista. (primeira posição vazia) n O i-ésimo item da lista está armazenado na i-ésima-1 posição do array, 0 ≤ i < Último. (Item[i]) #define InicioArranjo 0 #define MaxTam 1000 typedef int TipoChave; typedef int Apontador; typedef struct { TipoChave Chave; /* outros componentes */ } TipoItem; typedef struct { TipoItem Item[MaxTam]; Apontador Primeiro, Ultimo; } TipoLista; Item Algoritmos e Estrutura de Dados II Alocação Sequencial (estrutura) n Os itens armazenados em um array. n MaxTam define o tamanho máximo permitido para a lista. n O campo Último aponta para a posição seguinte a do último elemento da lista. (primeira posição vazia) n O i-ésimo item da lista está armazenado na i-ésima-1 posição do array, 0 ≤ i < Último. (Item[i]) #define InicioArranjo 0 #define MaxTam 1000 typedef int TipoChave; typedef int Apontador; typedef struct { TipoChave Chave; /* outros componentes */ } TipoItem; typedef struct { TipoItem Item[MaxTam]; Apontador Primeiro, Ultimo; } TipoLista; Item Alocação Sequencial (estrutura) n Os itens armazenados em um array. n MaxTam define o tamanho máximo permitido para a lista. n O campo Último aponta para a posição seguinte a do último elemento da lista. (primeira posição vazia) n O i-ésimo item da lista está armazenado na i-ésima-1 posição do array, 0 ≤ i < Último. (Item[i]) #define InicioArranjo 0 #define MaxTam 1000 typedef int TipoChave; typedef int Apontador; typedef struct { TipoChave Chave; /* outros componentes */ } TipoItem; typedef struct { TipoItem Item[MaxTam]; Apontador Primeiro, Ultimo; } TipoLista; Item Alocação Sequencial (estrutura) n Os itens armazenados em um array. n MaxTam define o tamanho máximo permitido para a lista. n O campo Último aponta para a posição seguinte a do último elemento da lista. (primeira posição vazia) n O i-ésimo item da lista está armazenado na i-ésima-1 posição do array, 0 ≤ i < Último. (Item[i]) #define InicioArranjo 0 #define MaxTam 1000 typedef int TipoChave; typedef int Apontador; typedef struct { TipoChave Chave; /* outros componentes */ } TipoItem; typedef struct { TipoItem Item[MaxTam]; Apontador Primeiro, Ultimo; } TipoLista; Ultimo Primeiro MaxTam Item[0] Item[1] Item /* faz lista ficar vazia */ void FLVazia(TipoLista *Lista) { Lista->Primeiro = InicioArranjo; Lista->Ultimo = Lista->Primeiro; } /* FLVazia */ Alocação Sequencial (operações) ??? ??? ??? ??? … ??? MaxTam /* faz lista ficar vazia */ void FLVazia(TipoLista *Lista) { Lista->Primeiro = InicioArranjo; Lista->Ultimo = Lista->Primeiro; } /* FLVazia */ Alocação Sequencial (operações) ??? ??? ??? ??? … ??? MaxTam Ultimo Primeiro /* faz lista ficar vazia */ void FLVazia(TipoLista *Lista) { Lista->Primeiro = InicioArranjo; Lista->Ultimo = Lista->Primeiro; } /* FLVazia */ /* testa se a lista está vazia */ int Vazia(TipoLista *Lista) { return (Lista->Primeiro == Lista->Ultimo); } /* Vazia */ Alocação Sequencial (operações) ??? ??? ??? ??? … ??? MaxTam Ultimo Primeiro void Insere(TipoItem x, TipoLista *Lista) { if (Lista->Ultimo >= MaxTam) printf("Lista esta cheia\n"); else { Lista->Item[Lista->Ultimo] = x; Lista->Ultimo++; } } /* Insere */ Alocação Sequencial (operações) ??? ??? ??? ??? … ??? MaxTam Ultimo Primeiro void Insere(TipoItem x, TipoLista *Lista) { if (Lista->Ultimo >= MaxTam) printf("Lista esta cheia\n"); else { Lista->Item[Lista->Ultimo] = x; Lista->Ultimo++; } } /* Insere */ Alocação Sequencial (operações) ??? ??? ??? ??? … ??? MaxTam Ultimo Primeiro x ??? ??? ??? … ??? MaxTam Ultimo Primeiro void Retira(Apontador p, TipoLista *Lista, TipoItem *Item) { int Aux; if (Vazia(Lista) || p >= Lista->Ultimo) { printf("Erro: Posicao nao existe\n"); return; } *Item = Lista->Item[p]; Lista->Ultimo--; for (Aux = p+1; Aux <= Lista->Ultimo; Aux++) Lista->Item[Aux - 1] = Lista->Item[Aux]; } Alocação Sequencial (operações) void Retira(Apontador p, TipoLista *Lista, TipoItem *Item) { int Aux; if (Vazia(Lista) || p >= Lista->Ultimo) { printf("Erro: Posicao nao existe\n"); return; } *Item = Lista->Item[p]; Lista->Ultimo--; for (Aux = p+1; Aux <= Lista->Ultimo; Aux++) Lista->Item[Aux - 1] = Lista->Item[Aux]; } x1 x2 x3 x4 ??? ??? Ultimo Primeiro p Alocação Sequencial (operações) void Retira(Apontador p, TipoLista *Lista, TipoItem *Item) { int Aux; if (Vazia(Lista) || p >= Lista->Ultimo) { printf("Erro: Posicao nao existe\n"); return; } *Item = Lista->Item[p]; Lista->Ultimo--; for (Aux = p+1; Aux <= Lista->Ultimo; Aux++) Lista->Item[Aux - 1] = Lista->Item[Aux]; } x1 x2 x3 x4 ??? ??? Ultimo Primeiro p Alocação Sequencial (operações) x1 x2 x3 x4 ??? ??? Ultimo Primeiro p void Retira(Apontador p, TipoLista *Lista, TipoItem *Item) { int Aux; if (Vazia(Lista) || p >= Lista->Ultimo) { printf("Erro: Posicao nao existe\n"); return; } *Item = Lista->Item[p]; Lista->Ultimo--; for (Aux = p+1; Aux <= Lista->Ultimo; Aux++) Lista->Item[Aux - 1] = Lista->Item[Aux]; } x1 x3 x4 ??? (x4) … ??? Ultimo Primeiro Alocação Sequencial (operações) x1 x2 x3 x4 ??? ??? Ultimo Primeiro p x1 x2 x3 x4 ??? ??? Ultimo Primeiro p void Imprime(TipoLista *Lista) { Apontador i; for (i = Lista->Primeiro; i < Lista->Ultimo; i++) printf("%d\n", Lista->Item[i].Chave); } x1 x2 x3 ??? … ??? Ultimo Primeiro Alocação Sequencial (operações) n Vantagens: q economia de memória (os apontadores são implícitos nesta estrutura). q Acesso a um item qualquer é O(1). n Desvantagens: q custo para inserir ou retirar itens da lista, que pode causar um deslocamento de todos os itens, no pior caso; O(n) q em aplicações em que não existe previsão sobre o crescimento da lista, a utilização de arranjos em linguagens como o Pascal pode ser problemática porque neste caso o tamanho máximo da lista tem de ser definido em tempo de compilação. Alocação Sequencial (vantagens e desvantagens) Alocação Encadeada n Cada item é encadeado com o seguinte mediante uma variável do tipo Apontador. n Permite utilizar posições não contíguas de memória. n É possível inserir e retirar elementos sem necessidade de deslocar os itens seguintes da lista. n Há uma célula cabeça para simplificar as operações sobre a lista. Alocação Encadeada n A lista é constituída de células. n Cada célula contém um item da lista e um apontador para a célula seguinte. n O registro TipoLista contém um apontador para a célula cabeça e um apontador para a última célula da lista. Algoritmos e Estrutura de Dados II Alocação Encadeada n Localização na memória: q Posições não sequenciais. n Visita: q Apenas na direção xi para xi+1. q Permite apenas acesso sequencial. n Inserção: q Realizada em qualquer posição com custo constante. n Remoção: q Custo constante em qualquer posição. Algoritmos e Estrutura de Dados II Alocação Encadeada n Características: q Tamanho da lista não é pré-definido q Cada elemento guarda quem é o próximo q Elementos não estão contíguos na memória info info prox info NULL info NULL info NULL prox prox © R. O. Prates Algoritmos e Estrutura de Dados II Sobre os Elementos da Lista n Elemento: guarda as informações sobre cada elemento. n Para isso define-se cada elemento como uma estrutura que possui: q campos de informações q ponteiro para o próximo elemento info prox © R. O. Prates Sobre a Lista n Uma lista é definida como um apontador para a primeira célula n Uma lista pode ter uma célula cabeça info prox prox info prox info NULL n Uma lista pode ter um apontador para o último elemento Último Primeiro © R. O. Prates Implementação em C typedef int TipoChave; typedef struct { TipoChave Chave; /* outros componentes */ } TipoItem; typedef struct Celula_str *Apontador; typedef struct Celula_str { TipoItem Item; Apontador Prox; } Celula; typedef struct { Apontador Primeiro, Ultimo; } TipoLista; © R. O. Prates Último Primeiro TipoLista Prox Item Celula Prox Item Celula Cria Lista Vazia NULL Cabeça Último Primeiro void FLVazia(TipoLista *Lista) { Lista->Primeiro = (Apontador) malloc(sizeof(Celula)); Lista->Ultimo = Lista->Primeiro; Lista->Primeiro->Prox = NULL; } int Vazia(TipoLista Lista) { return (Lista.Primeiro == Lista.Ultimo); } © R. O. Prates Inserção de Elementos na Lista info prox prox info prox info NULL Último n 3 opções de posição onde pode inserir: q 1ª. posição q última posição q Após um elemento qualquer E Primeiro © R. O. Prates Inserção na Primeira Posição info prox prox info prox info NULL Último info NULL prox Primeiro Novo © R. O. Prates Inserção na Última Posição info prox prox info prox info NULL Último info NULL prox Primeiro Novo © R. O. Prates Inserção na Após o Elemento E prox info prox info NULL Último info NULL prox Elem E info prox Primeiro Novo © R. O. Prates Inserção de Elementos na Lista n Na verdade, as 3 opções de inserção são equivalentes a inserir após uma célula apontada por p q 1ª. posição (p é a célula cabeça) q Última posição (p é o último) q Após um elemento qualquer E (p aponta para E) © R. O. Prates Inserção na Após o Elemento E prox info prox info NULL Último info NULL prox Elem E info prox Primeiro x © R. O. Prates void Insere(TipoItem x, TipoLista *lista, Apontador E){ Apontador novo; novo = (Apontador) malloc(sizeof(Celula)); novo->Item = x; novo->prox = E->prox; E->prox = novo; } Retirada de Elementos na Lista info prox prox info prox info NULL Último n 3 opções de posição de onde pode retirar: q 1ª. posição q última posição q Um elemento qualquer E © R. O. Prates Retirada do Elemento na Primeira Posição da Lista info prox prox info prox info NULL Último Primeiro Temp © R. O. Prates Retirada do Elemento E da Lista info prox prox info prox info NULL Último Elem E Anterior Primeiro © R. O. Prates Retirada do Último Elemento da Lista info prox prox info prox info NULL Último Anterior NULL Primeiro © R. O. Prates Retirada do elemento após E da Lista info prox prox info prox info NULL Último E Primeiro © R. O. Prates void RemoveProx(TipoLista *lista, Apontador E){ Apontador tmp; tmp = E->prox; if (tmp != NULL) { E->prox = tmp->prox; free(tmp); } else { E->prox = NULL; } } tmp Algoritmos e Estrutura de Dados II Alocação Encadeada (vantagens e desvantagens) n Vantagens: q Permite inserir ou retirar itens do meio da lista a um custo constante (importante quando a lista tem de ser mantida em ordem). q Bom para aplicações em que não existe previsão sobre o crescimento da lista (o tamanho máximo da lista não precisa ser definido a priori). n Desvantagem: q Utilização de memória extra para armazenar os apontadores. q O(n) para acessar um item no pior caso Algoritmos e Estrutura de Dados II Exemplo: Ordenação n Problema: Ordenar uma lista com alocação encadeada em tempo linear. Esta lista apresenta chaves inteiras com valores entre 0 e 255. 1 dados1 prox 0 dados2 prox 241 dados3 prox 0 dados4 prox 31 dados5 NULL . . . Último Primeiro Exemplo: Ordenação n Solução: Criar um vetor com 256 posições contendo ponteiros para listas com alocação dinâmica. … 0 255 Lista 0 Lista 255 . . . Exemplo: Ordenação n Solução: Criar um vetor com 256 posições contendo ponteiros para listas com alocação dinâmica. … 0 255 0 dados prox Lista 0 Lista 255 0 dados prox . . . . . . 0 dados NULL 255 dados prox 255 dados prox . . . 255 dados NULL Exemplo: Ordenação n Ordenação: n Percorrer lista original n Utilizar a chave de cada elemento para indexar o vetor n Insere elemento como último elemento da l ista correspondente n Cria uma nova lista com alocação dinâmica n Percorrer cada elemento do vetor em ordem sequencial n Percorre cada item da lista correspondente n Insere item na nova lista Exemplo: Crivo de Eratóstenes © R. O. Prates • Crie uma lista com números de 2 até n. • Encontre o primeiro número da lista. • Remova da lista todos os múltiplos do número primo encontrado. • O próximo número da lista é primo. • Repita o procedimento. • Ao final, a lista contém somente números primos. Exercícios n Implemente uma função que, dada uma lista encadeada e uma determinada chave C, remove o elemento com essa chave n Implemente uma função que remova todos os elementos de valor par de uma lista encadeada © R. O. Prates AEDSII_aula_013_pilhas_e_filas.pdf Pilhas e Filas Algoritmos e Estrutura de Dados II Listas, Pilhas e Filas n Listas: Inserção: em qualquer posição Remoção: em qualquer posição n Pilhas: Inserção: Topo (primeira posição na lista) Remoção: Topo (primeira posição na lista) n Filas: Inserção: Trás (última posição na lista) Remoção: Início (primeira posição na lista) Algoritmos e Estrutura de Dados II TAD Pilhas n Tipo Abstrato de dados com a seguinte característica: O último elemento a ser inserido é o primeiro a ser retirado (LIFO – Last In First Out) n Analogia: pilha de pratos, livros, etc n Usos: Chamada de subprogramas, avaliação de expressões aritméticas, etc... Algoritmos e Estrutura de Dados II TAD Pilhas n Conjunto de operações: 1) FPVazia(Pilha). Faz a pilha ficar vazia. 2) Vazia(Pilha). Retorna true se a pilha está vazia; caso contrário, retorna false. 3) Empilha(x, Pilha). Insere o item x no topo da pilha. 4) Desempilha(Pilha). Retorna o item x no topo da pilha, retirando-o da pilha. 5) Tamanho(Pilha). Esta função retorna o número de itens da pilha. n Representações comuns n Alocação sequencial (arranjos) e apontadores Algoritmos e Estrutura de Dados II TAD Pilhas M ax Ta m Topo Pilha Empilha(1, Pilha) Desempilha(1, Pilha) Topo Pilha Algoritmos e Estrutura de Dados II Implementação de Pilhas com alocação Sequencial n Os itens da pilha são armazenados em posições contíguas de memória. n Inserções e retiradas: apenas no topo. n Variável Topo é utilizada para controlar a posição do item no topo da pilha. Algoritmos e Estrutura de Dados II Implementação de Pilhas com alocação Sequencial Topo Pilha Empilha(21, Pilha) Desempilha(Pilha) 21 Topo Pilha Empilha(3, Pilha) 21 3 Topo Pilha 21 Topo Pilha retorna 3 . Algoritmos e Estrutura de Dados II Estrutura de Dados de Pilhas com alocação Sequencial #define MaxTam 1000 typedef int Apontador; typedef int TipoChave; typedef struct { TipoChave Chave; /* outros componentes */ } TipoItem; typedef struct { TipoItem Item[MaxTam]; Apontador Topo; } TipoPilha; Item[0] Item[1] Item[2] Item[3] ... Item[MaxTam-1] Topo Pilha MaxTam Algoritmos e Estrutura de Dados II Operações sobre Pilhas com alocação Sequencial void FPVazia(TipoPilha *Pilha) { Pilha->Topo = 0; } /* FPVazia */ int Vazia(TipoPilha *Pilha) { return (Pilha->Topo == 0); } /* Vazia */ Operações sobre Pilhas com alocação Sequencial void Empilha(TipoItem x, TipoPilha *Pilha) { if (Pilha->Topo == MaxTam) printf(“Erro: pilha está cheia\n"); else { Pilha->Item[Pilha->Topo] = x; Pilha->Topo++; } } Topo x Topo x Topo Operações sobre Pilhas com alocação Sequencial TipoItem Desempilha(TipoPilha *Pilha) { if (Vazia(Pilha)) printf(“Erro: a pilha está vazia\n"); else { Pilha->Topo--; return Pilha->Item[Pilha->Topo]; } } x Topo x Topo Topo Algoritmos e Estrutura de Dados II Operações sobre Pilhas Usando Arranjos int Tamanho(TipoPilha *Pilha) { return Pilha->Topo; } Algoritmos e Estrutura de Dados II Implementação de Pilhas por meio de Apontadores n Há uma célula cabeça no topo para facilitar a implementação das operações empilha e desempilha quando a pilha está vazia. n Desempilha: desliga a célula cabeça da lista a próxima célula, que contém o primeiro item, passa a ser a célula cabeça. n Empilha: Cria uma nova célula cabeça e adiciona o item a ser empilhado na antiga célula cabeça. NULL Algoritmos e Estrutura de Dados II Estrutura da Pilha Usando Apontadores Algoritmos e Estrutura de Dados II Estrutura da Pilha Usando Apontadores typedef int TipoChave; typedef struct { int Chave; /* --- outros componentes --- */ } TipoItem; typedef struct Celula *Apontador; typedef struct Celula { TipoItem Item; Apontador Prox; } Celula; typedef struct { Apontador Fundo, Topo; int Tamanho; } TipoPilha; Fundo Topo TipoPilha Prox Celula Prox Item Celula . Cabeça Algoritmos e Estrutura de Dados II Operações sobre Pilhas Usando Apontadores void FPVazia(TipoPilha *Pilha) { Pilha->Topo = (Apontador) malloc(sizeof(Celula)); Pilha->Fundo = Pilha->Topo; Pilha->Topo->Prox = NULL; Pilha->Tamanho = 0; } /* FPVazia */ Fundo Topo TipoPilha NULL Prox Celula . Algoritmos e Estrutura de Dados II Operações sobre Pilhas Usando Apontadores int Vazia(TipoPilha *Pilha) { return (Pilha->Topo == Pilha->Fundo); } /* Vazia */ Operações sobre Pilhas Usando Apontadores void Empilha(TipoItem x, TipoPilha *Pilha) { Apontador Aux; Aux = (Apontador) malloc(sizeof(Celula)); Pilha->Topo->Item = x; Aux->Prox = Pilha->Topo; Pilha->Topo = Aux; Pilha->Tamanho++; } Fundo Topo TipoPilha NULL Prox Celula Prox Celula Aux Operações sobre Pilhas Usando Apontadores void Empilha(TipoItem x, TipoPilha *Pilha) { Apontador Aux; Aux = (Apontador) malloc(sizeof(Celula)); Pilha->Topo->Item = x; Aux->Prox = Pilha->Topo; Pilha->Topo = Aux; Pilha->Tamanho++; } Fundo Topo TipoPilha x NULL Prox Celula Prox Celula Aux Operações sobre Pilhas Usando Apontadores TipoItem Desempilha(TipoPilha *Pilha){ Apontador q; if (Vazia(Pilha)) { printf(“Erro: pilha vazia\n"); ERRO; } q = Pilha->Topo; Pilha->Topo = q->Prox; free(q); Pilha->Tamanho--; return Topo->Item; } Fundo Topo TipoPilha x NULL Prox Celula Prox Celula q q Operações sobre Pilhas Usando Apontadores TipoItem Desempilha(TipoPilha *Pilha){ Apontador q; if (Vazia(Pilha)) { printf(“Erro: pilha vazia\n"); ERRO; } q = Pilha->Topo; Pilha->Topo = q->Prox; free(q); Pilha->Tamanho--; return Topo->Item; } Fundo Topo TipoPilha x NULL Prox Celula Prox Celula q q Operações sobre Pilhas Usando Apontadores TipoItem Desempilha(TipoPilha *Pilha){ Apontador q; if (Vazia(Pilha)) { printf(“Erro: pilha vazia\n"); ERRO; } q = Pilha->Topo; Pilha->Topo = q->Prox; free(q); Pilha->Tamanho--; return Topo->Item; } Fundo Topo TipoPilha x NULL Prox Celula q Algoritmos e Estrutura de Dados II Operações sobre Pilhas Usando Apontadores int Tamanho(TipoPilha *Pilha) { return (Pilha->Tamanho); } /* Tamanho */ Exemplos: Pilhas Inversão de strings n Inverter a string “Exemplo” usando uma pilha. 1. Empilha cada caractere em uma pilha vazia 2. Desempilha todos elementos a E E X E X E E X E M E X E M P E X E M P L E X E M P L O E X E M P L O E X E M P L E X E M P E X E M E X E E X E Exemplos: Pilhas Conversão notação infixada p/ pós-fixada n Infixada: (5 * (((9+8) * (4*6)) + 7)) n Pós-fixada: 5 9 8 + 4 6 * * 7 + * (tão logo encontre um operador, efetua a operação) n Utilizar uma pilha para converter de infixada para pós-fixada. a Converte(char *exp, TipoPilha *pilha){ for (i = 0; i < N; i++) { if (exp[i] == ‘)’) printf(“%c “, desempilha(pilha)) if (exp[i] == ‘+’ || exp[i] == ‘*’) empilha(exp[i], pilha); if (exp[i] >= ‘0’ && exp[i] <= ‘9’) printf(“%c “, exp[i]); } typedef char TipoItem; Exemplos: Pilhas Conversão notação infixada p/ pós-fixada n entrada: (5 * (((9+8) * (4*6)) + 7)) saída: 5 9 8 + 4 6 * * 7 + * a Entrada Pilha saída ( 5 5 * * ((( 9 9 + * + 8 8 ) * + * * * ( 4 4 * * * * 6 6 ) * * * ) * * + * + 7 7 ) * + ) * Algoritmos e Estrutura de Dados II TAD Filas n Tipo Abstrato de dados com a seguinte característica: O primeiro elemento a ser inserido é o primeiro a ser retirado (FIFO – First In First Out) n Analogia: fila bancária, fila do cinema n Usos: Sistemas operacionais: fila de impressão, processamento Algoritmos e Estrutura de Dados II TAD Filas n Conjunto de operações: 1) FFVazia(Fila). Faz a fila ficar vazia. 2) Enfileira(x, Fila). Insere o item x no final da fila. 3) Desenfileira(Fila). Retorna o item x no início da fila, retirando-o da fila. 4) Vazia(Fila). Esta função retorna true se a fila está vazia; senão retorna false. Algoritmos e Estrutura de Dados II Implementação de Filas com alocação sequencial Arranjos n Os itens são armazenados em posições contíguas de memória. n Enfileira: faz a parte de trás da fila expandir-se. n Desenfileira: faz a parte da frente da fila contrair- se. n A fila tende a se movimentar pela memória do computador, ocupando espaço na parte de trás e descartando espaço na parte da frente. Algoritmos e Estrutura de Dados II Implementação de Filas com alocação sequencial n Com poucas inserções e retiradas, a fila vai ao encontro do limite do espaço da memória alocado para ela. n Solução: imaginar o array como um círculo. A primeira posição segue a última. . Implementação de Filas com alocação sequencial n A fila se encontra em posições contíguas de memória, em alguma posição do círculo, delimitada pelos apontadores Frente e Trás. (Frente: posição do primeiro elemento, trás: a primeira posição vazia) n Enfileirar: mover o apontador Trás uma posição no sentido horário. n Desenfileirar: mover o apontador Frente uma posição no sentido horário. frente trás Enfileira(x): frente trás x Algoritmos e Estrutura de Dados II Estrutura da Fila com Alocação Sequencial #define MaxTam 1000 typedef int Apontador; typedef int TipoChave; typedef struct { TipoChave Chave; /* outros componentes */ } TipoItem; typedef struct { TipoItem Item[MaxTam]; Apontador Frente, Tras; } TipoFila; MaxTam-1 trás frente Operações sobre Filas com Alocação Sequencial n Nos casos de fila cheia e fila vazia, os apontadores Frente e Trás apontam para a mesma posição do círculo. n Uma saída para distinguir as duas situações é deixar uma posição vazia no array. n Neste caso, a fila está cheia quando Trás+1 for igual a Frente. void FFVazia(TipoFila *Fila) { Fila->Frente = 0; Fila->Tras = Fila->Frente; } /* FFVazia */ int Vazia(TipoFila *Fila) { return (Fila->Frente == Fila->Tras); } /* Vazia */ Operações sobre Filas com Alocação Sequencial int Enfileira(TipoItem x, TipoFila *Fila) { if ((Fila->Tras + 1) % MaxTam == Fila->Frente){ printf("Erro: fila está cheia\n"); return 0; } else { Fila->Item[Fila->Tras] = x; Fila->Tras = (Fila->Tras + 1) % MaxTam; } return 1; } MaxTam-1 trás frente MaxTam-1 trás frente Operações sobre Filas com Alocação Sequencial TipoItem Desenfileira(TipoFila *Fila) { if (Vazia(Fila)){ printf("Erro: fila está vazia\n"); ERRO; } else { int idx = Fila->Frente; Fila->Frente = (Fila->Frente + 1) % MaxTam; return Fila->Item[idx]; } } MaxTam-1 frente trás MaxTam-1 frente trás Algoritmos e Estrutura de Dados II Implementação de Filas por meio de Apontadores n Utiliza célula cabeça para facilitar a implementação das operações Enfileira e Desenfileira quando a fila está vazia. n Quando vazia: apontadores Frente e Trás apontam para a célula cabeça. n Enfileirar novo item: criar uma célula nova, ligá-la após a célula contendo xn e colocar nela o novo item. n Desenfileirar: desligar a célula cabeça da lista e a célula que contém x1 passa a ser a célula cabeça. NULL Estrutura da Fila Usando Apontadores typedef int TipoChave; typedef struct TipoItem { TipoChave Chave; /* outros componentes */ } TipoItem; typedef struct Celula *Apontador; typedef struct Celula { TipoItem Item; Apontador Prox; } Celula; typedef struct TipoFila { Apontador Frente, Tras; } TipoFila; n A fila é implementada por meio de células. n Cada célula contém um item da fila e um apontador para outra célula. n A estrutura TipoFila contém um apontador para a frente da fila (célula cabeça) e um apontador para a parte de trás da fila. Tras Frente TipoFila Prox Celula Prox Item Celula Cabeça Algoritmos e Estrutura de Dados II Operações sobre Filas Usando Apontadores void FFVazia(TipoFila *Fila) { Fila->Frente = (Apontador) malloc(sizeof(Celula)); Fila->Tras = Fila->Frente; Fila->Frente->Prox = NULL; } /* FFVazia */ int Vazia(TipoFila *Fila) { return (Fila->Frente == Fila->Tras); } /* Vazia */ Tras Frente TipoFila NULL Prox Celula Operações sobre Filas Usando Apontadores void Enfileira(TipoItem x, TipoFila *Fila) { Fila->Tras->Prox = (Apontador) malloc(sizeof(Celula)); Fila->Tras = Fila->Tras->Prox; Fila->Tras->Item = x; Fila->Tras->Prox = NULL; } Tras Frente TipoFila NULL Prox Celula NULL Prox x Celula Operações sobre Filas Usando Apontadores TipoItem Desenfileira(TipoFila *Fila){ Apontador q; if (Vazia(Fila)) { printf("Erro: fila está vazia\n"); ERRO; } q = Fila->Frente; Fila->Frente = Fila->Frente->Prox; free(q); return Fila->Frente->Item; } Trás Frente TipoFila x NULL Prox Celula Prox Celula AEDSII_aula_014_arvores.pdf ¡ Organizam dados de forma hierárquica ¡ Acontecem com frequência na natureza ¡ Fáceis de representar e manipular com computadores ¡ Úteis para várias tarefas Raiz Folhas Nós internos Filhos Pai Descendentes Ancestrais Irmãos Níveis 0 1 2 3 4 Caminho ¡ Árvores não contêm ciclos § Só existe um caminho entre qualquer par de nós ¡ Qualquer árvore pode ser transformada numa árvore binária § Focaremos em árvores binárias pois elas são mais fáceis de armazenar e manipular ¡ Uma árvore binária é uma árvore com zero, um ou dois filhos onde cada filho é também uma árvore binária § Definição é recursiva § Veremos que manipulação de árvores é fácil usando recursão struct arvore { struct arvore *esq; struct arvore *dir; int dado; }; ¡ Pré-‐ordem ¡ Ordem central ¡ Pós-‐ordem 3 2 1 3 1 2 2 1 3 void pre_ordem(struct arvore *f) { if(f == NULL) { return; } printf(“%d”, f->dado); pre_ordem(f->esq); pre_ordem(f->dir); } 3 2 1 4 6 7 8 9 A B C 5 1 void pre_ordem(struct arvore *f){ if(f == NULL) { return; } printf(“%d”, f->dado); pre_ordem(f->esq); pre_ordem(f->dir); } 3 2 1 4 6 7 8 9 A B C 5 1 2 void pre_ordem(struct arvore *f){ if(f == NULL) { return; } printf(“%d”, f->dado); pre_ordem(f->esq); pre_ordem(f->dir); } 3 2 1 4 6 7 8 9 A B C 5 1 2 4 void pre_ordem(struct arvore *f){ if(f == NULL) { return; } printf(“%d”, f->dado); pre_ordem(f->esq); pre_ordem(f->dir); } 3 2 1 4 6 7 8 9 A B C 5 1 1 2 4 7 void pre_ordem(struct arvore *f){ if(f == NULL) { return; } printf(“%d”, f->dado); pre_ordem(f->esq); pre_ordem(f->dir); } 3 2 1 4 6 7 8 9 A B C 5 1 2 4 7 A void pre_ordem(struct arvore *f){ if(f == NULL) { return; } printf(“%d”, f->dado); pre_ordem(f->esq); pre_ordem(f->dir); } 3 2 1 4 6 7 8 9 A B C 5 1 2 4 7 A B void pre_ordem(struct arvore *f){ if(f == NULL) { return; } printf(“%d”, f->dado); pre_ordem(f->esq); pre_ordem(f->dir); } 3 2 1 4 6 7 8 9 A B C 5 1 2 4 7 A B 3 void pre_ordem(struct arvore *f){ if(f == NULL) { return; } printf(“%d”, f->dado); pre_ordem(f->esq); pre_ordem(f->dir); } 3 2 1 4 6 7 8 9 A B C 5 1 2 4 7 A B 3 5 6 8 C 9 void pre_ordem(struct arvore *f){ if(f == NULL) { return; } printf(“%d”, f->dado); pre_ordem(f->esq); pre_ordem(f->dir); } void ordem_central(struct arvore *f) { if(f == NULL) { return; } ordem_central(f->esq); printf(“%d”, f->dado); ordem_central(f->dir); } 3 2 1 4 6 7 8 9 A B C 5 A void ordem_central(struct arvore *f){ if(f == NULL) { return; } ordem_central(f->esq); printf(“%d”, f->dado); ordem_central(f->dir); } 3 2 1 4 6 7 8 9 A B C 5 A 7 void ordem_central(struct arvore *f){ if(f == NULL) { return; } ordem_central(f->esq); printf(“%d”, f->dado); ordem_central(f->dir); } 3 2 1 4 6 7 8 9 A B C 5 A 7 B void ordem_central(struct arvore *f){ if(f == NULL) { return; } ordem_central(f->esq); printf(“%d”, f->dado); ordem_central(f->dir); } 3 2 1 4 6 7 8 9 A B C 5 A 7 B 4 void ordem_central(struct arvore *f){ if(f == NULL) { return; } ordem_central(f->esq); printf(“%d”, f->dado); ordem_central(f->dir); } 3 2 1 4 6 7 8 9 A B C 5 A 7 B 4 2 1 5 3 C 8 6 9 void ordem_central(struct arvore *f){ if(f == NULL) { return; } ordem_central(f->esq); printf(“%d”, f->dado); ordem_central(f->dir); } void pos_ordem(struct arvore *f) { if(f == NULL) { return; } pos_ordem(f->esq); pos_ordem(f->dir); printf(“%d”, f->dado); } A void pos_ordem(struct arvore *f){ if(f == NULL) { return; } pos_ordem(f->esq); pos_ordem(f->dir); printf(“%d”, f->dado); } 3 2 1 4 6 7 8 9 A B C 5 2 1 4 7 A A B 3 2 1 4 6 7 8 A B C 5 9 void pos_ordem(struct arvore *f){ if(f == NULL) { return; } pos_ordem(f->esq); pos_ordem(f->dir); printf(“%d”, f->dado); } A B 7 3 2 1 4 6 7 8 A B C 5 9 void pos_ordem(struct arvore *f){ if(f == NULL) { return; } pos_ordem(f->esq); pos_ordem(f->dir); printf(“%d”, f->dado); } A B 7 4 3 2 1 4 6 7 8 A B C 5 9 void pos_ordem(struct arvore *f){ if(f == NULL) { return; } pos_ordem(f->esq); pos_ordem(f->dir); printf(“%d”, f->dado); } A B 7 4 2 3 2 1 4 6 7 8 A B C 5 9 void pos_ordem(struct arvore *f){ if(f == NULL) { return; } pos_ordem(f->esq); pos_ordem(f->dir); printf(“%d”, f->dado); } A B 7 4 2 5 3 2 1 4 6 7 8 A B C 5 9 void pos_ordem(struct arvore *f){ if(f == NULL) { return; } pos_ordem(f->esq); pos_ordem(f->dir); printf(“%d”, f->dado); } A B 7 4 2 5 C 3 2 1 4 6 7 8 A B C 5 9 void pos_ordem(struct arvore *f){ if(f == NULL) { return; } pos_ordem(f->esq); pos_ordem(f->dir); printf(“%d”, f->dado); } A B 7 4 2 5 C 8 9 6 3 1 3 2 1 4 6 7 8 A B C 5 9 void pos_ordem(struct arvore *f){ if(f == NULL) { return; } pos_ordem(f->esq); pos_ordem(f->dir); printf(“%d”, f->dado); } 5 9 8 + 4 6 * * 7 + * +5 * 7 + * 9 8 4 * 6 void pos_ordem(struct arvore *f){ if(f == NULL) { return; } pos_ordem(f->esq); pos_ordem(f->dir); printf(“%d”, f->dado); } ¡ Até agora consideramos árvores que organizam os dados em forma hierárquica sem inspecionar os elementos ¡ Árvores de busca mantém uma ordem entre seus elementos § Raiz é maior que os elementos na árvore à esquerda § Raiz é menor que os elementos na árvore à direita 8 5 6 4 B 2 A C 1 3 9 7 0 struct arvore { struct arvore *esq; struct arvore *dir; int dado; }; struct arvore * cria_arvore(void) { struct arvore *novo; novo = malloc(sizeof(struct arvore)); novo->esq = NULL; novo->dir = NULL; novo->dado = 0; } struct arvore *busca_elemento(struct arvore *t, int D) { if(t == NULL) /* elemento não existente na árvore */ return NULL; else if (D == t->dado) /* encontrou elemento */ return t; else if (D < t->dado) /* busca na árvore esquerda */ return busca_elemento(t->esq, D); else if (D > t->dado) /* busca na árvore direita */ return busca_elemento(t->dir, D); } void acha_menor(arvore *t) { if(t->esq == NULL) { return t; } return acha_menor(t->esq); } ¡ O elemento vai ser inserido como uma folha da árvore de busca ¡ Vamos procurar o lugar de inserção navegando da raiz até a folha onde ele será inserido 8 5 6 4 B 2 A C 1 3 9 7 4.5 8 5 6 4 B 2 A C 1 3 9 7 4.5 8 5 6 4 B 2 A C 1 3 9 7 4.5 8 5 6 4 B 2 A C 1 3 9 7 4.5 void insere_elemento(struct arvore *t, int D) { if(D < t->dado) { if(t->esq) { insere_elemento(t->esq, D); } else { /* achou local de inserção */ struct arvore *novo = cria_arvore(); novo->dado = D; t->esq = novo; } } else if(D > t->dado) { if(t->dir) { insere_elemento(t->dir, D); } else { struct arvore *novo = cria_arvore(); novo->dado = D; t->dir = novo; } } else { printf(“elemento já existe na árvore\n”); } } ¡ Remover folhas ¡ Remover nós com um filho ¡ Remover nós com dois filhos 8 5 6 4 B 2 A C 1 3 9 7 8 5 6 4 B 2 A C 1 3 9 7 8 5 6 B2 A C1 3 9 7 8 5 6 4 B 2 A C 1 3 9 7 sucessor 8 5 7 4 B 2 A C 1 3 9 7 8 5 7 4 B 2 A C 1 3 9 7 struct arvore *remove(struct arvore *t, int D) { struct arvore *aux; if(t == NULL) { printf(“elemento ausente\n”); } else if(D < t->dado){ t->esq = remove(t->esq, D);} else if(D > t->dado){ t->dir = remove(t->dir, D);} else if(t->esq == NULL && t->dir == NULL) { free(t); return NULL; /* zero filhos */ } else if(t->esq == NULL) { aux = t->dir; free(t); return aux; /* 1 filho */ } else if(t->dir == NULL) { aux = t->esq; free(t); return aux; /* 1 filho */ } else { /* 2 filhos */ struct arvore *suc = acha_menor(t->dir); t->dado = suc->dado; t->dir = remove(t->dir, suc->dado); return t; } return t; } void acha_menor(arvore *t) { if(t->esq == NULL) { return t; } return acha_menor(t->esq); } AEDSII_aula_015_ordenacao.pdf Ordenação: Introdução e métodos elementares Algoritmos e Estruturas de Dados II Ordenação } Objetivo: } Rearranjar os itens de um vetor ou lista de modo que suas chaves estejam ordenadas de acordo com alguma regra. } Estrutura: typedef int TipoChave; typedef struct { ChaveTipo chave; /* outros componentes */ } Item; 2 E X E M P L O E E L M O P X Critérios de Classificação } Localização dos dados: } Ordenação interna: todas as chaves estão na memória principal. } Ordenação externa: chaves na memória principal e na memória secundária. } Estabilidade: } Relacionado com o manutenção da ordem relativa entre chaves de mesmo valor. } Método é estável se a ordem relativa dos registros com a mesma chave não se altera após a ordenação. 3 E X E M P L O E E L M O P X 1 1 2 2 Critérios de Classificação } Uso da memória: } In place: transforma os dados de entrada utilizando apenas um espaço extra de tamanho constante. } Movimentação dos dados: } Direta: registro todo é acessado e deve ser movido } Indireta: apenas as chaves são acessadas e ponteiros são rearranjados e não o registro todo. } Adaptabilidade: } Sequência de operações executadas conforme a entrada. } Não adaptável: operações executadas independe da entrada. 4 Critérios de Avaliação 5 } Seja n o número de registros em um vetor, considera- se duas medidas de complexidade: } Número de comparações C(n) entre as chaves; } Número de trocas ou movimentações M(n) de itens; #define Troca(A, B) {Item c = A; A = B; B = c; } void Ordena(Item *v, int n) { int i, j; for (i = 0; i < n-1; i++) { for (j = n-1; j > i; j--) { if (v[j-1].chave > v[j].chave) /* comparações */ Troca(v[j-1], v[j]); /* trocas */ } Método Bolha 6 } Ideia: } Passa no arquivo e troca elementos adjacentes que estão fora de ordem, até os registros ficarem ordenados. } Algoritmo } Supondo movimentação da esquerda para direita no vetor; } Quando o maior elemento do vetor for encontrado, ele será trocado até ocupar a última posição; } Na segunda passada, o segundo maior será movido para a penúltima posição do vetor. E X E M P L O E E X M P L O E E M X P L O E E M P X L O E E M P L X O E E M P L O X E E M P L O X Método Bolha 7 void Bolha (Item *v, int n) { int i, j; for(i = 0; i < n-1; i++) for(j = 1; j < n-i; j++) if (v[j].chave < v[j-1].chave) Troca(v[j-1], v[j]); } Método Bolha 8 void Bolha (Item *v, int n) { int i, j; for(i = 0; i < n-1; i++) for(j = 1; j < n-i; j++) if (v[j].chave < v[j-1].chave) Troca(v[j-1], v[j]); } E X E M P L O E E M P L O X E E M L O P X E E L M O P X E E L M O P X E E L M O P X E E L M O P X E E L M O P X Método Bolha: Complexidade 9 } Comparações – C(n): void Bolha (Item *v, int n) { int i, j; for(i = 0; i < n-1; i++) for(j = 1; j < n-i; j++) if (v[j].chave < v[j-1].chave) Troca(v[j-1], v[j]); } } )n(Onn )n()n)(n()n(n in)in()n(C n i n i n i n i n i in j 2 2 2 0 2 0 2 0 2 0 2 0 1 2 12 2 211 111 = − = +−− −− −−= −−=−−== ∑ ∑∑∑∑∑ − = − = − = − = − = − = Método Bolha: Complexidade 10 } Movimentações – M(n): )n(C)n(M = #define Troca(A, B) {Item c = A; A = B; B = c; } Método Bolha 11 } Vantagens } Algoritmo simples } Algoritmo estável } Desvantagens } Não adaptável } Muitas trocas de itens Variação Bolha: Ordenação Par-Ímpar 12 void ParImpar(Item *v, int n) { int ordenado = 0; while(!ordenado) { ordenado = 1; for(int i = 0; i < n-1; i += 2) if(v[i] > v[i+1]) { Troca(v[i], v[i+1]); ordenado = 0; } for (int i = 1; i < n-1; i += 2) if(v[i] > v[i+1]) { Troca(v[i], v[i+1]); ordenado = 0; } } Método Seleção 13 } Seleção do n-ésimo menor (ou maior) elemento da lista } Troca do n-ésimo menor (ou maior) elemento com a n-ésima posição da lista } Uma única troca por vez é realizada Método Seleção 14 void Selecao (Item *v, int n){ int i, j, Min; for (i = 0; i < n - 1; i++) { Min = i; for (j = i + 1 ; j < n; j++) if (v[j].chave < v[Min].chave) Min = j; Troca(v[i], v[Min]); } } E X E M P L O E X E M P L O E E X M P L O E E L M P X O E E L M P X O E E L M O X P E E L M O P X E E L M O P X Método Seleção: Complexidade 15 } Comparações – C(n): } Movimentações – M(n): )n(Onn )n()n)(n()n(n in)i()n()n(C n i n i n i n i 2 2 2 0 2 0 2 0 2 0 2 1 2 211 1111 = − = −− −− −−= −−=+++−= ∑∑∑∑ − = − = − = − = )n(O)n()n(M =−= 1 Método Seleção 16 } Vantagens: } Custo linear no tamanho da entrada para o número de movimentos de registros – a ser utilizado quando há registros muito grandes; } Desvantagens: } Não adaptável (não importa se o arquivo está parcialmente ordenado); } Algoritmo não é estável; Método Inserção 17 } Algoritmo utilizado pelo jogador de cartas } As cartas são ordenadas da esquerda para direita uma a uma. } O jogador escolhe a segunda carta e verifica se ela deve ficar antes ou na posição que está. } Depois a terceira carta é classificada, deslocando-a até sua correta posição. } O jogador realiza esse procedimento até ordenar todas as cartas. Método Inserção 18 Método Inserção 19 void Insercao(Item *v, int n) { int i,j; Item aux; for (i = 1; i < n; i++) { aux = v[i]; j = i - 1; while (( j >= 0 ) && (aux.Chave < v[j].Chave)) { v[j + 1] = v[j]; j--; } v[j + 1] = aux; } } E X E M P L O E X E M P L O E E X M P L O E E M X P L O E E M P X L O E E L M P X O E E L M O P X Método Inserção: Complexidade 20 } Comparações – C(n): } Anel interno: i-ésima iteração, valor de Ci: } melhor caso: Ci = 1 } pior caso: Ci = i } caso médio: Ci = (1 + 2 + 3 + ... + i) / i } Para o caso médio, assume-se que todas as permutações de entrada são igualmente prováveis. 2 1 2 )1(11 1 + =⎟ ⎠ ⎞ ⎜ ⎝ ⎛ +== ∑ = iii i k i C i k i Método Inserção: Complexidade 21 } Comparações – C(n): } Anel externo: } Complexidade total: } Melhor caso (itens já estão ordenados) } Pior caso (itens em ordem reversa): ∑ − = 1 1 n i iC ∑ − = =−== 1 1 )(11)( n i nOnnC )( 222 ))(1()( 2 1 1 2 nOnnnninC n i =−= − ==∑ − = Método Inserção: Complexidade 22 } Comparações – C(n): } Caso médio: ⎟ ⎠ ⎞ ⎜ ⎝ ⎛ −+ − =⎟ ⎠ ⎞ ⎜ ⎝ ⎛ += + =∑ ∑∑ − = − = − = )1( 2 )1( 2 11 2 1 2 1)( 1 1 1 1 1 1 nnniinC n i n i n i )( 2 1 442 1 4 2 22 nOnnnnn =−+=⎟⎟ ⎠ ⎞ ⎜ ⎜ ⎝ ⎛ ⎟ ⎠ ⎞ ⎜ ⎝ ⎛ −+⎟⎟ ⎠ ⎞ ⎜⎜ ⎝ ⎛ − = Método Inserção: Exemplos 23 } Melhor Caso: } Pior Caso: 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 6 5 4 3 2 1 5 6 4 3 2 1 4 5 6 3 2 1 3 4 5 6 2 1 2 3 4 5 6 1 1 2 3 4 5 6 Método Inserção 24 } Vantagens: } É o método a ser utilizado quando o arquivo está “quase” ordenado. } É um bom método quando se deseja adicionar uns poucos itens a um arquivo ordenado, pois o custo é linear. } O algoritmo de ordenação por inserção é estável. } Desvantagens: } Alto custo de movimentação de elementos no vetor. Ordenação Interna: Sumário 25 } Métodos Simples: } Adequados para pequenos arquivos. } Requerem O(n2) comparações. } Produzem programas pequenos. } Métodos Eficientes: } Adequados para arquivos maiores. } Requerem O(n log n) comparações. } As comparações são mais complexas nos detalhes. } Métodos simples são mais eficientes para pequenos arquivos. Perguntas 26 } Como adicionar estabilidade: Sedgewick p. 259 AEDSII_aula_016_shellsort.pdf Ordenação: Shellsort Algoritmos e Estruturas de Dados II Introdução } Proposto por Donald Shell em 1959. } Extensão do algoritmo de ordenação inserção. 2 Motivação 3 while (( j >= 0 ) && (aux.Chave < v[j].Chave)) { v[j + 1] = v[j]; j--; } 11 comp. 24 comp. Motivação 4 } Problema do método de inserção } Troca itens adjacentes para determinar o ponto de inserção. } São efetuadas n - 1 comparações e movimentações quando o menor item está na posição mais à direita no vetor. } O método de Shell contorna este problema permitindo trocas de registros distantes um do outro. Algoritmo 5 } Os itens separados de h posições são rearranjados. } Todo h-ésimo item leva a uma sequência ordenada. } Tal sequência é dita estar h-ordenada. } Quando h=1, o algoritmo é equivalente ao algoritmo de inserção Exemplo 6 } Entrada: O R D E N A } h = 4, 2, 1 Escolha do h 7 } Sequência para h: } A sequência corresponde a 1, 4, 13, 40, 121, 364, 1093, 3280, ... } Knuth (1973, p. 95) mostrou experimentalmente que esta sequência é difícil de ser batida por mais de 20% em eficiência. 1s para 113 1 s para ,1)( >+= == )h(s- h(s) sh ShellSort void Shellsort (Item* A, int n) { int i, j; int h = 1; Item aux; do /* calcula o h inicial */ h = h * 3 + 1; while (h < n); do { h = (h-1/3); /* atualiza o valor de h */ for(i = h; i < n; i++) { aux = A[i]; j = i; /* efetua comparações entre elementos com distância h */ while (A[j – h].Chave > aux.Chave) { A[j] = A[j – h]; j -= h; if (j < h) break; } A[j] = aux; } } while (h != 1); } ShellSort n Análise q A razão da eficiência do algoritmo ainda não é conhecida. q Ninguém ainda foi capaz de analisar o algoritmo. q A sua análise contém alguns problemas matemáticos muito difíceis. q A começar pela própria seqüência de incrementos. q O que se sabe é que cada incremento não deve ser múltiplo do anterior. ShellSort n Análise q Conjecturas referente ao número de comparações para a seqüência de Knuth: Conjectura 1 : C(n) = O(n1,25) Conjectura 2 : C(n) = O(n (ln n)2 ) ShellSort n Vantagens: q Shellsort é uma ótima opção para arquivos de tamanho moderado. q Sua implementação é simples e requer uma quantidade de código pequena. n Desvantagens: q O tempo de execução do algoritmo é sensível à ordem inicial do arquivo. q O método não é estável. Exercícios 12 1. Mostre um exemplo de entrada que demonstra que o método de ordenação seleção não é estável. 2. Mostre um exemplo que demonstra que o Shellsort é instável para sequencia h=1,2 3. O método da bolha não é adaptável, altere o código para que ele se torne adaptável. 4. Qual dos métodos: bolha, inserção e seleção executa menos comparações para um vetor de entrada contendo valores idênticos. Exercícios 13 1. Dê um exemplo de um vetor com N elementos que maximiza o número de vezes que o mínimo é atualizado no método de ordenação seleção. 2. Mostre um exemplo de entrada que demonstra que o método de ordenação seleção não é estável. 3. Mostre um exemplo que demonstra que o Shellsort é instável para sequencia h=1,2 4. O método da bolha não é adaptável, altere o código para que ele se torne adaptável. 5. Qual dos métodos: bolha, inserção e seleção executa menos comparações para um vetor de entrada contendo valores idênticos. 14 AEDSII_aula_017_quicksort.pdf Ordenação: Quicksort Algoritmos e Estruturas de Dados II Introdução 2 } É o algoritmo de ordenação interna mais rápido que se conhece para uma ampla variedade de situações. } Provavelmente é o mais utilizado. } A ideia básica é dividir o problema de ordenar um conjunto com n itens em dois problemas menores. } Os problemas menores são ordenados independentemente. } Os resultados são combinados para produzir a solução final. Introdução 3 } A parte mais delicada do método é o processo de partição. } O vetor A [Esq ... Dir] é rearranjado por meio da escolha arbitrária de um pivô x. } O vetor A é particionado em duas partes: } Parte esquerda: chaves ≤ x. } Parte direita: chaves > x. Exemplo 4 x Dificuldades na escolha do pivô 5 Algoritmo – Particionamento 6 } Algoritmo para particionamento: 1. Escolha arbitrariamente um pivô x, troque com elemento mais à direita. 2. Seta p (divisão da partição) para esquerda do vetor 3. Percorre o vetor da esquerda para direita 1. Se elemento A[i] ≤ pivô x 1. Troca A[i] pelo elemento A[p] 2. Incrementa posição da divisão da partição, p 4. Troca pivô da direita para atual divisão da partição 5. Retorna a posição que divide a partição. Algoritmo – Após Partições 7 } Ao final de cada particionamento, os elementos do vetor A[esq … dir] estão } A[esq], A[esq +1], …, A[j]: menores ou iguais a x } A[i], A[i+1],…,A[dir]: maiores que x } Uma vez obtidas as partições, cada uma deve ser ordenada – recursivamente. ≤ x x > x Particionamento 8 int particiona(Item a[], int e, int d) { int i, p; /* contém índice da divisão da partição */ Item pivo = a[d]; p = e; for (i = e; i < d; i++) { if (a[i].chave <= pivo.chave) Troca(a[i],a[p++]); } Troca(a[p],a[d]); return p; } Particionamento - Exemplo 9 2 8 7 1 3 5 6 4 2 8 7 1 3 5 6 4 i p 2 8 7 1 3 5 6 4 i p 2 8 7 1 3 5 6 4 i p 2 1 7 8 3 5 6 4 i p 2 1 3 8 7 5 6 4 i p 2 1 3 8 7 5 6 4 i p 2 1 3 4 7 5 6 8 Algoritmo – Recursão 10 } Cuidados para que a execução termine } Não chamar a recursão para apenas um elemento. } Chamar a recursão para partições menores que a atual. void ordena(Item a[], int e, int d) { int i; if (d < e) return; /* condição de parada */ i = particiona(a, e, d); /* i: índice do pivô */ ordena(a, e, i-1); /* ordena partição esquerda */ ordena(a, i+1, d); /* ordena partição direita */ } void quicksort(Item a[], int n) { ordena(a, 0, n-1); } Quicksort - Exemplo } O pivô x é escolhido como sendo A[d]. } Exemplo: Algoritmos e Estrutura de Dados II 3 6 4 5 1 7 2 Quicksort - Exemplo Algoritmos e Estrutura de Dados II 3 6 4 5 1 7 2 1 2 4 5 3 7 6 Primeira partição 1 2 4 3 5 7 6 ordena 1 2 4 5 3 6 7 segunda partição 1 2 4 5 3 7 6 Quicksort - Exemplo 13 1 2 4 5 3 6 7 1 2 4 5 3 6 7 1 2 4 5 3 6 7 ordena 1 2 4 5 3 6 7 terceira partição 1 2 3 4 5 6 7 Quicksort - Exemplo 14 1 2 3 4 5 6 7 ordena 1 2 3 4 5 6 7 1 2 3 4 5 6 7 quarta partição 1 2 3 4 5 6 7 1 2 3 4 5 6 7 ordena 1 2 3 4 5 6 7 final Quicksort } Características } Qual o pior caso para o Quicksort? Qual sua ordem de complexidade? } Qual o melhor caso? } O algoritmo é estável? Algoritmos e Estrutura de Dados II Quicksort – Análise 16 } Seja C(n) a função que conta o número de comparações. } Pior caso: C(n) = O(n2) } O pior caso ocorre quando, sistematicamente, o pivô é escolhido como sendo um dos extremos de um arquivo já ordenado. } Isto faz com que o procedimento Ordena seja chamado recursivamente n vezes, eliminando apenas um item em cada chamada. Quicksort – Análise } Melhor caso: C(n) = 2C(n/2) + n = O(n log n) } Esta situação ocorre quando cada partição divide o arquivo em duas partes iguais. } Caso médio de acordo com Sedgewick e Flajolet (1996, p. 17): C(n) ≈ 1,386n log n – 0,846n, } Isso significa que em média o tempo de execução do Quicksort é O(n log n). Algoritmos e Estrutura de Dados II Melhorias no Quicksort } Escolha do pivô: mediana de três } Evita o pior caso } Depois da partição, trabalhar primeiro no subvetor de menor tamanho } Diminui o crescimento da pilha } Utilizar um algoritmo simples (seleção, inserção) para partições de tamanho pequeno } Quicksort não recursivo } Evita o custo de várias chamadas recursivas Quicksort não recursivo 19 void quicksortNR(Item a[], int n) { int i, e, d; TipoPilha p; FPVazia(&p); Empilha(&p, n-1); Empilha(&p, 0); while (Vazia(&p) == 0) { e = Desempilha(&p); d = Desempilha(&p); i = particiona(a, e, d); /* partição esquerda */ if (i-1 > e) { Empilha(&p, i-1); Empilha(&p, e);} /* partição direita */ if (d > i+1) { Empilha(&p, d); Empilha(&p, i+1); } } Quicksort Algoritmos e Estrutura de Dados II } Vantagens: } É extremamente eficiente para ordenar arquivos de dados. } Necessita de apenas uma pequena pilha como memória auxiliar. } Requer cerca de n log n comparações em média para ordenar n itens. } Desvantagens: } Tem um pior caso O(n2) comparações. } Sua implementação é muito delicada e difícil: } Um pequeno engano pode levar a efeitos inesperados para algumas entradas de dados. } O método não é estável. Seleção usando Quicksort 21 } Objetivo } Encontrar o k-ésimo menor elemento sem ordenar todo o vetor. } Algoritmo coloca o elemento ordenado na k-ésima posição do vetor. void seleciona(Item a[], int e, int d, int k) { int i; if (d <= e) return; /* condição de parada */ i = particiona(a, e, d); /* i: índice do pivô */ if (k < i) seleciona(a, e, i-1,k); /*k-th na esquerda*/ if (k > i) seleciona(a, i+1, d); /* k-th na direita */ } Exercícios 22 1. Mesmo com o uso da estratégia da mediana, mostre um vetor de entrada que cai no pior caso do quicksort. 2. Por que não usar o algoritmo de ordenação por seleção para identificar o k-ésimo menor elemento do vetor? AEDSII_aula_018_heapsort.pdf Ordenação: Heapsort Algoritmos e Estruturas de Dados II Introdução 2 } Possui o mesmo princípio de funcionamento da ordenação por seleção. } Selecione o menor item do vetor } Troque-o pelo item da primeira posição } Repita operação com os elementos restantes do vetor } Implementação direta } Encontrar o menor elemento requer n-1 comparações } Ideia: } Utilização de uma fila de prioridades implementada com um heap. Fila de Prioridades 3 } Definição: } Estrutura de dados composta de chaves, que suporta duas operações básicas: inserção de um novo item e remoção do item com a maior chave. } A chave de cada item reflete a prioridade em que se deve tratar aquele item. } Aplicações: } Sistemas operacionais, sistema de memória (paginação). Fila de Prioridades 4 } Operações } Constrói a fila de prioridade com N itens } Insere um novo item } Retira o maior item } Altera a prioridade de um item Fila de Prioridades 5 } Representações } Lista sequencial ordenada, não ordenada e heap. Constrói Insere Retira máximo Lista ordenada O(N log N) O(N) O(1) Lista não ordenada O(N) O(1) O(N) heaps O(N log N) O(log N) O(log N) Fila de Prioridades 6 } Algoritmos de ordenação com fila de prioridades } Utiliza operação insere para adicionar todas as N chaves } Utiliza a operação retira máximo para receber uma lista na ordem reversa. Algoritmo Lista ordenada Lista não ordenada heaps Fila de Prioridades 7 } Algoritmos de ordenação com fila de prioridades } Utiliza operação insere para adicionar todas as N chaves } Utiliza a operação retira máximo para receber uma lista na ordem reversa. Algoritmo Lista ordenada Inserção Lista não ordenada Seleção heaps Heapsort Heap 8 } Representação vetorial A[1], A[2], ..., A[n] } Será um heap se A[i] ≥A[2i] e A[i] ≥ A[2i+1] para todo i = 1, 2, 3, ..., n/2. } Representação de árvore binária } Será um heap se cada nó for maior ou igual seus filhos. Heap 9 } Representação vetorial para de árvore } Nós são numerados de 1 a n } O primeiro é chamado raiz } O nó k/2 é o pai do nó k, 1 < k ≤ n } Os nós 2k e 2k+1 são filhos da esquerda e direita do nó k, para 1 ≤ k ≤ n/2. Heap 10 } Representação por meio de vetores é compacta } Permite caminhar pelos nós da árvore facilmente } Filhos de um nó i estão nas posições 2i e 2i + 1 } O pai de um nó i está na posição i/2 } A maior chave sempre está na posição 1 Heap 11 } Restauração da condição de heap (adição no fim) } Garantir que o valor da chave do pai é maior que dos filhos. } Testa se todos os elementos A[2i] e A[2i+1] são menores ou igual a A[i]. R O D E N A S 1 2 3 4 5 6 7 1 2 3 4 5 6 7 R O D E N A S R O S E N A D S O R E N A D O R D E N A R O D E N A Heap 12 } Restauração da condição de heap (árvore) O R D E N A S 1 2 3 4 5 6 7 O R D E N A S 1 2 3 6 5 4 7 O R D E N A S 1 2 3 6 5 4 7 O R S E N A D 1 2 3 6 5 4 7 1 2 3 S R O E N A D 1 2 3 6 5 4 7 4 Heap – Refaz a condição de heap 13 void RefazBaixoCima(Item A[], int k) { /* se pai for menor que filho, troca */ while (k > 1 && A[k/2] < A[k]) { Troca(A[k], A[k/2]); /* vai para o pai e repete o processo */ k = k / 2; } Heap 14 } Restauração da condição de heap (remoção) } Garantir que o valor da chave do pai é maior que dos filhos. Cima para baixo, restaurando o heap 1 2 3 4 5 6 7 S O R E N A D S O R E N A D D O R E N A R O D E N A Heap – Refaz a condição de heap 15 void RefazCimaBaixo(Item A[], int k, int Dir) { int j; while (2*k <= Dir) { j = 2*k; /* encontra maior filho */ if (j < Dir && A[j] < A[j+1]) j++; /* testa se pai é maior que filho */ if (A[k] >= A[j]) break; /* pai é menor que filho, troca posições */ Troca(A[k], A[j]); k = j; } } Heap – Construção do heap 16 void Constroi(Item A[], int N) { int Esq; /* inicia na metade do vetor */ Esq = N / 2 + 1; while (Esq > 1) { Esq--; RefazCimaBaixo(A, Esq, N); } Fila de Prioridade - Operações 17 Int N; /* controla número de elementos na fila */ Item pq[MaxN]; /* contém os dados */ /* inicia fila de prioridade */ Void FPInicia() { N = 0; } /* insere um elemento */ void FPInsere(Item v) { A[++N] = v; RefazBaixoCima(pq, N); } /* remove elemento com valor máximo */ Item FPRemoveMaximo() { Troca(pq[1], pq[N]); RefazCimaBaixo(pq, 1, N-1); return pq[N--]; } Ordenação com fila de prioridades 18 Void FPSort(Item A[], int n) { int k; FPInicia(); /* insere elementos na fila de prioridade */ for (k = 0; k < n; k++) FPInsere(A[k]); /* remove maiores elementos da fila de prioridade */ for (k = n-1; k >= 0; k--) a[k] = FPRemoveMaximo(); } Heapsort 19 void Heapsort(Item A[], int n) { int Esq, Dir; Item x; Constroi(A, n); /* constroi o heap */ Esq = 1; Dir = n; /* assumindo elementos em A[1,...,n]*/ /* ordena o vetor */ while (Dir > 1) { Troca(A[1], A[Dir]); Dir--; RefazCimaBaixo(A, Esq, Dir); } } Heapsort – Análise Algoritmos e Estrutura de Dados II } O procedimento RefazCimaBaixo gasta cerca de log n operações no pior caso. } Logo, o heapsort gasta um tempo proporcional a O(n log n), no pior caso. Heapsort 21 } Vantagens } O(n log n). } Desvantagens } Não é estável. Exercícios 22 1. Por que não usar o algoritmo de ordenação por seleção para identificar o k-ésimo menor elemento do vetor? 2. Mesmo com o uso da estratégia da mediana, mostre um vetor de entrada que cai no pior caso do quicksort. 3. Um vetor com elementos em ordem reversa é um heap? 4. Mostre que o heapsort não é estável. AEDSII_aula_018_mergesort.pdf Ordenação: Mergesort Algoritmos e Estruturas de Dados II Introdução 2 } Baseado em merging } Combinação de dois vetores ordenados em um vetor maior que também esteja ordenado } Quicksort vs. Mergesort } Quicksort: } divide o vetor em vetores independentes } Indexação da posição do pivô + duas chamadas recursivas } Mergesort: } une dois vetores para criar um único } Duas chamadas recursivas + procedimento para unir vetores Merging 3 } Dois vetores a e b ordenados para um vetor c } Ideia } Escolhe para c, o menor de dos elementos que ainda não foram escolhidos dos vetores a e b. mergeAB(Item c[], Item a[], int N, Item b[], int M ) { int i, j, k; for (i = 0, j = 0, k = 0; k < N+M; k++) { if (i == N) { c[k] = b[j++]; continue; } if (j == M) { c[k] = a[i++]; continue; } if (a[i].chave < b[j].chave) c[k] = a[i++]; else c[k] = b[j++]; } } Merging 4 } Problema } Há dois testes no laço interno. } Dois vetores separados são passados (a e b) } Solução } Para evitá-los, copia um dos vetores em ordem reversa e o percorre da direita para esquerda. } Passa vetor único, indicando o índice do último elemento do vetor da esquerda (variável m). Merging 5 Item aux[maxN]; merge(Item a[], int e, int m, int d){ int i, j, k; /* copia a e b (reverso) para vetor auxiliar */ for (i = 0; i <= m; i++) aux[i] = a[i]; for (j = m+1; j <= d; j++) aux[d-m+j+1] = a[j]; i = e; j = d; for (k = e; k <= d; k++) if (aux[i].chave <= aux[j].chave) a[k] = aux[i++]; else a[k] = aux[j--]; } Mergesort 6 void mergesort(Item a[], int e, int d) { int m = (d+e)/2; if (d <= e) return; mergesort(a, e, m); mergesort(a, m+1, d); merge(a, e, m, d); } Mergesort não Recursivo 7 #define min(A, B) (A < B) ? A : B void mergesortBU(Item a[], int e, int d) { int i, m; for (m = 1; m < d-e; m = m+m) for (i = e; i <= d-m; i += m+m) merge(a, i, i+m-1, min(i+m+m-1, d)); } Considerações 8 } Vantagens } Ordena vetor com N elementos e tempo proporcional a NlogN, não importa a entrada. } Deve ser considerado quando alto custo de pior caso não pode ser tolerável. } Método de ordenação estável. } Desvantagens } Requer espaço extra proporcional a N. } Não é adaptável (tempo de execução independe dos dados da entrada). AEDSII_aula_019_radixsort.pdf Ordenação: Radixsort Algoritmos e Estruturas de Dados II Radixsort } Algumas vezes, a comparação entre chaves pode ser uma operação muito cara } Existem alguns algoritmos de ordenação sem comparação de chaves } Bucket Sort } Counting Sort } Tally Sort } etc... } Radix Sort é uma classe de algoritmos que usa a representação binária das chaves para a ordenação Introdução 3 } Até agora vimos métodos de ordenação que comparam chaves } Esta é uma abordagem geral que funciona para qualquer tipo de chave } Uma abordagem alternativa para ordenação é processar as chaves por partes } Por exemplo, começamos pelas primeiras letras do nome quando procuramos um nome num catálogo } Não precisamos comparar chaves Ideia 4 } Quebrar uma chave em vários pedaços } Dígitos de um número em uma dada base (radix) } 312 tem os dígitos 3, 1 e 2 na base 10 } 312 tem os dígitos 100111000 na base 2 } “exemplo” tem 6 caracteres (base 256) } Ordenar de acordo com o primeiro pedaço } Números cujo dígito mais à esquerda começa com 0 vêm antes de números cujo dígito mais à esquerda é 1 } Podemos ordenar repetindo esse processo para todos os pedaços 5 Algoritmos e Estrutura de Dados II Radixsort } Idéia Geral: } chaves cujo bit mais a esquerda é 0, vem antes que chaves cujo bit é 1 } Repetindo-se isso para todos os bits de forma adequada é possível ordenar } Como extrair os bits } Formas eficientes em c: and (&) e shift(>>) } bit = x & 00000001; x >> 1; } Formas simples: divisão e resto } Considerando bits indexados de 0 a n da dir para esq, para extrair o bit i de um número X, temos (X / 2i) % 2 Algoritmos e Estrutura de Dados II Radix Exchange Sort } Algoritmo analisa os bits da esquerda para a direita } Funcionamento similar ao do Quicksort, mas a partição é feita comparando-se bits ao invés de chaves } Chamadas recursivas ordenando os subvetores pelo bit i-1 } Complexidade: } O(n * b) comparações de bits Algoritmos e Estrutura de Dados II Radix Exchange Sort quicksortB(int a[], int l, int r, int w) { int i = l, j = r; if (r <= l || w > 0) return; while (j != i) { while (digit(a[i], w) == 0 && (i < j)) i++; while (digit(a[j], w) == 1 && (j > i)) j--; exch(a[i], a[j]); } if (digit(a[r], w) == 0) j++; quicksortB(a, l, j-1, w+1); quicksortB(a, j, r, w+1); } void sort(Item a[], int l, int r) { quicksortB(a, l, r, numbits - 1); } Fonte: Algorithms in C Robert Sedgewick Algoritmos e Estrutura de Dados II Exemplo [ 3 2 5 6 2 0 7] [ 011 010 101 110 001 000 111] Algoritmos e Estrutura de Dados II Exemplo [ 3 2 5 6 2 0 7] [ 011 010 101 110 001 000 111] [ 011 010 101 110 001 000 111] [ 011 010 000 001 110 101 111] [ 001 000 010 011] [ 000 001] [010 011] [101 110 111] [110 111] [ 0 1 2 3 5 6 7] Radixsort – Ordenando um dígito 11 } Contar quantos elementos existem de cada valor 123 142 087 263 233 014 132 Digito Contador 0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9 0 Radixsort – Ordenando um dígito 12 } Para isso iremos contar quantos elementos existem de cada valor 123 142 087 263 233 014 132 Digito Contador 0 0 1 0 2 2 3 3 4 1 5 0 6 0 7 1 8 0 9 0 Radixsort – Ordenando um dígito 13 } Depois calcular a posição deles no vetor ordenado 123 142 087 263 233 014 132 Dig C Posicao 0 0 0 1 0 0 2 2 0 3 3 2 4 1 5 5 0 0 6 0 0 7 1 6 8 0 0 9 0 0 Radixsort – Ordenando um dígito 14 } E finalmente colocar os elementos em suas posições 123 142 087 263 233 014 132 123 Dig C Posicao 0 0 0 1 0 0 2 2 0 3 3 3 4 1 5 5 0 0 6 0 0 7 1 6 8 0 0 9 0 0 Radixsort – Ordenando um dígito 15 } Para isso iremos contar quantos elementos existem de cada valor 123 142 087 263 233 014 132 142 123 Dig C Posicao 0 0 0 1 0 0 2 2 1 3 3 3 4 1 5 5 0 0 6 0 0 7 1 6 8 0 0 9 0 0 Radixsort – Ordenando um dígito 16 } Para isso iremos contar quantos elementos existem de cada valor 123 142 087 263 233 014 132 142 123 087 Dig C Posicao 0 0 0 1 0 0 2 2 1 3 3 3 4 1 5 5 0 0 6 0 0 7 1 7 8 0 0 9 0 0 Radixsort – Ordenando um dígito 17 } Para isso iremos contar quantos elementos existem de cada valor 123 142 087 263 233 014 132 142 123 263 087 Dig C Posicao 0 0 0 1 0 0 2 2 1 3 3 4 4 1 5 5 0 0 6 0 0 7 1 7 8 0 0 9 0 0 Radixsort – Ordenando um dígito 18 } Para isso iremos contar quantos elementos existem de cada valor 123 142 087 263 233 014 132 142 123 263 233 087 Dig C Posicao 0 0 0 1 0 0 2 2 1 3 3 5 4 1 5 5 0 0 6 0 0 7 1 7 8 0 0 9 0 0 Radixsort – Ordenando um dígito 19 } Para isso iremos contar quantos elementos existem de cada valor 123 142 087 263 233 014 132 142 123 263 233 014 087 Dig C Posicao 0 0 0 1 0 0 2 2 1 3 3 5 4 1 6 5 0 0 6 0 0 7 1 7 8 0 0 9 0 0 Radixsort – Ordenando um dígito 20 } Para isso iremos contar quantos elementos existem de cada valor 123 142 087 263 233 014 132 142 132 123 263 233 014 087 Dig C Posicao 0 0 0 1 0 0 2 2 1 3 3 5 4 1 6 5 0 0 6 0 0 7 1 7 8 0 0 9 0 0 Radixsort – Ordenando o vetor 21 } Repetimos o mesmo processo para o próximo dígito } Funciona por que o método do contador que usamos anteriormente é estável! 123 142 087 263 233 014 132 142 132 123 263 233 014 087 014 123 132 233 142 263 087 Radixsort – Ordenando o vetor 22 } Repetimos o mesmo processo para o próximo dígito } Funciona por que o método do contador que usamos anteriormente é estável! 123 142 087 263 233 014 132 142 132 123 263 233 014 087 014 123 132 233 142 263 087 014 087 123 132 142 233 263 Radixsort 23 void radix(int *v, int n, int base, int num_digitos) { int i, j, w, count[base+1], d, idx; int *aux = (int *) malloc(n * sizeof(int)); for(w = 0; w < num_digitos; w++) { for(j = 0; j < base; j++) count[j] = 0; // zera contador for(i = 0; i < n; i++) { // conta dígitos d = digito(v[i], w, base); count[d+1]++; } // seta índices para os dígitos for(j = 1; j < base; j++) count[j] += count[j-1]; for(i = 0; i < n; i++) { // adiciona nas posições d = digito(v[i], w, base); idx = count[d]; count[d] += 1; aux[idx] = v[i]; } for(i = 0; i < n; i++) v[i] = aux[i]; // retorna p/ v } } Radixsort – Análise 24 } Nenhuma comparação } Inspeções de dígitos: } 2*n*num_digitos } Se num_digitos for pequeno ou constante, então radixsort tem custo linear O(n) } Trocas: } n*num_digitos } Número de trocas também é O(n) Vantagens e desvantagens 25 } Vantagens: } Estável } Não compara as chaves } Desvantagens: } Nem sempre é fácil otimizar a inspeção de dígitos } Depende do hardware } Só é bom se o número de dígitos for pequeno Exercícios 26 1. Por que não usar o algoritmo de ordenação por seleção para identificar o k-ésimo menor elemento do vetor? 2. Mesmo com o uso da estratégia da mediana, mostre um vetor de entrada que cai no pior caso do quicksort. 3. Um vetor com elementos em ordem reversa é um heap? 4. Mostre que o heapsort não é estável. 5. Como seria a implementação do radixsort utilizando o TAD fila? Radixsort – Análise 27 } Nenhuma comparação } Inspeções de dígitos: } 2*n*num_digitos } Se num_digitos for pequeno ou constante, então radixsort tem custo linear O(n) } Trocas: } n*num_digitos } Número de trocas também é O(n) } Quicksort é comparável ao radixsort porque o número de dígitos é da ordem de lg(n) na base 2 e log10(n) na base 10 AEDSII_aula_019a_comparacao_ordenacao.pdf Comparação entre os métodos de ordenação Algoritmos e Estruturas de Dados II Número de comparações 2 Método Complexidade Inserção O(n2) Seleção O(n2) Bolha O(n2) Shellsort O(n lg(n)2) Quicksort O(n lg(n)) Heapsort O(n lg(n)) Radixsort O(kn) Tempo de execução 3 } O método que levou menos tempo real para executar recebeu o valor 1 e os outros receberam valores relativos } Elementos em ordem aleatória: Tempo de execução 4 } Elementos em ordem crescente Tempo de execução 5 } Elementos em ordem decrescente Observações 6 } Observações sobre os métodos: } 1. Shellsort, quicksort e heapsort têm a mesma ordem de grandeza para arranjos de até 30 mil elementos } 2. O quicksort é o mais rápido para todos os tamanhos aleatórios experimentados } 3. A relação heapsort/quicksort se mantém constante para todos os tamanhos de entrada } 4. A relação shellsort/quicksort aumenta com o tamanho da entrada } 5. Para arquivos pequenos (500 elementos), o shellsort é mais rápido que o heapsort (e vice versa) } 6. Inserção é o método mais rápido para qualquer tamanho se os elementos estão ordenados (e vice versa) } 7. Entre os algoritmos de custo quadrático, o inserção é melhor para entradas aleatórias Influência da ordem inicial dos elementos 7 } 1. O shellsort é bastante sensível à ordenação ascendente ou descendente da entrada } 2. Em arquivos do mesmo tamanho, o shellsort executa mais rápido para arquivos ordenados } 3. Em arquivos do mesmo tamanho, o quicksort executa mais rápido para arquivos ordenados } 4. O heapsort não depende da ordenação da entrada Inserção 8 } É o mais interessante para arquivos com menos do que 20 elementos } O método é estável } Possui comportamento melhor do que o método da bolha que também é estável } Sua implementação é tão simples quanto as implementações do bolha e seleção } Para arquivos já ordenados, o método é O(n) } O custo é linear para adicionar alguns elementos a um arquivo já ordenado Seleção 9 } É vantajoso quanto ao número de movimentos de registros, que é O(n) } Deve ser usado para arquivos com elementos muito grandes, desde que o número de elementos a ordenar seja pequeno Shellsort 10 } Bom para ordenar um número moderado de elementos } Quando encontra um arquivo parcialmente ordenado trabalha menos Quicksort 11 } É o algoritmo mais eficiente que existe para uma grande variedade de situações } O algoritmo é recursivo, o que demanda uma pequena quantidade de memória adicional } Pior caso realiza O(n2) operações } O principal cuidado a ser tomado é com relação à escolha do pivô } A escolha do elemento do meio do arranjo melhora o desempenho quando o arquivo está total ou parcialmente ordenado } O pior caso tem uma probabilidade muito pequena de ocorrer quando os elementos forem aleatórios } Geralmente se usa a mediana de uma amostra de três elementos para evitar o pior caso } Usar inserção em partições pequenas melhora desempenho significativamente Heapsort 12 } É um método de ordenação elegante e eficiente } Não necessita de nenhuma memória adicional } Executa sempre em tempo proporcional a O(n lg(n)) } Aplicações que não podem tolerar eventuais variações no tempo esperado de execução devem usar o heapsort AEDSII_aula_020_pesquisa_sequencial_e_binaria_arvore_bin_pesq.pdf Pesquisa em Memória Primária Algoritmos e Estruturas de Dados II Pesquisa em Memória Primária 2 } Pesquisa: } Recuperação de informação em um grande volume de dados. } Informação é dividida em registros e cada registro contém uma chave. } Objetivo: } Encontrar itens com chaves iguais à chave dada na pesquisa. } Aplicações: } Contas em um banco } Reservas de uma companhia aérea Pesquisa em Memória Primária 3 } Escolha do método de busca } Quantidade de dados envolvidos. } Frequência com que operações de inserção e retirada são efetuadas. } Métodos de pesquisa: } Pesquisa sequencial } Pesquisa binária } Árvore de pesquisa } Árvores binárias de pesquisa sem balanceamento } Árvores binárias de pesquisa com balanceamento } Pesquisa digital } Hashing Tabelas de Símbolos 4 } Estrutura de dados contendo itens com chaves que suportam duas operações } Inserção de um novo item } Retorno de um item que contém uma determinada chave. } Tabelas são também conhecidas como dicionários } Chaves – palavras } Item – entradas associadas as palavras (significado, pronúncia) Tipo Abstrato de Dados 5 n Considerar os algoritmos de pesquisa como tipos abstratos de dados (TADs), com um conjunto de operações associado a uma estrutura de dados, n Há independência de implementação para as operações. } Operações: } Inicializar a estrutura de dados } Pesquisar um ou mais registros com uma dada chave } Inserir um novo registro } Remover um registro específico } Ordenar os registros Pesquisa Sequencial 6 } Método de pesquisa mais simples } A partir do primeiro registro, pesquisa sequencialmente até encontrar a chave procurada } Registros ficam armazenados em um vetor (arranjo). } Inserção de um novo item } Adiciona no final do vetor. } Remoção de um item com chave específica } Localiza o elemento, remove-o e coloca o último item do vetor em seu lugar. Pesquisa Sequencial 7 # define MAX 10 typedef int TipoChave; typedef struct { TipoChave Chave; /* outros componentes */ } Registro; typedef int Indice; typedef struct { Registro Item[MAX + 1]; Indice n; } Tabela; Pesquisa Sequencial 8 void Inicializa(Tabela *T) { T->n = 0; } /* retorna 0 se não encontrar um registro com a chave x */ Indice Pesquisa(TipoChave x, Tabela *T){ int i; T->Item[0].Chave = x; /* sentinela */ i = T->n + 1; do { i--; } while (T->Item[i].Chave != x); return i; } Pesquisa Sequencial 9 void Insere(Registro Reg, Tabela *T) { if (T->n == MAX) printf("Erro : tabela cheia\n"); else { T->n++; T->Item[T->n] = Reg; } } void Remove(TipoChave x, Tabela *T) { Int idx; idx = Pesquisa(x, T); /* se encontrou o item, troca pelo último, reduz o n */ if (idx) T->Item[idx] = T->Item[T->n--]; } Pesquisa Sequencial 10 } Análise: } Pesquisa com sucesso } melhor caso: C(n) = 1 } pior caso: C(n) = n } caso médio: C(n) = (n+1) / 2 } Pesquisa sem sucesso } C(n) = n + 1 Pesquisa Binária 11 } Redução do tempo de busca aplicando o paradigma dividir para conquistar. 1. Divide o vetor em duas partes 2. Verifica em qual das partes o item com a chave se localiza 3. Concentra-se apenas naquela parte } Restrição: chaves precisam estar ordenadas } Manter chaves ordenadas na inserção pode levar a comportamento quadrático. } Se chaves estiverem disponíveis no início, um método de ordenação rápido pode ser usado. } Trocas de posições podem reduzir a eficiência. Pesquisa Binária 12 } Exemplo: pesquisa pela chave L A A A C E E E G H I L M N P R A A A C E E E G H I L M N P R H I L M N P R H I L L Pesquisa Binária 13 } Exemplo: pesquisa pela chave J A A A C E E E G H I L M N P R A A A C E E E G H I L M N P R H I L M N P R H I L L Pesquisa Binária 14 Indice Binaria(TipoChave x, Tabela *T) { Indice i, Esq, Dir; if (T->n == 0) return 0; /* vetor vazio */ Esq = 1; Dir = T->n; do { i = (Esq + Dir) / 2; if (x > T->Item[i].Chave) Esq = i + 1; /* procura na partição direita */ else Dir = i - 1; /* procura na partição esquerda */ } while ((x != T->Item[i].Chave) && (Esq <= Dir)); if (x == T->Item[i].Chave) return i; else return 0; Pesquisa Binária 15 } Análise } A cada iteração do algoritmo, o tamanho da tabela é dividido ao meio. } Logo, o número de vezes que o tamanho da tabela é dividido ao meio é cerca de log n. } Ressalva } Alto custo para manter a tabela ordenada: a cada inserção na posição p da tabela implica no deslocamento dos registros a partir da posição p para as posições seguintes. } Portanto, a pesquisa binária não deve ser usada em aplicações muito dinâmicas. Árvore Binária de Pesquisa } Árvores de pesquisa mantêm uma ordem entre seus elementos } Raiz é maior que os elementos na árvore à esquerda } Raiz é menor que os elementos na árvore à direita Árvore Binária de Pesquisa struct arvore { struct arvore *esq; struct arvore *dir; Registro reg; }; struct arvore *cria_arvore(Registro reg) { struct arvore *novo; novo = malloc(sizeof(struct arvore)); novo->esq = NULL; novo->dir = NULL; novo->reg = reg; } 0 Árvore Binária de Pesquisa: Busca void Pesquisa(Registro *x, struct arvore *t) { if (t == NULL) { printf("Registro não esta presente na árvore\n"); } else if (x->Chave < t->reg.Chave) Pesquisa(x, t->Esq); /* busca no filho esquerdo */ else if (x->Chave > t->reg.Chave) Pesquisa(x, t->Dir); /* busca no filho direito */ else *x = t->reg; } Árvore Binária de Pesquisa: Busca 8 5 6 4 B 2 A C 1 3 9 7 Árvore Binária de Pesquisa: Inserção } O elemento vai ser inserido como uma folha da árvore de busca } Vamos procurar o lugar de inserção navegando da raiz até a folha onde ele será inserido Árvore Binária de Pesquisa: Inserção 8 5 6 4 B 2 A C 1 3 9 7 4.5 Árvore Binária de Pesquisa: Inserção 8 5 6 4 B 2 A C 1 3 9 7 4.5 Árvore Binária de Pesquisa: Inserção 8 5 6 4 B 2 A C 1 3 9 7 4.5 Árvore Binária de Pesquisa: Inserção 8 5 6 4 B 2 A C 1 3 9 7 4.5 Árvore Binária de Pesquisa: Inserção void insere_elemento(struct arvore *t, Registro reg) { if(reg.Chave < t->reg.Chave) { /* chave menor */ if (t->esq) { insere_elemento(t->esq, reg); } else { /* achou local de inserção */ struct arvore *novo = cria_arvore(reg); t->esq = novo; } } else { /* chave maior ou igual ao nodo atual */ if (t->dir) { insere_elemento(t->dir, reg); } else { struct arvore *novo = cria_arvore(reg); t->dir = novo; } } } Árvore Binária de Pesquisa: Remoção } Remover folhas } Remover nós com um filho } Remover nós com dois filhos 8 5 6 4 B 2 A C 1 3 9 7 Árvore Binária de Pesquisa: Remoção } Nó com 1 filho 8 5 6 4 B 2 A C 1 3 9 7 Árvore Binária de Pesquisa: Remoção } Nó com 1 filho 8 5 6 B2 A C1 3 9 7 Árvore Binária de Pesquisa: Remoção } Nó com 2 filhos 8 5 6 4 B 2 A C 1 3 9 7 Árvore Binária de Pesquisa: Remoção 8 5 7 4 B 2 A C 1 3 9 7 } Nó com 2 filhos Árvore Binária de Pesquisa: Remoção 8 5 7 4 B 2 A C 1 3 9 7 } Nó com 2 filhos Árvore Binária de Pesquisa: Remoção struct arvore *remove(struct arvore *t, TipoChave Chave) { struct arvore *aux; if(t == NULL) { printf(“elemento ausente\n”); } else if(Chave < t->reg.Chave){ t->esq=remove(t->esq, Chave); } else if(Chave > t->reg.Chave){ t->dir=remove(t->dir, Chave); } else if (t->esq == NULL && t->dir == NULL) { free(t); return NULL; /* zero filhos */ } else if(t->esq == NULL) { aux = t->dir; free(t); return aux; /* 1 filho direita */ } else if(t->dir == NULL) { aux = t->esq; free(t); return aux; /* 1 filho esquerda */ } else { /* 2 filhos */ struct arvore *suc = acha_menor(t->dir); t->reg = suc->reg; t->dir = remove(t->dir, suc->reg.Chave); return t; } return t; } Árvore Binária de Pesquisa: Remoção void acha_menor(arvore *t) { if(t->esq == NULL) { return t; } return acha_menor(t->esq); } Árvore Binária de Pesquisa: Análise } Número de comparações: busca com sucesso } melhor caso: C(n) = O(1) } pior caso: C(n) = O(n) } caso médio: C(n) = O(log n) AEDSII_aula_021_arvores_binarias_de_pesquisa_SBB.pdf Árvores binárias de pesquisa com balanceamento Algoritmos e Estruturas de Dados II Árvores binárias de pesquisa 2 } Pior caso para uma busca é O(n) 3 1 4 5 6 2 Ordem de inserção: 1 3 2 4 5 6 Árvore completamente balanceada 3 } Nós folha (externos) aparecem em no máximo dois níveis diferentes } Minimiza o tempo médio de pesquisa } Assumindo distribuição uniforme das chaves } Problema: manter árvore completamente balanceada após cada inserção é muito caro Árvore completamente balanceada 4 } Para inserir a chave 1 na árvore à esquerda e manter a árvore completamente balanceada precisamos movimentar todos os nós Árvores “quase balanceadas” 5 } Objetivo: } Funções para pesquisar, inserir e retirar eficientes } O(lg(n)) } Solução intermediária que mantém a árvore “quase balanceada”, em vez de tentar manter a árvore completamente balanceada Árvores “quase balanceadas” 6 } Várias formas de definir e construir uma árvore “quase balanceada” } Exemplos de restrições aplicadas a árvores para fazê- las “quase balanceadas” } Restringir a diferença entre as alturas das subárvores de cada nó } Minimizar o comprimento do caminho interno da árvore 8 = 0 + 1 + 1 + 2 + 2 + 2 Árvores SBB 7 } Uma árvore SBB é uma árvore binária com apontadores verticais e horizontais, tal que: } Todos os caminhos da raiz até cada nó externo possuem o mesmo número de apontadores verticais } Não podem existir dois apontadores horizontais sucessivos Árvores SBB – estrutura 8 #define SBB_VERTICAL 0 #define SBB_HORIZONTAL 1 struct sbb { struct registro reg; struct sbb *esq; struct sbb *dir; int esqtipo; int dirtipo; } Pesquisa em árvore SBB 9 } Idêntica à pesquisa em uma árvore de busca binária não balanceada } Ignoramos a direção dos apontadores Inserção numa árvore SBB 10 } A chave a ser inserida é sempre inserida após o apontador vertical mais baixo na árvore } Dependendo da situação anterior à inserção podem aparecer dois apontadores horizontais } Transformação local para manter as propriedades da árvore SBB Inserção do 4, 6, 8, 11? Criação de um nó 11 struct sbb *cria_no(struct registro reg) { struct sbb *no = malloc(sizeof(struct sbb)); no->reg = reg; no->esq = NULL; no->dir = NULL; no->esqtipo = SBB_VERTICAL; no->dirtipo = SBB_VERTICAL; return no; } } Métodos para reorganizar casos onde aparecem dois ponteiros horizontais consecutivos } Esquerda-esquerda (e direita-direita) } Esquerda-direita (e direita-esquerda) Transformações para manter propriedadas da árvore SBB 12 Transformações para manter propriedadas da árvore SBB - código 13 void ee(struct sbb **ptr) { struct sbb *no = *ptr; struct sbb *esq = no->esq; no->esq = esq->dir; esq->dir = no; esq->esqtipo = SBB_VERTICAL; no->esqtipo = SBB_VERTICAL; *ptr = esq; } ptr no esq x x ptr Transformações para manter propriedadas da árvore SBB - código 14 void dd(struct sbb **ptr) { struct sbb *no = *ptr; struct sbb *dir = no->dir; no->dir = dir->esq; dir->esq = no; dir->dirtipo = SBB_VERTICAL; no->dirtipo = SBB_VERTICAL; *ptr = dir; } ptr 1 2 3 x ptr dir no x Transformações para manter propriedadas da árvore SBB - código 15 void ed(struct sbb **ptr) { struct sbb *no = *ptr; struct sbb *esq = no->esq; struct sbb *dir = esq->dir; esq->dir = dir->esq; no->esq = dir->dir; dir->esq = esq; dir->dir = no; esq->dirtipo = SBB_VERTICAL; no->esqtipo = SBB_VERTICAL; *ptr = dir; } ptr no esq dir x y x y ptr Transformações para manter propriedadas da árvore SBB - código 16 void de(struct sbb **ptr) { struct sbb *no = *ptr; struct sbb *dir = no->dir; struct sbb *esq = dir->esq; no->dir = esq->esq; dir->esq = esq->dir; esq->esq = no; esq->dir = dir; dir->esqtipo = SBB_VERTICAL; no->dirtipo = SBB_VERTICAL; *ptr = esq; } esq 1 2 3 x ptr dir no y ptr x y Exemplo de inserção em árvore SBB 17 } Inserir chaves 7, 10, 5, 2, 4, 9, 3, 6 Exemplo de inserção em árvore SBB 18 } Inserir chaves 7, 10, 5, 2, 4, 9, 3, 6 Inserção em árvores SBB 19 void iinsere(struct registro reg, struct sbb **ptr, int *incli, int *fim) { /* adiciona, pois encontrou uma folha */ if(*ptr == NULL) iinsere_aqui(reg, ptr, incli, fim); /* busca na sub-árvore esquerda */ } else if (reg.chave < *ptr->reg.chave) { iinsere(reg, &(*ptr->esq), &(*ptr->esqtipo), fim); if (*fim) return; if (*ptr->esqtipo == SBB_VERTICAL) { *fim = TRUE; } else if (*ptr->esq->esqtipo == SBB_HORIZONTAL) { ee(ptr); *incli = SBB_HORIZONTAL; } else if (*ptr->esq->dirtipo == SBB_HORIZONTAL) { ed(ptr); *incli = SBB_HORIZONTAL; } } /* continua */ Inserção em árvores SBB 20 /* busca na sub-árvore direita */ } else if (reg.chave > (*ptr)->reg.chave) { iinsere (reg, &(*ptr->dir), &(*ptr->dirtipo), fim); if (*fim) return; if (*ptr->dirtipo == SBB_VERTICAL) { *fim = TRUE; } else if (*ptr->dir->dirtipo == SBB_HORIZONTAL) { dd(ptr); *incli = SBB_HORIZONTAL; } else if (*ptr->dir->esqtipo == SBB_HORIZONTAL) { de(ptr); *incli = SBB_HORIZONTAL; } /* chave já existe */ } else { printf(“erro: chave já está na árvore.\n”); *fim = TRUE; } } Inserção em árvores SBB 21 void iinsere_aqui(struct registro reg, struct sbb **ptr, int *incli, int *fim) { struct sbb *no = malloc(sizeof(struct sbb)); no->reg = reg; no->esq = NULL; no->dir = NULL; no->esqtipo = SBB_VERTICAL; no->dirtipo = SBB_VERTICAL; *ptr = no; *incli = SBB_HORIZONTAL; *fim = FALSE; } Inserção em árvores SBB 22 void insere(struct registro reg, struct sbb **raiz) { int fim = FALSE; int inclinacao = SBB_VERTICAL; iinsere(reg, raiz, &inclinacao, &fim); } void inicializa(struct sbb **raiz) { *raiz = NULL; } Exemplo de inserção em árvore SBB 23 } Inserir a chave 5.5 na árvore a seguir SBBs – análise 24 } Dois tipos de altura } Altura vertical h: conta o número de apontadores verticais da raiz até as folhas } Altura k: o número de ponteiros atravessados (comparações realizadas) da raiz até uma folha } A altura k é maior que a altura h sempre que existirem apontadores horizontais na árvore } Para qualquer árvore SBB temos hkh 2≤≤ SBBs – análise 25 } Bayer (1972) mostrou que } Custo para manter a propriedade SBB depende da altura da árvore O(lg(n)) } Número de comparações em uma pesquisa com sucesso numa árvore SBB } Melhor caso: C(n) = O(1) } Pior caso: C(n) = O(lg(n)) } Caso médio: C(n) = O(lg(n)) 2221 −+≤≤+ )lg()lg( nkn Exercícios 26 } Desenhe a árvore SBB resultante da inserção das chaves Q U E S T A O F C I L em uma árvore vazia. } Dê a lista de inserções que gera a árvore SBB abaixo: 6 8 5 4 2 Retirada numa árvore SBB 27 } Usaremos três procedimentos auxiliares } EsqCurto: chamado quando o nó (referenciado por um apontador vertical) é retirado à esquerda e diminui a altura da árvore } DirCurto: chamado quando um nó (referenciado por um apontador vertical) é retirado à direita e diminui a altura da árvore } Antecessor: chamado quando um nó possui dois filhos Retirada em árvore SBB – exemplos 28 Retirando 7, 5 e 9: Retirada em árvore SBB – exemplos 29 Retirada em árvore SBB – exemplos 30 Retirada em árvore SBB – exemplos 31 Retirada em árvore SBB – exemplos 32 Retirada de árvore SBB – código 33 void iretira(struct registro reg, struct sbb **ptr, int *fim) { /* chegou a um nó vazio e não encontrou chave */ if (*ptr == NULL) { *fim = TRUE; return; } /* busca na sub-árvore esquerda */ if (reg.chave < *ptr->reg.chave) { iretira(reg, &(*ptr->esq), fim); if(!(*fim)) EsqCurto(ptr, fim); } /* busca na sub-árvore direita */ else if (reg.chave > *ptr->reg.chave) { iretira(reg, &(*ptr->dir), fim); if(!(*fim)) DirCurto(ptr, fim); } /* continua */ Retirada de árvore SBB – código 34 else { /* encontrou o registro */ *fim = FALSE; struct sbb *no = *ptr; if (no->dir == NULL) { /* sem filho direito */ *ptr = no->esq; free(no); if (*ptr != NULL) { *fim = TRUE; } } else if (no->esq == NULL) { /* sem filho esquerdo */ *ptr = no->dir; free(no); if (*ptr != NULL) { *fim = TRUE; } } } else { /* com dois filhos */ antecessor(ptr, &(*ptr->esq), fim); if (!(*fim)) EsqCurto(ptr, fim); } } } Retirada de árvore SBB – código 35 void antecessor(struct sbb **q, struct sbb **ptr, int *fim) { /* busca pelo maior elemento */ if (*ptr->dir != NULL) { antecessor (q, &(*ptr->dir), fim); if (!(*fim)) { DirCurto(ptr, fim); return; } } /* encontrou o maior elemento */ *q->reg = *ptr->reg; q = ptr; *ptr = *ptr->esq; free(*q); if (*ptr != NULL) *fim = TRUE; Retirada de árvore SBB – código 36 void EsqCurto(struct sbb **ptr, int *fim) { if (*ptr->esqtipo == SBB_HORIZONTAL) { *ptr->esqtipo = SBB_VERTICAL; *fim = TRUE; } else if (*ptr->dirtipo == SBB_HORIZONTAL) { struct sbb *dir = *ptr->dir; *ptr->dir = dir->esq; dir->esq = *ptr; *ptr = dir; if (*ptr->esq->dir->esqtipo == SBB_HORIZONTAL) { de(&(*ptr->esq)); *ptr->esqtipo = SBB_HORIZONTAL; } else if (*ptr->esq->dir->dirtipo == SBB_HORIZONTAL) { dd(&(*ptr->esq)); *ptr->esqtipo = SBB_HORIZONTAL; } *fim = TRUE; } else { *ptr->dirtipo = SBB_HORIZONTAL; if(*ptr->dir->esqtipo == SBB_HORIZONTAL) { de(ptr); *fim = TRUE; } else if(/* checa outro lado */) { ... } }}} Retirada de árvore SBB – código 37 void DirCurto(struct sbb **ptr, int *fim) { if(*ptr->dirtipo == SBB_HORIZONTAL) { *ptr->dirtipo = SBB_VERTICAL; *fim = TRUE; } if (*ptr->esqtipo == SBB_HORIZONTAL) { struct sbb *esq = *ptr->esq; *ptr->esq = esq->dir; esq->dir = *ptr; *ptr = esq; if(*ptr->dir->esq->dirtipo == SBB_HORIZONTAL) { ed(&(*ptr->dir)); *ptr->dirtipo = SBB_HORIZONTAL; } else if(*ptr->dir->esq->esqtipo == SBB_HORIZONTAL) { ee(&(*ptr->dir)); *ptr->dirtipo = SBB_HORIZONTAL; } *fim = TRUE; } *ptr->esqtipo = SBB_HORIZONTAL; if(*ptr->esq->dirtipo == SBB_HORIZONTAL) { ed(ptr); *fim = TRUE; } if(*ptr->esq->esqtipo == SBB_HORIZONTAL) { ee(ptr); *fim = TRUE; } }}} AEDSII_aula_022_hashing.pdf Pesquisa em memória primária: hashing Algoritmos e Estruturas de Dados II Hashing 2 } Algoritmos vistos efetuam comparações para localizar uma chave. } Os registros armazenados em uma tabela são diretamente endereçados a partir de uma transformação aritmética sobre a chave de pesquisa. } Busca por meio de operações aritméticas que transformam a chave em endereços em uma tabela. Hashing 3 } Um método de pesquisa com o uso da transformação de chave é constituído de duas etapas principais: } Computar o valor da função de transformação, a qual transforma a chave de pesquisa em um endereço da tabela. } Considerando que duas ou mais chaves podem ser transformadas em um mesmo endereço de tabela, é necessário existir um método para lidar com colisões. 20 10 23 74 x%10 função hashing 0 20 1 10 2 3 23 4 74 tabela Hashing: colisões 4 } Qualquer que seja a função de transformação, algumas colisões irão ocorrer fatalmente, e tais colisões têm de ser resolvidas de alguma forma. } Mesmo que se obtenha uma função de transformação que distribua os registros de forma uniforme entre as entradas da tabela, existe uma alta probabilidade de haver colisões. Hashing: colisões 5 } O paradoxo do aniversário (Feller,1968, p. 33), diz que em um grupo de 23 ou mais pessoas, juntas ao acaso, existe uma chance maior do que 50% de que 2 pessoas comemorem aniversário no mesmo dia. } Para hashing: } Tabela com 365 posições } Adição de 23 chaves } Chance > 50% de haver colisão Hashing: colisões 6 } A probabilidade p de se inserir N itens consecutivos sem colisão em uma tabela de tamanho M é: Hashing: colisões 7 } Seja uma tabela com 50.063.860 de posições (M): Chaves (N) Chance de colisão Fator de carga (N/M) 1000 0.995% 0.002% 2000 3.918% 0.004% 4000 14.772% 0.008% 6000 30.206% 0.012% 8000 47.234% 0.016% 10000 63.171% 0.020% 12000 76.269% 0.024% 14000 85.883% 0.028% 16000 92.248% 0.032% 18000 96.070% 0.036% 20000 98.160% 0.040% 22000 99.205% 0.044% Função de Transformação 8 } Uma função de transformação deve mapear chaves em inteiros dentro do intervalo [0...M − 1], onde M é o tamanho da tabela. } A função de transformação ideal é aquela que: } Seja simples de ser computada. } Para cada chave de entrada, qualquer uma das saídas possíveis é igualmente provável de ocorrer. } Exemplo: } Usa o resto da divisão por M (onde k é a chave) h(k) = k % M Chaves não Numéricas 9 } As chaves não numéricas devem ser transformadas em números: n n é o número de caracteres da chave. n Chave[i] corresponde à representação ASCII do i- ésimo caractere da chave. n p[i] é um inteiro de um conjunto de pesos gerados para 1 ≤ i ≤ n. ∑ = −×= n i iniChaveH 1 128][ H Chaves não numéricas 10 Indice h(TipoChave Chave, TipoPesos p) { unsigned int Soma = 0; int i; int comp = strlen(Chave); for (i = 0; i < comp; i++) Soma += (unsigned int) Chave[i] * p[i]; return (Soma % M); } Resolução de Colisões 11 } Encadeamento } Endereçamento aberto Resolução de Colisões - Encadeamento 12 } Cria uma lista encadeada para cada endereço da tabela. } Todas as chaves com mesmo endereço na tabela são encadeadas em uma lista linear. 0 U tabela 1 A P 2 3 4 5 E Q I S T Resolução de Colisões – Encadeamento 13 } Exemplo: Considerando a i-ésima letra do alfabeto representada por i e a função de transformação h(Chave) = Chave mod M (M=10). Insira EXEMPLO na tabela hashing. E = 5 X = 25 M = 13 P = 16 L = 12 O = 15 Estrutura de Dicionário para Encadeamento 14 #define M 10 #define n 5 typedef char TipoChave[n]; typedef unsigned int TipoPesos[n]; typedef struct { /* outros componentes */ TipoChave Chave; } TipoItem; typedef unsigned int Indice; typedef struct _celula* Apontador; typedef TipoLista TipoDicionario[M]; typedef struct _celula { TipoItem Item; Apontador Prox; } Celula; typedef struct { Celula *Primeiro, *Ultimo; } TipoLista; TipoDicionario M Item Prox Celula TipoLista Estrutura de Dicionário para Encadeamento 15 void Inicializa(TipoDicionario T) { int i; for (i = 0; i < M; i++) /* inicializa as M listas */ FLVazia(&T[i]); } Estrutura de Dicionário para Encadeamento 16 Apontador Pesquisa(TipoChave Ch, TipoPesos p, TipoDicionario T){ /* Apontador de retorno aponta para o item anterior da lista */ Indice i; Apontador Ap; i = h(Ch, p); if (Vazia(T[i])) return NULL; /* Pesquisa sem sucesso */ else { Ap = T[i].Primeiro; while ((Ap->Prox->Prox != NULL) && (strncmp(Ch, Ap->Prox->Item.Chave, sizeof(TipoChave)))) { Ap = Ap->Prox;} if (!strncmp(Ch, Ap->Prox->Item.Chave, sizeof(TipoChave))) return Ap; else return NULL; /* Pesquisa sem sucesso */ } } Estrutura de Dicionário para Encadeamento 17 void InsereHashing(TipoItem x, TipoPesos p, TipoDicionario T) { Indice i; if (Pesquisa(x.Chave, p, T) == NULL) /* insere do TAD Lista */ Insere(x, &T[h(x.Chave, p)]); else printf(“Registro ja esta presente\n"); } void RetiraHashing(TipoItem x, TipoPesos p, TipoDicionario T) { Apontador Ap = Pesquisa(x.Chave, p, T); if (Ap == NULL) printf(" Registro nao esta presente\n"); else Retira(Ap, &T[h(x.Chave, p)], &x); /*Retira do TAD Lista*/ } Encadeamento: Análise 18 } Tamanho esperado de cada lista: N/M } Assumindo que qualquer item do conjunto tem igual probabilidade endereçado para qualquer entrada de T. } N: número de registros, M: tamanho da tabela } Operações Pesquisa, InsereHashing e RetiraHashing: O(1 + N/M) } 1: tempo para encontrar a entrada na tabela } N/M: tempo para percorrer a lista } M ~ N, tempo se torna constante. Resolução de Colisões: Endereçamento Aberto 19 } Quando o número de registros a serem armazenados na tabela puder ser previamente estimado, não há necessidade de se utilizar apontadores. } Para tabela com tamanho M (M > N), pode-se utilizar os espaços vazios da própria tabela para resolver as colisões. } Endereçamento aberto: chaves são armazenadas na própria tabela. Resolução de Colisões: Endereçamento Aberto 20 } Quando encontra uma colisão, procura localizações alternativas (hj). } Hashing linear } Hashing quadrático 11 para , mod ))(( −≤≤+= MjMjxhhj Mjxhhj mod ))(( 2+= Endereçamento Aberto: Exemplo 21 } Se a i-ésima letra do alfabeto é representada pelo número i e a função de transformação abaixo é utilizada h(Chave) = Chave mod M No exemplo, considera-se M = 7 } O resultado da inserção das chaves L U N E S na tabela, usando hashing linear (j = 1) para resolver colisões é mostrado a seguir. Endereçamento Aberto: Exemplo 22 h(L) = h(12) = 5 h(U) = h(21) = 0 h(N) = h(14) = 0 h(E) = h(5) = 5 h(S) = h(19) = 5 Endereçamento Aberto: Implementação 23 #define Vazio "!!!!!!!!!!" #define Retirado "**********" #define M 7 #define n 11 /* Tamanho da chave */ typedef unsigned int Apontador; typedef char TipoChave[n]; typedef unsigned TipoPesos[n]; typedef struct { /* outros componentes */ TipoChave Chave; } TipoItem; typedef unsigned int Indice; typedef TipoItem TipoDicionario[M]; Endereçamento Aberto: Implementação 24 void Inicializa(TipoDicionario T) { int i; /* inicializa todas as posições como vazias */ for (i = 0; i < M; i++) strcpy(T[i].Chave, Vazio); } Endereçamento Aberto: Implementação 25 Apontador Pesquisa(TipoChave Ch, TipoPesos p, TipoDicionario T) { unsigned int i = 0; unsigned int Inicial; Inicial = h(Ch, p); /* transforma a chave */ while ((strcmp(T[(Inicial + i) % M].Chave, Vazio) != 0) && (strcmp(T[(Inicial + i) % M].Chave, Ch) != 0) && (i < M)) i++; if (strcmp(T[(Inicial + i) % M].Chave, Ch) == 0) return ((Inicial + i) % M); else return M; /* Pesquisa sem sucesso */ } Endereçamento Aberto: Implementação 26 void Retira(TipoChave Ch, TipoPesos p, TipoDicionario T) { Indice i; i = Pesquisa(Ch, p, T); if (i < M) /* registro encontrado */ strcpy(T[i].Chave, Retirado); else printf("Registro nao esta presente\n"); } Endereçamento Aberto: Implementação 27 void Insere(TipoItem x, TipoPesos p, TipoDicionario T) { unsigned int i = 0; unsigned int Inicial; if (Pesquisa(x.Chave, p, T) < M) { printf("Elemento ja esta presente\n"); return; } Inicial = h(x.Chave, p); /* transforma a chave */ while ((strcmp(T[(Inicial + i) % M].Chave, Vazio) != 0) && (strcmp(T[(Inicial + i) % M].Chave, Retirado) != 0) && (i < M)) i++; if (i < M) { strcpy (T[(Inicial + i) % M].Chave, x.Chave); /* Copiar os demais campos de x, se existirem */ } else printf(“Tabela cheia\n"); } Endereçamento Aberto: Análise 28 } Seja α = N / M o fator de carga da tabela. Conforme demonstrado por Knuth (1973), o custo de uma pesquisa com sucesso é } O hashing linear sofre de um mal chamado agrupamento. } Ocorre na medida em que a tabela começa a ficar cheia, pois a inserção de uma nova chave tende a ocupar uma posição na tabela que esteja contígua a outras posições já ocupadas, o que deteriora o tempo necessário para novas pesquisas. ⎟ ⎠ ⎞ ⎜ ⎝ ⎛ − += α1 11 2 1)(nC Endereçamento Aberto: Análise 29 α = N / M C(n) 0.1000 1.0556 0.2000 1.1250 0.3000 1.2143 0.4000 1.3333 0.5000 1.5000 0.6000 1.7500 0.7000 2.1667 0.8000 3.0000 0.9000 5.5000 0.9500 10.5000 0.9800 25.5000 0.9900 50.5000 ⎟ ⎠ ⎞ ⎜ ⎝ ⎛ − += α1 11 2 1)(nC Vantagens e Desvantagens da Transformação da Chave 30 } Vantagens } Alta eficiência no custo de pesquisa, que é O(1) para o caso médio. } Simplicidade de implementação. } Desvantagens } Pior caso é O(N). AEDSII_aula_022_hashing2.pdf Pesquisa em memória primária: hashing Algoritmos e Estruturas de Dados II Hashing Dinâmico 2 } Evita que o fator de carga fique muito alto (N/M) } Ideia: dobra o tamanho da tabela hashing quando fator de carga passa de um limiar. α = N / M C(n) 0.1000 1.0556 0.2000 1.1250 0.3000 1.2143 0.4000 1.3333 0.5000 1.5000 0.6000 1.7500 0.7000 2.1667 0.8000 3.0000 0.9000 5.5000 0.9500 10.5000 0.9800 25.5000 0.9900 50.5000 void expande(TipoPesos p, TipoDicionario T) { T2 = NovoHeap(2 * M); for (i = 0; i < M; i++) if (((strcmp(T[i].Chave, Vazio) != 0) && (strcmp(T[i].Chave, Retirado) != 0) InsereHashing(T[i].Chave,p,T2); } /* libera a memória para T */ Hashing Duplo 3 } Mecanismo para resolução de colisão } Endereçamento aberto apresenta o efeito de agrupamento } Agrupa chaves próximas a um endereço, mesmo que a função de espalhamento não a coloque lá. } Solução: hashing duplo } Ao invés de examinar cada uma das posições sucessivas após uma colisão, uma segunda função de hashing é aplicada. Hashing Duplo 4 Apontador Pesquisa(TipoChave Ch, TipoPesos p, TipoDicionario T) { unsigned int i=0, count = 0; unsigned int Inicial, segundo; Inicial = h(Ch, p); /* transforma a chave */ segundo = h2(Inicial); /* h2(x) = ((x%97)+1) */ while ((strcmp(T[(Inicial + i) % M].Chave, Vazio) != 0) && (strcmp(T[(Inicial + i) % M].Chave, Ch) != 0) && (count < M)) { i += segundo; count++; } if (strcmp(T[(Inicial + i) % M].Chave, Ch) == 0) return ((Inicial + i) % M); else return M; /* Pesquisa sem sucesso */ } Hashing Duplo: Análise 5 α = N / M C(n) 0.1000 1.0536 0.2000 1.1157 0.3000 1.1889 0.4000 1.2771 0.5000 1.3863 0.6000 1.5272 0.7000 1.7200 0.8000 2.0118 0.9000 2.5584 0.9500 3.1534 0.9800 3.9919 0.9900 4.6517 ⎟ ⎠ ⎞ ⎜ ⎝ ⎛ − = α ln α )n(C 1 11 Hashing Duplo: Análise 6 Hashing duplo Endereçamento aberto linear α = N / M C(n) C(n) 0.1000 1.0536 1.0556 0.2000 1.1157 1.1250 0.3000 1.1889 1.2143 0.4000 1.2771 1.3333 0.5000 1.3863 1.5000 0.6000 1.5272 1.7500 0.7000 1.7200 2.1667 0.8000 2.0118 3.0000 0.9000 2.5584 5.5000 0.9500 3.1534 10.5000 0.9800 3.9919 25.5000 0.9900 4.6517 50.5000 ⎟ ⎠ ⎞ ⎜ ⎝ ⎛ − += α1 11 2 1)(nC Endereçamento aberto: Hashing duplo: ⎟ ⎠ ⎞ ⎜ ⎝ ⎛ − = α ln α )n(C 1 11 Hashing Perfeito 7 } Uma função de espalhamento h é perfeita se h(xi) ≠ h(xj) para todo i ≠ j } Desta maneira, não haverá colisões de chaves. } Problema: } Quando chaves adicionais foram adicionadas, colisões poderão ocorrer. } Indicações de uso: } Conjunto de chaves seja estático e frequentemente pesquisado. Exemplo: palavras-chave de um compilador Hashing Perfeito 8 } Definição anterior permite que tabelas grandes sejam utilizadas (M >> N), o que não economiza muito espaço de armazenamento. } Além de perfeita, idealmente uma função de espalhamento deve ser mínima. Se há N chaves, tem-se uma tabela de espalhamento com N posições. Hashing Perfeito 9 } Encontrando uma função de hashing perfeita } Abordagens: } Funções de espalhamento perfeitas por redução do quociente } h(x) = (x + s) / d (para inteiros s e d) } Funções de espalhamento perfeitas por redução de resto } h(x) = ((r + s * x) % z) / d } Espalhamento recíproco – espalhamento perfeito e mínima } h(x) = (c / (d * x + e)) % M } Quando chaves são primos entre si: h(x) = (c / x) % M Exercícios 10 } Mostre o estado final de um dicionário com resolução de conflitos por encadeamento depois da inserção das chaves q u e s t a o f c i l. Considere que o dicionário não armazena chaves duplicadas, que o valor de cada caractere é sua ordem no alfabeto e que o dicionário possui uma tabela com 5 listas (isto é, M = 5). } Mostre o conteúdo de um dicionário com resolução de conflitos com endereçamento aberto depois da inserção das chaves q u e s t a o f c i l. Considere que a tabela do dicionário tem 17 posições e que o valor de cada caractere é sua ordem no alfabeto. AEDSII_aula_023_pesquisa_digital.pdf Pesquisa digital Algoritmos e Estruturas de Dados II Pesquisa digital } A pesquisa digital usa a representação das chaves para estruturar os dados na memória } Por exemplo, a representação de um número em binário } A representação de um string com uma sequência de caracteres } A pesquisa digital está para árvores binárias de pesquisa como radixsort está para os métodos de ordenação } Pesquisa não é baseada em comparação de chaves, mas sim em processamento feito sob a chave Trie binária } Cada nó no nível i representa o conjunto de todas as chaves que começam com a mesma sequência de i bits } Cada nó tem duas ramificações, uma para as chaves cujo bit (i+1) é zero e outra para chaves cujo bit (i+1) é um Trie binária – exemplo de pesquisa } Busca em uma trie binária é parecida com pesquisa em árvore de busca } Mas não comparamos chaves } Percorremos a trie de acordo com os bits da chave Q Trie – estruturas struct no { struct no *esq; struct no *dir; struct registro *reg; }; struct registro { int chave; /* outros dados */ }; Trie binária – pesquisa struct registro *pesquisaR(struct no *t, int chave, int p) { if(t == NULL) return NULL; if(t->esq == NULL && t->dir == NULL) { int regchave = t->reg->chave; if (regchave == chave) { return t->reg; } else { return NULL; } } if(digito(chave, p) == 0) { /*busca sub-árvore esquerda */ return pesquisaR(t->esq, chave, p+1); } else { /* busca sub-árvore direita */ return pesquisaR(t->dir, chave, p+1); } } struct registro *pesquisa(struct no *trie, int chave){ return pesquisaR(trie, chave, 0); } Trie binária – exemplo de inserção } Fazemos uma pesquisa na árvore para descobrir onde a chave será inserida } Primeiro caso: se o nó externo onde a pesquisa terminar for vazio, basta cria um novo nó para conter a nova chave } Inserindo W = 110110 Q Trie binária – exemplo de inserção } Fazemos uma pesquisa na árvore para descobrir onde a chave será inserida } Segundo caso: se o nó externo onde a pesquisa terminar tiver uma chave, criamos nós internos até encontrar o bit onde a nova chave difere da chave já existente } Inserindo K = 100010 Q Trie – exemplo de inserção Q W = 110110 K = 100010 Trie – inserção struct no *insereR(struct no *t, struct registro *reg, int p){ int chave = reg->chave; if(t == NULL) return cria_trie(reg); if(t->esq == NULL && t->dir == NULL) { return separa(cria_trie(reg), t, p); } if(digito(chave, p) == 0) /* insere sub-árvore esquerda */ t->esq = insereR(t->esq, reg, p+1); else /* insere na sub-árvore direita */ t->dir = insereR(t->dir, reg, p+1); return t; } void insere(struct no **trie, struct registro *reg) { *trie = insereR(*trie, reg, 0); } Trie – inserção struct no *separa(struct no *no1, struct no *no2, int p){ novo = cria_trie(NULL); int chave1 = no1->reg->chave; int chave2 = no2->reg->chave; if(digito(chave1, p) == 0 && digito(chave2, p) == 0){ novo->esq = separa(no1, no2, p+1); } else if(/* chave1 == 0 && chave2 == 1 */) { novo->esq = no1; novo->dir = no2; } else if(/* chave1 == 1 && chave2 == 0 */) { novo->dir = no1; novo->esq = no2; } else if(/* chave1 == 1 && chave2 == 1 */) { novo->dir = separa(no1, no2, p+1); } return novo; } Vantagens } O formato das tries não depende da ordem em que as chaves são inseridas } Depende apenas dos valores das chaves } Inserção e busca numa trie com N chaves aleatórias requer aproximadamente lg(N) comparações de bits no caso médio } O pior caso é limitado pelo número de bits das chaves Desvantagens } Caminhos de uma única direção acontecem quando chaves compartilham vários bits em comum } Por exemplo, as chaves B (00010) e C (00011) são idênticas exceto no último bit } Requer inspeção de todos os bits da chave independente do número de registros na trie } Os registros são armazenados apenas nas folhas, o que desperdiça memória em nós intermediários } Uma trie com N registros tem aproximadamente 1.44N nós Patricia } Practical Algorithm To Retrieve Information Coded in Alphanumeric } Criada por Morrison 1968 para recuperação de informação em arquivos de texto } Estendido por Knuth em 73, Sedgewick em 88, Gonnet e Baeza-Yates em 91 Patricia } Patricia usa o conceito de pesquisa digital, mas estrutura os dados de forma a evitar as desvantagens citadas das tries } Remove caminhos de única direção } Evita o desperdício de memória em nós internos } Cada nó da árvore contém uma chave e um índice indicando qual bit deve ser testado para decidir qual ramo seguir Patricia – exemplo de pesquisa RH S E C A 0 3 2 1 4 4 Busca por R = 10010 I = 01001 Patricia – estruturas struct no { struct no *esq; struct no *dir; int bit; struct registro *reg; }; struct registro { int chave; /* outros dados */ }; Patricia – pesquisa struct registro *pesquisaR(struct no *t, int chave,int bit) { if(t->bit <= bit) return t; if(digito(chave, t->bit) == 0) { return pesquisaR(t->esq, chave, t->bit); } else { return pesquisaR(t->dir, chave, t->bit); } } struct registro *pesquisa(struct no *pat, int chave) { struct registro *reg; reg = pesquisaR(pat->esq, chave, -1); if(reg->chave == chave) { return reg; } else { return NULL; } } Patricia – exemplo de inserção RH S E C A 0 3 2 1 4 4 Inserindo I = 01001 H = 01000 Patricia – exemplo de inserção (caso 1) RH S E C A 0 3 2 1 4 4 I 4 Inserindo I = 01001 H = 01000 Primeiro bit que diferencia chaves não foi utilizada na busca Patricia – exemplo de inserção RH S E C A 0 3 2 1 4 4 Inserindo N = 01110 I = 01001 I 4 Patricia – exemplo de inserção (caso 2) RH S E C A 0 3 2 1 4 4 N 2 I 4 Inserindo N = 01110 I = 01001 Primeiro bit que diferencia chaves foi pulado na busca Casos da Inserção } Caso 1: O novo nó substitui um nó externo } Acontece quando o bit que diferencia a nova chave da chave encontrada não foi utilizado na busca } Caso 2: O novo nó substitui um nó interno } Acontece quando o bit que diferencia a nova chave da chave encontrada foi pulado durante a busca struct no *inicializa() { struct no *novo; novo = cria_pat(NULL, -1); novo->esq = novo->dir = novo; return novo; } Patricia – inserção void insere(struct no *pat, struct registro *reg) { int chave = reg->chave; struct registro *ins; int ichave; int i = 0; ins = pesquisaR(pat->esq, chave, -1); ichave = ObtemChave(ins); /* 0 se não há chaves */ if (chave == ichave) return; /* chave já existe */ /* procura pelo bit diferenciador */ while (digito(chave, i) == digito(ichave, i)) i++; /* i é o bit diferenciador */ pat->esq = insereR(pat->esq, reg, i, pat); } Patricia – inserção struct no *insereR(struct no *t, struct registro *reg, int bit, struct no *pai) { int chave = reg->chave; if((t->bit >= bit) || (t->bit <= pai->bit)) { struct no *x = cria_pat(reg, bit); x->esq = digito(chave, x->bit) ? t : x; x->dir = digito(chave, x->bit) ? x : t; return x; } else if (digito(chave, t->bit) == 0) { t->esq = insereR(t->esq, reg, bit, t); } else { t->dir = insereR(t->dir, reg, bit, t); } return t; } Considerações } Inserção numa árvore patricia com N chaves aleatórias requer aproximadamente lg(N) comparações de bits no caso médio e 2lg(N) comparações no pior caso } O número de comparações de bits é limitado pelo tamanho das chaves } Não existe desperdício de memória nos nós internos Exercícios } Implemente as funções } struct no * cria_trie(struct registro *reg); } struct no * cria_pat(struct registro *reg, int bit); } Mostre a trie resultante da inserção das chaves E X M P L O F A E: 00101 X: 11000 M: 01101 P: 10000 L: 01100 O: 01111 F: 00110 A: 00001 Exercícios } Implemente as funções } struct no * cria_trie(struct registro *reg); } struct no * cria_pat(struct registro *reg, int bit); } Mostre a trie resultante da inserção das chaves E X M P L O F A } Mostre a árvore patricia resultante da inserção das chaves E X M P L O F A E: 00101 X: 11000 M: 01101 P: 10000 L: 01100 O: 01111 F: 00110 A: 00001 Tries M-árias } Apresentamos apenas tries binárias mas podemos ter tries M-árias } Cada filho corresponde a um possível valor do i-ésimo “bit” } Por exemplo, cada nó numa trie para armazenar strings pode ter 256 filhos (um pra cada valor possível para um caractere) Solucao Aulas 07, 08 e 09.pdf Universidade Federal de Minas Gerais Departamento de Ciência da Computação Algoritmos e Estruturas de Dados II 2º Semestre de 2011 Solução dos Exercícios Aula 07, 08 e 09.pdf 1. •••• A função p1 calcula a multiplicação da matriz A pela matriz B e coloca o resultado na matriz C. •••• A operação relevante é o número de operações de multiplicação e soma realizadas com os elementos dos vetores. ∑∑∑ − = − = − = === 1 0 1 0 1 0 32...2)( n i n j n k nnT )()( 3nOnT = 2. A função p2 calcula o seguinte somatório: ∑∑ = = + === n i n ij nn x 1 2 2 ...1 ∑∑ = − = − === n i i j nny 1 21 1 2 ...1 Considerando que a operação relevante seja o número de operações de soma realizadas com as variáveis x e y: ∑ ∑ ∑ = = − = == += n i n ij i j nnT 1 2 1 1 ...11)( )()( 2nOnT = 3. Vamos analisar as listas de comandos dentro do IF e do ELSE para verificar qual delas executa o maior número de operações. IF 1...1)( 1 1 −−=== ∑ − += innT n ij ELSE 111)( 1 0 +=+= ∑ − = nnT n j Portanto, o pior caso será alcançado quando os comandos do ELSE forem sempre executados, ou seja, quando todos os elementos de x forem menores ou iguais a 10. Neste caso, ∑ − = +==+= 1 0 2 ...)1()( n i nnnnT 4. int exprec(int n) { if (n<=0) return(1); else return(2*(exprec(n-1))); } 5. Retorna o máximo divisor comum dos números a e b. 6. a) nnT log)( = b) nnnT log)( = c) 2 13)( −= nnT 7. == = 2loglog 42 )( nnn nnf a b ( )12−= nOn ( )nOn = ( )2)( nnT Θ= Solucao exercicios aulas 5 e 6.pdf Universidade Federal de Minas Gerais Departamento de Ciência da Computação Algoritmos e Estruturas de Dados II 2º Semestre de 2011 Solução dos Exercícios Aula 05 e 06.pdf 1. 1 seg. 1 min. 1 hora 1 dia 1 mês 1 ano 1 século log n 2 1,1 x 1018 101.084 1026.009 10780.270 109.493.282 10949.328.194 n 1 3,6 x 103 1,2 x 107 7,4 x 109 6,7 x 1012 9,9 x 1014 9,9 x 1018 n 1 6 x 10 3,6 x 103 8,6 x 104 2,6 x 106 3,2 x 107 3,2 x 109 n log n 1 15 414 6.787 150.687 1,5 x 106 1,2 x 108 n2 1 7 60 293 1.609 5.615 56.156 n3 1 3 15 44 137 315 1.466 2n 0 5 11 16 21 24 31 n! 1 4 6 8 9 10 12 2. q: probabilidade pesquisa com sucesso 1-q: probabilidade pesquisa sem sucesso Nas pesquisas com sucesso, cada registro tem a mesma probabilidade de ser acessado. pi: probabilidade de que o i-ésimo registro seja o procurado pi = q/n Número de comparações das pesquisas sem sucesso = n. Portanto, T(n) = 1 (q/n) + 2 (q/n) + 3 (q/n) + ... + n (q/n) + n (1-q) = ... = (2n+q-nq)/2 3. a) Operação relevante é o número de vezes que a operação soma é realizada 2 2 1 3...3)( nnT i n ij j ik ===∑∑∑ = = = b) Operação relevante é o número de comparações com os elementos do vetor Pior caso: ∑ = −+=== n j nnjnT 2 2 1 22 ...)( Melhor caso: 1...1)( 2 −===∑ = nnT n j Operação relevante é o número de movimentações com os elementos de A Pior caso: ( )∑ = −+==+= n j nnjnT 2 2 3 2 5 2 ...2)( Melhor caso: 33...3 2 −==∑ = n n j c) Operação relevante é o número de comparações com os elementos do vetor ∑∑ = += −=== n i n ij nn nT 1 1 2 22 ...1)( Operação relevante é o número de movimentações com os elementos do vetor. Pior caso: ∑∑ = += −=== n i n ii nnnT 1 2 1 2 3 2 3 ...3)( Melhor caso: T(n)=0 d) Operação relevante é o número de comparações com a chave ∑∑ − = += −=== 1 1 1 2 22 ...1)( n i n ij nn nT Operação relevante é o número de movimentações com a chave ∑ − = −=== 1 1 )1(3...3)( n i nnT 4. Vamos tentar provar que 6n3 = Θ(n2). Para isso devemos achar constantes 01 >c , 02 >c e 00 >n , tal que: 0 2 2 32 1 ;6 nnncnnc ≥∀≤≤ Dividindo por n2, temos: 021 ;6 nncnc ≥∀≤≤ ⇒≥⇒≤ nccn 66 22 Impossível, pois não existe constante que possa ser maior ou igual a 6n para todo n maior ou igual a 0n . Portanto, 6n3 ≠ Θ(n2)