AED - Algoritmos e Estruturas de Dados Licenciatura em Engenharia Electrónica Exame de 17 de Junho de 2015-1a Época - Resolução Prova escrita, individual e sem consulta. 20 valores. NOME: NÚMERO: PARTE I - Questões de Escolha Múltipla Preencha as respostas na tabela (usando apenas letras maiúsculas). Se nenhuma opção servir, escreva NENHUMA. Se pretender alterar a sua resposta, risque e escreva ao lado a sua nova opção. Todas as questões de escolha múltipla valem 1 valor. As questões de escolha múltipla não respondidas são cotadas com 0 valores, mas por cada resposta errada são descontados 1/4=0.25 valores. Questão 1 2 3 4 5 6 7 8 Resposta A C B C D B B C 1. Considere o seguinte extracto de programa em C: #include <stdio.h> #define N 10 int k,j,n=0; for(k = 0 ; k < N; k++) for(j = k + 1; j < N; j++) n++; printf("%d\n",n); Indique qual a saída deste programa: A. 45 B. 55 C. 60 D. 66 Há várias formas de chegar a este resultado. Uma forma simples é verificar que os dois ciclos correspondem a varrer os elementos da sub-matriz triangular superior (ou inferior) de uma matrix quadrada de N N (ou seja, excluindo os elementos da diagonal). É simples perceber que o resultado deve ser então (N 2 N)/2. Note que N 2 N são todos os elementos da matriz excepto a diagonal, a divisão por 2 corresponde à sub-matriz indicada. Uma forma alternativa é verificar que para k = 0 o ciclo interior é executado N 1 vezes, para k = 1, N 2, etc. No total, o número de vezes que o ciclo interior é executado é dado por (N 1) + (N 2) + (N 3) +... + 2 + 1 Isto não é mais que a soma dos elementos de uma progressão aritmética. Recordando que, no caso deste tipo de progressão, a soma dos elementos é dada por, M k=1 a k = a 1 + a M 2 M E que neste caso M = N 1, a 1 = N 1, a M = 1, resulta M a k = k=1 (N 1) + 1 2 (N 1) = N 2 N 2
2. Considere o seguinte extracto de programa em C: struct s { int n; struct s *p; *b = NULL,*a; int j; for(j=0;j<3;j++){ a = malloc(sizeof(struct s)); a->n = j-3; a->p=b; b=a; while(b!=null) { printf("%d ",b ->n); b = b -> p; Indique qual a saída deste programa: A. 1 2 3 B. -3-2 -1 C. -1-2 -3 D. 3 2 1 Os elementos são inseridos na base da lista na sequência -3, -2, -1, ficando p último elemento na base. A listagem corresponde obviamente a 1, 2, 3. 3. Considere que uma árvore é suportada em C com a estrutura: typedef struct tree { char c; struct tree*l,*r; Tree; é usada para uma representação de uma árvore binária, a qual após inicializada, pode ser representada desta forma: Admita que esta árvore é listada a partir da raiz usando a seguinte função: void list(tree *t){ if(t){ list(t->r); list(t->l); printf("%c",t->c); Indique qual a saída deste programa: A. ABCDEFG B. ACBEGFD C. GFEDCBA D. DBACFEG
É fácil de ver que se trata de uma listagem da árvore da direita para a esquerda em ordem pós-fixada (ou post-order). Basta olhar para um elemento para saber qual a resposta correcta... 4. Indique qual das afirmações seguintes é falsa: A. N(N + 3) 5 O(N 7 / N + 1) B. N 2 N + 3 O(N 5/2 ) C. N 2 N + 1 O(N 2 lg(n)) D. log(n 3 ) O(log N 2 ). É suficiente verificar que em A. a função é de ordem O(N 6 ) e a da direita de O(N 6+1/2 ), pelo que a afirmação é verdadeira. Em B., a função à esquerda é da ordem de O(N 2+1/2 ) = O(N 5/2 ), pelo que a afirmação é igualmente verdadeira. Em D., basta notar que O(log(N α )) = O(α log(n)) = O(log(N)), pelo que ambos os membros são da ordem de O(log(N)). Em C, descontando o factor multiplicativo N 2, basta recordar que N cresce mais depressa que lg(n), pelo que a afirmação é falsa. 5. Considere uma árvore ordenada e perfeitamente balanceada de N inteiros. Considere um algoritmo que percorre todos os nós da árvore e, em cada nó da árvore, efectua N operações. A complexidade deste algoritmo é (deverá indicar a ordem de complexidade correspondente ao menor dos majorantes): A. O(N 2 log 2 N) B. O(N) C. O(N log 2 N) D. O(N 2 ) É fácil verificar que se são percorridos N nós e em cada nó efectuadas N operações elementares, a complexidade tem que ser O(N 2 ). 6. Indique qual das tabelas seguintes não pode ser um acervo: A. 20 15 13 12 11 10 9 B. 20 15 15 13 12 16 11 C. 9 3 8 1 2 8 7 D. 9 8 3 7 6 1 2. Basta representar as tabelas em forma de árvore e verificar a condição de acervo. 7. Considere uma árvore binária ordenada totalmente balanceada com 31 nós. No pior caso, o número de nós que é preciso percorrer para saber se um determinado item se encontra na árvore é A. 31 B. 5 C. 10 D. 1 Sendo a árvore balanceada, a altura é dada por h = (log 2 (N)+1 = 5. O pior caso corresponde ao nó procurado estar nas folhas terminais, em que será necessário percorrer um número de nós igual à altura da árvore. 8. Admita que tem uma lista ordenada de inteiros, representados por uma lista simplesmente ligada. O número médio de operações necessárias para determinar qual o nó em que se encontra um inteiro que faz parte da lista é: A. N B. log 2 N C. N/2 D. log 2 (N/2) Se a lista tem N elementos, e a procura é feita sequencialmente, o melhor caso será 1 e e o pior N. Em média será N/2. Nota: se fosse omitida a indicação de que o elemento faz parte da lista, poderia haver muitas procuras de elementos inexistentes, em que o número de operações seria N. Nestas condições não seria possível estimar o valor médio. PARTE II - Questões de Desenvolvimento Responda às questões de desenvolvimento em folhas de exame devidamente identificadas com nome e número.
[3.0V] 9. Considere uma lista simplesmente ligada de reais suportada num tipo L definido por typedef struct list { float x; struct list *next; L; Escreva uma função com protótipo float average(l *base); que receber um apontador para a base da lista e que retorna o valor médio de todos os elementos da lista. Nota: se a lista estiver vazia, o valor retornado deve ser 0. float average(l *b){ int n=0; float sum = 0; if(b == NULL) return 0; while(b!= NULL){ sum += b -> x; n++; return sum / n; [3.0V] 10. Considere uma árvore suportada num tipo T definido por typedef struct tree { float x; struct tree *left,*right; T; [2.0V] [1.0V] a) Escreva uma função com protótipo int count(t *r); que recebe como argumento um apontador para a raíz da árvore r e que retorna o número total de elementos na árvore. int count(tree *t){ if(t == NULL) return 0; else return count(t->l) + count(t->r) + 1; b) Indique a complexidade do algoritmo, admitindo que a árvore tem N nós. Dado que a cada nó da árvore é percorrido apenas uma vez, a complexidade é obviamente O(N). [3.0V] 11. O algoritmo quick sort baseia-se na noção de partição e função de partição. Considere uma função de partição aplicada a uma tabela de inteiros com o protótipo int partition(int a[],int l, int r) em que l e r são os índices que indicam o primeiro e último elemento do subconjunto da tabela que se pretende particionar. Admita que aplicamos o algoritmo de partição a uma tabela de 12 inteiros x[] e que, após a chamada a esta função na forma
i = partition(x,0,11); a tabela fica organizada como se segue: 28 7 41 8 1 50 82 78 57 75 60 94 [1.0V] [2.0V] a) Indique o valor de i retornado pela função partition. A partição divide a tabela em duas partes, em que a primeira corresponde a todos os valores menores que um elemento pivot, e a segunda todos os valores superiores. O valor retornado pela função é o índice pivot. É fácil verificar que i=5 b) Escreva uma função de ordenação baseada no algoritmo quick sort com protótipo void quicksort(int x[],int l, int r) e que utiliza a função partition() definida anteriormente. Nota: não é necessário escrever o código da função partition(). void quicksort(int x[],int l, int r){ int i; if(r <= l) return; i = partition(x,l,r); quicksort(x,l,i-1); quicksort(x,i+1,r); [1.0] 12. Considere o seguinte algoritmos recursivo: int xpto{int *tab, int N){ if(n<=1) return 0; else return xpto(tab,n/2) + xpto(tab+n/2,n/2); Admita para simplificar que N é uma potência de 2. complexidade da função xpto(). Determine a equação de recorrência e a C N = C N/2 + C N/2 = 2C N/2 com condição terminal C 1 = 1. Fazendo por conveniência, N = 2 k, C 2 k = 2C 2 k /2 = 2C 2 k 1 C 2 k 1 = 2C 2 k 2 ou seja C 2 k = 4C 2 k 2 C 2 k = 8C 2 k 3 Generalizando C 2 k = 2 j C 2 k j Para j = k, C 2 k = 2 k C 2 0 = 2 k C 1 = 2 k ou seja C N = N pelo que a complexidade é O(N).
[2.0V] 13. Considere o seguinte grafo: Determine os caminhos mínimos de A a todos os nós da árvore, usando o algoritmo de Dijkstra. Não se pretende o código, apenas a explicitação da sequência de operações e testes. Nota: a resposta só será valorizada se forem indicados todos os passos do algoritmo de Dijkstra. Passo 1, Nó A=0, resta {B, C, D, E, F C = 5, A C Passo 2, Nó B=2 (podia rb ser o F), resta {C, D, E, F : D = 5, B D Passo 3, Nó F=2, resta {C, D, E: Passo 4, Nó C=3 (podia tb ser o D ou E), resta {D, E: (nenhum caminho actualizado) Passo 5, Nó D=3 (podia tb ser o E), resta {E: (nenhum caminho actualizado) Passo 6, Nó E=3, resta {: (nenhum caminho actualizado)
logo A B, Custo = 2 A B C, Custo = 3 A F D, Custo = 3 A F E, Custo = 3, A F, Custo = 2