aeds
DESCRIPTION
Algoritmos e estruturas de dadosTRANSCRIPT
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
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
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)
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 };
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));
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 ?
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
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
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