aeds

76

Upload: joao-victor-cosme

Post on 23-Dec-2015

8 views

Category:

Documents


3 download

DESCRIPTION

Algoritmos e estruturas de dados

TRANSCRIPT

Algoritmos e estruturas de dados: partes centrais de qualquer software

Escolher a solução mais adequada

Entender os compromissos de diferentes estruturas de dados

Entender os limites do computador

Tipos abstratos de dados Análise de algoritmos

O(n), O(log(n)), etc

Estruturas de dados

Listas, filas, pilhas e árvores

Métodos de ordenação

Quicksort, heapsort, etc

Métodos de pesquisa

Hashing, árvores balanceadas

Parte 1 Prova 1 Trabalho prático 1

Parte 2 Prova 2 Trabalho prático 2

Parte 3 Prova 3 Trabalho prático 3

Projeto de Algoritmos Nívio Ziviani

Introduction to Algorithms Cormen, Leiserson, Rivest, Stein

Algorithms Robert Sedgewick e Kevin Wayne

The Art of Computer Programming Volumes 1 e 3

Donald Knuth

3 provas (20+20+20 = 60 pontos)

1 prova suplementar

4 trabalhos práticos (4+10+10+16 = 40 pts)

Implementação

Documentação

Teste

3 listas de exercícios (1+1+1 = 3 pontos)

Página Web da disciplina

http://www.dcc.ufmg.br/~cunha/

Linguagem de programação: C

CodeBlocks

GCC

Sistema operacional recomendado: Linux

Os trabalhos práticos precisam rodar no Linux

Alta carga extra classe

Eficiência

Construções similares a instruções de máquina

Acesso direto à memória Portabilidade

De microcontroladores a supercomputadores

Poucos requisitos para execução

Biblioteca padrão limitada

Software de sistema ou de base

Linux

Gnome

Python, Perl, PHP, GCC

Bibliotecas

▪ GNU Scientific Library

▪ Partes do Matlab

Várias aplicações

Inclusão de cabeçalhos Declarações globais Definições de funções

#include <stdio.h>

char *mensagem = “hello, world!\n”;

int main(void) {

puts(mensagem);

return 0;

}

Compilador

date.h

struct date { int day; int month; int year; } struct date create(void); int week_of_year(struct date d);

data.h

#include “data.h” struct data create(void) { ... } int semana_do_ano(struct data d) { ... } ...

data.c

data.o

Compilador

date.h

struct date { int day; int month; int year; } struct date create(void); int week_of_year(struct date d);

data.h

#include “data.h” int main(int argc, char **argv) { ... }

principal.c

data.o

principal.o

date.o main.o

Ligador calendário.exe

Economize tempo de depuração tratando todos os avisos do compilador

Muitas vezes a mensagem de erro não reflete o que está ocorrendo; observar as redondezas da linha em que o erro/warning foi indicado

[debian:~/prof/aeds2/src]% gcc –Wall –c data.c data.c: In function ‘main’: data.c:12: warning: ‘return’ with no value, in function returning non-void data.c:11: warning: ‘hoje’ is used uninitialized in this function

Identificadores de funções e variáveis

Letras, números, e underscores

Não podem começar com número

Palavras reservadas

auto double int struct

break else long switch

case enum register typedef

char extern return union

const float short unsigned

continue for signed void

default goto sizeof volatile

do if static while

inline restrict

Tipos inteiros char, short, int, long, long long

Tipos de ponto flutuante float, double, long double

void

Arranjos Estruturas Ponteiros

type_sizes

type_limits

float_precision

ascii_table

Aritméticos x + y, x - y, x * y, x / y, x % y, -x

Incremento e decremento x++, ++x, y--, --y

Comparação x > y, x >= y, x < y, x <= y, x == y, x!= y

Lógicos !x, x && y, x || y

Binários x & y, x | y, x ^ y, ~x, x << y, x >> y

Atribuição x = y, x += y, x |= y, x <<= y, etc.

Endereçamento de memória &x, *x, x[y], x.campo, x->campo

Conversão (int)x, (double)x

Condicional x ? y : z

sizeof sizeof(x), sizeof(double)

Precedência 1 << 2 * 3 % 4 ^ 5 – 6 && 7

Sequência de elementos de um único tipo

Tamanho fixo

Sem checagem de limites

Opcional: inicialização durante a declaração

▪ Impossível atribuir a um arranjo depois da declaração

int primos[7];

int primos[7] = {2, 3, 5, 7, 11, 13, 17};

2 3 5 7 11 13 17

primos

Alocação linear

Compilador converte indíces para a posição do elemento referenciado

double identidade[4][4] = {{1, 0, 0, 0}, {0, 1, 0, 0},

{0, 0, 1, 0}, {0, 0, 0, 1}};

1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1

identidade

identidade[x][y] = valor do elemento na posição x*4 + y

identidade[2][2] = décimo valor = 1

Um string termina com o caractere nulo ’\0’

Um string num arranjo de 80 caracteres char string[80] = “hello world!”

Strings inicializados na declaração char string[] = “hello world!”

O tamanho do arranjo é definido automaticamente pelo compilador

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 79

h e l l o w o r l d ! \0 ? ? ... ... ?

Copiar um string: strcpy, strncpy Funções com n no nome recebem o tamanho do

arranjo como parâmetro ▪ Evita erros caso o string não caiba no arranjo

Concatenar strings: strcat, strncat String de destino tem que ter espaço

Comparar strings: strcmp, strncmp Comparar strings com == só compara a posição dos

arranjos, não o conteúdo Tamanho de um string: strlen Duplicar um string: strdup

Converter string para inteiros int atoi(const char *string)

long atol(const char *string)

long long atoll(const char *string)

Converter de string para ponto flutuante double atof(const char *string)

array_boundaries1

array_boundaries2

string_cmp

string_gotchas

Combinam conjunto de dados relacionados

Campos armazenados em sequência

Possível inclusão de espaçamento pelo compilador

struct cliente {

char nome[48];

long long cpf;

long telefone;

struct endereco

residencial;

...

};

struct cliente cl;

cl.cpf = 65423423123;

sprintf(cl.residencial.rua,

“Rua dos Goitacases”);

cl.residencial.numero = 1325;

struct endereco end = {

“Rua dos Goitacases”, 1325 };

struct_static

Referência para um objeto na memória Vários usos:

Manipulação de dados

▪ Exemplo: ordenar um arranjo de estruturas grandes

Passagem de parâmetro por referência

Declarações:

int i = 10;

int * ponteiro = &i;

int ** ppp = &ponteiro;

Variável Posição Valor

i 0x80 10

ponteiro 0x84 0x80

ppp 0x88 0x84

Operadores & e * Endereço de uma variável é um ponteiro para

aquela variável Acessar o valor da variável apontada

int x = 10;

int y;

int * ponteiro = &x;

y = *ponteiro + 1; // y = x + 1 = 11

*ponteiro = 20; // x = 20

Nenhum ponteiro válido tem o valor NULL NULL não pode ser acessado

Falha de segmentação

Útil para denotar um ponteiro não inicializado ou condições de erro

int * ponteiro;

/* ponteiro tem um valor

* aleatório, não sabemos

* para onde ele aponta. */

ponteiro = NULL;

fatores(ponteiro);

int * fatores(int * p)

{

if(!p) { return NULL; }

// calcula fatores primos

...

}

Não existe objeto com tipo void Um ponteiro para void é como um coringa,

pode apontar para qualquer tipo de objeto

int i;

int * int_ptr;

void * void_ptr;

double * double_ptr;

int_ptr = &i;

void_ptr = int_ptr; // OK

double_ptr = int_ptr; // !OK

double_ptr = void_ptr; // OK

/* Qual o problema com

* double_ptr? */

Variável Posição Valor

i 0x80 10

int_ptr 0x84 0x80

void_ptr 0x88 0x80

double_ptr 0x8c 0x80

Declaração e inicialização normal

Acesso aos campos

struct data { int dia; int mes; int ano; };

struct data d1;

struct data *ptr = &d1;

int i = 0;

(*ptr).dia = 8;

(*ptr).mes = 3;

(*ptr).ano = 2012;

ptr->dia = 8;

ptr->mes = 3;

ptr->ano = 2012;

Variável Posição Valor

d1.dia 0x80 8

d1.mes 0x84 3

d1.ano 0x88 2012

ptr 0x8c 0x80

i 0x90 0

Declaração Tipo de retorno, nome, parâmetros

▪ double pow(double x, double y)

Em cabeçalhos .h para funções externas Em arquivos .c para funções auxiliares (internas)

Definição Em arquivos .c Corpo da função double pow(double x, double y) { ... }

C passa parâmetros por valor

Modificações do valor de um parâmetro não afeta a variável original (fora da função)

int incrementa(int x) {

x = x+1;

return x;

}

int main(int argc, char **argv)

{

x = 1;

y = incrementa(x);

printf(“%d %d\n”, x, y);

...

Variedade de utilidades Declaração int (*comparador)(void *e1, void *e2);

Inicialização

Nome da função é convertido em ponteiro

Passando como parâmetro

int compara_dados(void *e1, void *e2) { ... };

int main(void) {

int (*compara)(void *e1, void *e2) = compara_dados;

void ordena(void *dados, int nelem,

int (*comparador)(void *e1, void *e2));

func_params

struct_func_params test_write_to_str

Preferível sempre que não soubermos quanta memória um programa utilizará Alocação estática é fixa Alocação dinâmica permite utilizar a quantidade

necessária de memória sem desperdícios Alocar um novo bloco de memória malloc(), calloc()

Redimensionar um bloco já alocado realloc()

Liberar um bloco alocado free()

malloc aloca um bloco de size bytes O conteúdo do bloco alocado é indeterminado Retorna um ponteiro para void

O programador decide como usar o bloco alocado

Retorna NULL em caso de erro int *primos = malloc(7 * sizeof(int));

if(!primos) { perror(NULL); exit(1); }

char *buffer = malloc(64);

if(!buffer) { perror(NULL); exit(1); }

double *fracoes = malloc(64);

if(!fracoes) { perror(NULL); exit(1); }

memset(fracoes, 0, 8*sizeof(double));

int *primos = malloc(7 * sizeof(int));

if(!primos) { perror(NULL); exit(1); }

char *buffer = malloc(64);

if(!buffer) { perror(NULL); exit(1); }

double *fracoes = malloc(8*sizeof(double));

if(!fracoes) { perror(NULL); exit(1); }

memset(fracoes, 0, 8*sizeof(double));

calloc aloca um bloco com espaço para count objetos de tamanho size bytes

O bloco alocado é inicializado com zero

int *primos = calloc(7, sizeof(int));

if(!primos) { perror(NULL); exit(1); }

char *buffer = calloc(64, sizeof(char));

if(!buffer) { perror(NULL); exit(1); }

double *fracoes = calloc(8, sizeof(double));

if(!fracoes) { perror(NULL); exit(1); }

memset(fracoes, 0, 8*sizeof(double));

Redimensiona o bloco de memória apontado por ptr para size bytes

Mantém o conteúdo do bloco apontado por ptr (limitado pelo tamanho do novo bloco)

O local do novo bloco de memória pode mudar

▪ Mesmo se o novo bloco for menor que o anterior!

int *primos = calloc(7, sizeof(int));

if(!primos) { perror(NULL); exit(1); }

realloc(primos, 5*sizeof(int)); // BUG

primos = realloc(primos, 5*sizeof(int)); // OK

if(!primos) { perror(NULL); exit(1); }

Libera o bloco de memória apontado por ptr

Chamar free mais de uma vez pra um mesmo bloco de memória é um bug

free só pode ser chamada em ponteiros retornados por malloc, calloc e realloc.

char * montar_string(struct endereco e) {

char *string = malloc(128);

if(!string) { perror(NULL); exit(1); }

// montar string ...

return string;

}

char * exemplo(char parametro[])

{

int i; char estatico[80];

char *dinamico = malloc(80);

Variável Posição Valor

parametro 0x7c 0x480

i 0x80 ?

estatico[0] 0x84 ?

... ... ?

estatico[79] 0xd3 ?

dinamico 0xd4 0x884

Variável Posição Valor

dinamico[0] 0x884 ?

dinamico[1] 0x885 ?

... ... ?

dinamico[79] 0x8d3 ?

dyn_alloc

pointer_arith

Rotinas de entrada e saída não fazem parte da linguagem

Disponíveis em bibliotecas que acompanham os compiladores

Padronizadas

Em C, são definidas no cabeçalho stdio.h

formato específica como os valores devem ser impressos na saída. printf(“X = %d”, x);

printf(“Area: %f\n”, PI*r*r);

printf(“Nome: %s”, aluno.nome);

Existem vários caracteres de controle Retorna o número de conversões impressas

Útil para checar erros

conversão

c char

d int

u unsigned int

x int, hexadecimal

f float

e float, científico

g float, e ou f

p ponteiro

s string

% sinal percentual

%[opções][largura mínima][.precisão][tamanho]conversão

printf(“valor do float na posição %p = %f\n”, ptr, *ptr);

tamanho

hh char

h short

l long

ll long long

L long double

z size_t

t ptrdiff_t

j intmax_t

printf(“valor do double na posição %p = %lf\n”, ptr, *ptr);

tamanho

0 zeros à esquerda

# alternativa

- alinhar à esquerda

+ mostrar sinal positivo

espaço para sinal positivo

‘ agrupar milhares

I digitos alternativos

printf(“valor do double na posição %p = %+06.2lf\n”, ptr, *ptr);

Caracteres especiais e reposicionamento do cursor

printf(“barra invertida \\\n”);

printf(“aspas duplas \”\n”);

printf(“tab\ttab\ttab\tnova linha\ntexto\n”);

scanf é o inverso do printf: lê dados do terminal

Mesmos códigos de conversão

Mesmas sequências de escape

Passar um ponteiro para a variável que você quer inicializar

int nlados = 0; float lado = 0;

scanf(“%f %d\n”, &lado, &nlados);

perimetro = lado * nlados;

Observe que scanf interrompe a leitura de um string (%s) quando encontra um branco

Especificadores de tamanho e filtro %[aeiou]s lê apenas vogais Para na primeira consoante, número, espaço, pontuação, etc

%[0123456789]s lê apenas números %60s lê apenas 60 caracteres %60[^0123456789]s lê até 60 caracteres parando

quando encontrar um número

char buffer[80];

scanf(“%79s”, buffer);

getchar lê um único caractere do terminal putchar(int c) imprime o caractere

com valor c no terminal

Mesma coisa, só precisamos passar o manipulador do arquivo como parâmetro FILE *entrada;

FILE *saida;

...

fscanf(entrada, “%79s”, buffer);

char c = fgetc(entrada);

fputc(c, saida);

fprintf(saida, “X = %d”, x);

FILE * fopen(char *nome, char *modo)

Abre o arquivo com o dado nome

modo pode ser: ▪ “r” para leitura, “w” para escrita, “rw” para leitura e escrita

▪ Se o arquivo já existir, podemos usar “a” para adicionar ao arquivo

Sempre teste se o retorno é nulo, pois podem ocorrer erros ▪ Arquivo ou caminho não existente, permissões insuficientes, etc.

int fclose(FILE *arquivo)

Fecha o arquivo apontado por arquivo

FILE *arquivo = fopen(“C:\Users\Cunha\Desktop\teste.txt”, “w”);

if(!arquivo) { perror(NULL); exit(EXIT_FAILURE); }

fprintf(arquivo, “hello arquivo!\n”);

fclose(arquivo);

printf e fprintf são idênticas, só operam sobre manipuladores de arquivos diferentes printf sempre imprime na saída padrão (terminal)

fprintf recebe o arquivo onde imprimir como parâmetro

O manipulador do arquivo correspondente à saída padrão é o stdout, e o manipulador da entrada padrão é o stdin ▪ fprintf(stdout, “imprimindo no terminal\n”);

Cuidado ao terminar de ler um arquivo

Use int feof(FILE *arquivo)para testar se já leu o arquivo até o fim ▪ feof retorna falso se o arquivo ainda não tiver

terminado

▪ feof só retorna verdadeiro depois que você tentar ler após o arquivo ter terminado

Saber a posição atual do arquivo long ftell(FILE *arquivo)

Mudar para uma dada posição no arquivo int fseek(FILE *arquivo,

int base, int distancia)

Onde base pode ser: ▪ SEEK_SET, o começo do arquivo

▪ SEEK_CUR, a posição atual no arquivo

▪ SEEK_END, o final do arquivo

write_test_file

feof_scanf_test

int main(int argc, char *argv[]) { ... }

argv é um arranjo de strings, um parâmetro em cada índice do arranjo

argc é o número de parâmetros em argv

argv[0] é sempre o nome do executável ▪ Logo, argc >= 1

Para processamento avançado de parâmetros, use getopt() Parâmetros em qualquer ordem, opcionais, etc. ls -al --color=auto --sort=x

cmdline_params (CodeBlocks) crypto

Pequeno esforço, grande impacto

Código mais legível Ajuda o entendimento das idéias

Útil quando você for ler o código 6 meses depois

Útil para outras pessoas Código com menos erros Economizar tempo e ter menos dor de cabeça

Em AEDS2: ajuda entendimento das idéias e correção dos trabalhos

Realça estrutura lógica do código

Em geral, indenta-se com tabulação (tab)

Em geral um tab corresponde a 8 espaços, mas é configurável na maioria dos editores

static char * concat (char *s1, char *s2) { while (x == y) { something (); somethingelse (); } finalthing (); } static char * concat (char *s1, char *s2) { while (x == y) { something (); somethingelse (); } finalthing (); }

static char * concat(char *s1, char *s2)

{

while(x == y) {

something();

something_else();

}

final_thing();

}

static char *

concat(char *s1, char *s2)

{

while(x == y)

{

something();

something_else();

}

final_thing();

}

static char *

concat(char *s1, char *s2)

{

while(x == y)

{

something();

something_else();

}

final_thing();

}

GNU

K&R

Allman

Facilitam a compreensão do código, mais importantes para código complexo

Código bem escrito não depende muito de comentários

Comentário errado é pior do que nenhum comentário

Revisar comentários quando o código mudar

No início de um módulo Descrever variáveis globais ou importantes Em funções para explicar os parâmetros, o

tipo de retorno, e o que a função faz

Não explicar como a função faz a tarefa, código deve ser claro o bastante

Indicar invariantes de loops

Não comentar o óbvio DUH:

i += 1; // incrementa i.

OK:

i += 1; // compensar borda.

Escolher bons identificadores ajudam a compreensão do código void ordena(int *vetor);

void processa(int *vetor);

double media(int *vetor);

Mesma coisa para variáveis ▪ Variáveis auxiliares podem receber nomes simples, mas

sem exagerar ▪ Indices: i, j, k

▪ Variáveis tipo ponto flutuante: x, y, z

▪ Strings: s1, s2, str

Se houver, preferir o estilo que já estiver em uso

Underscore: int num_clientes;

struct list *lista_alunos;

CamelCase: int numClientes;

struct lista *listaAlunos;

Não usar “números mágicos” no código Valores podem precisar ser modificados “Números mágicos” não têm significado Usar #define para dar nome a constantes

Nomes em maiúsculas

#define PI 3.14159

#define TAMANHO_MAX_LINHA 256

char * le_linha(FILE *entrada) {

char *linha = malloc(TAMANHO_MAX_LINHA);

...

return linha;

}

Particionamento de um programa Um módulo geralmente é um par de arquivos

modulo.c contém a implementação das funções

modulo.h contém a declaração das funções e tipos de dados; é importado por outros módulos

Outros programadores só precisam saber o que o módulo faz, não como ele funciona

Procurar identificar módulos independentes para que eles possam ser reaproveitados