GABARITO – ISC02
Questão 1
Programa em C. Os outros serão equivalentes:
Nota: Não inclui os #includes necessários, pois isso o compilador pode indicar os headers necessarios.
void calculeProdInterno( int n, int *v, int *u, int *resp ) {
for( int i = 0 ; i < n ; i++ )
resp[i] = v[i]*u[i];
}
int calculeModulo( n, resp ) {
int sum = 0;
for( int i = 0 ; i < n ; i++ )
sum += resp[i]*resp[i];
return (int)sqrt( sum );
}
void main( int argc, char **argv ) {
int n, *v, *u, *resp;
n = atoi( argv[1] ); // Pegue o tamanho dos vetores
v = (int *)malloc( n*sizeof( int ) );
u = (int *)malloc( n*sizeof( int ) );
resp = (int *)malloc( n*sizeof( int ) );
for( int i = 0 ; i < n ; i++ ) {
v[i] = atoi( argv[i+2] );
u[i] = atoi( argv[i+n+2] );
}
calculeProdInterno( n, v, u, resp );
int k = calculeModulo( n, resp );
for( int i = 0 ; i < n ; i++ )
printf( “resp*%d+ = %d”, i, resp*i+ );
printf( “\nValor do Modulo e' igual a %d\n”, k );
}
Questão 2
Este algoritmo começa por escolher um elemento do vetor, chamado pivot. Normalmente o pivot é o primeiro elemento do
vetor. Em cada etapa, este pivot é comparado com todos os elementos do vetor e cada elemento menor que o pivot é deslocado
para o começo do vetor. Após cada passada, todos os elementos menores se encontrarão no começo do vetor, e os elementos
maiores ficam no fim do vetor. O pivot é então trocado com o último elemento menor que este; suponha que esta seja a posição
i do vetor. Recursivamente, passamos a ordenar da posição zero até a i, e da posição i+1 até n. A recursão avança até que a
chamada tenha apenas que ordenar um elemento, que por si só, já está ordenado.
No entanto, se pegarmos o pivot sempre do primeiro elemento e o vetor já estiver ordenado em ordem crescente ou
2
decrescente, este algoritmo apresentará custo quadrático O(n ). Para evitar este custo, devemos escolher o pivot de uma
posição aleatória do vetor e deslocá-lo para a primeira posição. Desta forma o custo passa a ser O(nlogn), já que na maioria das
vezes o valor pego como pivot vai separar o vetor, em cada passo, mais ou menos ao meio.
Como exemplo, considere o vetor:
6 5 3 1 8 7 2 4 → Separando o vetor segundo o pivot “6”
6 5 3 1 2 4 8 7 → Troque o pivot com o “4” que é o ultimo numero menor
4 5 3 1 2 6 8 7 → Executa-se a separação dos subvetores “4 5 3 1 2” e “8 7”, uma vez que o número 6 já está em seu lugar.
Todos à esquerda são menores e todos à direita são maiores que ele. Seguindo este esquema de recursão, ao fim o vetor estará
organizado em ordem crescente.
Obs: No caso do vetor “8 7”, como só contém dois valores, faz-se a otimização de ordená-los diretamente.
O custo computacional pode ser visto da seguinte forma: considerando-se n elementos no vetor original, cada subdivisão o
separa em uma potência de 2, ou seja:
n
n/2 + n/2
n/4 + n/4 + n/4 + n/4
…
k
k
k
k
n/2 + n/2 + … (2 vezes) … + n/2
k
k
Perceba que n/2 = 1, o que leva a n = 2 ; ou k = log(n). Este valor equivale à altura da árvore. Como o custo para juntar cada
etapa no retorno da recursão tem custo n (o número de elementos do vetor), o que nos leva a um custo de ordenação de
O(nlogn).
Questão 3
Listas encadeadas são estruturas que permitem a alocação dinâmica de elementos que guardam dados de interesse,
sendo que esses elementos contêm, além dos dados, um ponteiro que aponta para o próximo elemento da lista.
Como exemplo mostramos abaixo uma estrutura em C que guarda um inteiro, a função principal que permite inserir
elementos diferentes de zero e, ao lado, a função de inserção na lista:
struct _elem {
int v;
struct *_elem prox;
} elem;
Void main() {
elem *primeiro = NULL;
int v = 1;
while( v != 0 ) {
scanf( “%d”, &v );
if( v != 0 ) insere( &primeiro, v );
}
Insere( elem **no, int v ) {
if( *no == NULL ) {
*no = malloc( sizeof(elem) );
(*no)->v = v;
(*no)->prox = NULL);
} eles {
insere( &(*no), v )
imprime( primeiro );
}
}
}
imprime( elem *no ) {
if( no != NULL ) {
printf( “%d “, no->v );
imprime( no->prox );
}
}
Do que jeito implementado, o custo de inserção será de O(n).
Questão 4
Esta arquitetura é dividida em três partes, a saber: nível externo, nível interno e nível conceitual, sendo esse último
um nível de interface entre os dois primeiros.
Nível Externo: é o ni´vel mais perto do usuário. Em termos de utilidade, ele pode ser um usuário ou um programa de
usuário ou um administrador, sendo que este último estará também interessado nos níveis conceitual e interno.
Um usuário terá a sua disposição uma linguagem de programação, podendo ser uma API SQL ou uma linguagem de
alto nível como Cobol, PL1, C. Sendo a linguagem de alto nível, ressaltamos que ou ela contém um subconjunto de
SQL, ou ela tem à disposição uma API que permite acessar uma sublinguagem SQL para fazer o acesso aos bancos de
dados.
Em princípio, toda sublinguagem SQL contém pelo menos duas partes bem distintas, “Data Definition Language”
(DDL), que permite a declaração de objetos para os bancos de dados, e a “Data Manipulation Language” (DML), que
permite a manipulação destes objetos.
Nível Interno: é uma camada de baixo nível, responsável por cuidar da gravação dos registros criados e salvos pelo
nível do usuário. No entanto, essa camada da arquitetura não lida diretamente com a paginação entre a memória
secundária e a memória principal. Ele define o esquema interno de representação dos registros, especifica os índices
necessários, como os registros gravados serão representados, a sequência física dos registros. Esta camada da
arquitetura é escrita em uma “data definition language” própria, que não precisa ter correlação com a DDL utilizada
pelo usuário no nível externo.
Nível Conceitual: é a camada intermediária, faz com que os dados entrados pelo usuário sejam representados em
um formato que permitirá, ao nível interno, gerenciá-los de modo adequado. Essa camada é escrita em outra “data
definition Language”. Para garantir independência entre a representação e os dados, as definições DDL neste nível
não devem ser baseadas em nenhuma consideração a respeito das estruturas dos registros a serem gravados, nem
às técnicas de acessos a esses registros.
Por fim, é importante mencionar que o nível conceitual não inclui apenas a representação intermediária dos dados
entre os níveis externo e interno, mas é responsável por importantes tarefas, tais como segurança e regras de
integridade dos dados.
Questão 5
O proposito de um sistema cliente/servidor de bancos de dados é fornecer, do lado dos aplicativos ou usuários, uma
interface de alto nível de acesso aos registros de dados. De um modo simplificado, tal arquitetura pode ser formada
por apenas duas camadas, de um lado um servidor (também chamado de “backend”) e do outro lado por vários
clientes (também chamados de “frontends”).
O Servidor é um “Data Base Manegement System” (DBMS). O DBMS é responsável por três tarefas principais: 1)
Intercepta os pedidos dos usuários e analisá-los; 2) Inspeciona o esquema externo para o usuário, gerencia o
mapeamento entre as camadas externas e conceitual, gerencia a representação conceitual, gerencia o mapeamento
entre as camadas conceitual e interna, e gerencia as definições das estruturas de armazenamento; 3) Por fim,
executa as operações necessárias sobre o banco de dados.
Os Clientes são os vários aplicativos que rodam se comunicando com o DBMS, pedindo acessos aos dados. Um
cliente pode ser também um usuário que, por via de comandos diretos, lança comandos de acessos ao servidor. Para
o servidor, não faz diferença se o pedido vem de um aplicativo ou de um usuário direto, uma vez que ambos estarão
usando o mesmo conjunto de funções de pedido de acesso aos dados.
Já os aplicativos podem ser separados em duas categorias: aplicativos implementados por usuários, normalmente
escritos em C ou Cobol, ou aplicativos fornecidos por empresas, também conhecidos como ferramentas. Essas
ferramentas podem se encaixar nas seguintes categorias: planilhas, pacotes estatísticos, ferramentas de
gerenciamento de copias, entre outas.
Se o servidor for executado em uma máquina e os clientes executarem em máquinas diferentes, essa configuração é
conhecida por processamento distribuído. As diferentes máquinas são conectadas via rede.
Entre as características de um sistema distribuído estão: 1) As respostas aos pedidos dos clientes já são feitas em
paralelos pois hoje em dia praticamente todos os computadores já são produzidos com multiprocessadores, o que
aumenta a eficiência nas respostas; 2) As máquinas que executam o DBMS podem ser montadas de modo a
melhorar ainda mais a performance do DBMS. 3) A maquina do cliente também pode ser personalizada de modo a
oferecer melhores interfaces para “query” ao DBMS e facilitar sobremaneira a utilização do lado do cliente; 4) Vários
clientes podem acessar um único servidor DBMS, diminuindo o custo de montagem de um sistema cliente/servidor.
Questão 6
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="description" content="3rd Workshop on Applications for Multi Core Architectures" />
<meta name="keywords" content="multicore, manycore, applications" />
<script type="text/javascript" src="mdcr.js"></script>
<link rel="stylesheet" type="text/css" href="template.css" media="screen,projection" />
<link rel="stylesheet" type="text/css" href="print.css" media="print" />
<title>Concurso Cepel 2014</title>
</head>
<body>
<!-- Header -->
<center>
<tr><td colspan="2" class="header">
<br>
<h2>
<p style="font-family:times;color:#1919A0"> Ficticia S.A.<br></p>
</h2>
</td></tr>
</center>
<!-- ===================================================== -->
<table border="1" width="50%"
summary="This table gives some statistics about fruit
flies: average height and weight, and percentage
with red eyes (for both males and females).">
<caption><em>Tabela Teste Para a Prova do Cepel</em></caption>
<tr><th><center>Nome</center><td><center>Idade</center><td><center> Salário </center>
<tr><th>Rodrigo Maia Silva <td><center> 32 </center><td><center> 1.200,00 </center>
<tr><th>Regina Amaro Santos <td><center> 35 </center><td><center> 1.350,00 </center>
<tr><th>Laura Souza
<td><center> 30 </center><td><center> 1.150,00 </center>
</table>
<!-- ==================================================== -->
</body>
</html>
Questão 7
Exemplo de como criar uma tabela simples para guardar o nome, sobrenome e idade dos parentes:
Criando um database
create database familia;
Abrindo o database:
use familia;
Criando uma tabela:
create table silva ( nome
varchar(20),
sobrenome varchar(20),
idade
numeric(3), );
Ou “number” em sql.
Averiguando a tabela:
Show tables;
describe silva;
Inserindo os dados de dois parentes:
insert into silva (nome, sobrenome, idade) values ('Geraldo', 'Silva', 22 );
insert into silva (nome, sobrenome, idade) values ('Mario', 'Silva', 26 );
Listando os dados do bando de dados família:
select * from silva;
select * from silva where idade > 24;
Saindo do sql:
quit;
Questão 8
Árvores binárias de busca são estruturas que permitem o armazenamento organizado de dados. Este tipo de árvore
exige que se considere uma regra no momento da inserção de elementos. O mais usual é que cada novo elemento
seja comparado com cada elemento existente na árvore e, começando pela raiz, se o novo elemento for menor, será
inserido à esquerda do elemento atual; se for maior, será inserido à direita do elemento atual. Como exemplo
suponha a árvore abaixo:
10
6
4
12
15
20
O elemento 10 é a raiz da arvore. Se tentarmos inserir o número 8, ele será comparado com a raiz; como é menor e
existe um nó da árvore à esquerda, vamos compará-lo com este elemento a esquerda, ou seja, com o número 6. Por
ser maior, e como o 6 não tem elemento à direita, o número 8 pode ser inserido neste local, resultando em:
10
6
4
8
15
12 20
O pior caso acontece quando os valores inseridos estão em ordem crescente ou decrescente. Um conjunto de
números em ordem crescente resultaria numa árvore completamente desbalanceada, como vemos a seguir,
4
6
8
10
12
15
20
ocasionando um custo de insercao quadratico: O(n2)
Já o melhor caso, que foi mostrado no exemplo anterior, ocorre quando a inserção acompanha uma ordem em que
não há muita diferença de profundidade entre o ramo mais alto e o mais baixo da árvore. Neste caso se diz que a
árvore encontra-se balanceada.
Questão 9
Árvores rubro-negras são regidas por quatro regras que garantem seu balanceamento, ao contrário das árvores
binárias de busca comuns. Tais regras consistem em impor as seguintes restrições aos nós da árvore:
1.
2.
3.
4.
5.
Todo nó é colorido de preto ou vermelho.
A raiz da árvore é preta.
Todas as folhas da árvore são pretas.
Todo nó vermelho tem filhos pretos.
Qualquer caminho entre um nó da árvore e suas folhas descendentes, tem o mesmo número de nós negros.
Como exemplo, considere a árvore a seguir:
A argumentação sobre o balanceamento pode ser feita, de forma sucinta, pensando-se que se a partir da raiz, o
ramo esquerdo tem “k” filhos pretos, então o ramo direito poderá ter no máximo “2k” filhos vermelhos, o que
significa que esse ramo da árvore terá uma altura de no máximo o dobro da do outro ramo. Essa diferença, por ser
linear, é considerada um balanceamento em termos de custo computacional.
Questão 10
Classes são extensões dos tipos de dados abstratos “structure” que incluem, além da possibilidade de armazenar
dados em variáveis, funções que permitem que o tratamento desses dados seja feito de forma transparente para o
programador que utiliza essa classe. Essa característica traz vários benefícios. Podem ser criadas funções criadoras e
destruturas que se encarregam de inicializar e limpar qualquer alocação de memória, por exemplo.
Além disso, as funções membro das classes, como são chamadas em programação orientada a objetos, fazem com
que a manipulação dos dados de uma classe independa de o programador saber ou não o nome das variáveis. Isto
permite que um grupo que mantenha um conjunto de classes, ao manter o nome das funções membro, possa alterar
o interior das classes, sem que isso atrapalhe a reutilização de códigos e organizações externas destas classes.
Comparando com a programação procedural, na qual variáveis são criadas ao longo dos códigos, se o programador
precisa mudar o nome de uma variável, todas as funções que utilizem esta variável terão que ser alteradas por
todos os programadores que já a usaram.
Questão 11
O merge sort funciona em uma primeira etapa dividindo o vetor de n elementos ao meio; em seguida ele divide cada
subvetor ao meio, e assim por diante, até que reste apenas um elemento em cada subvetor. Quando chega nessa
etapa, por conter apenas um elemento, este subvetor está ordenado. Então começa a segunda etapa, na qual cada
dupla vizinha de subvetores é juntada de forma que, ao fim dessa fusão, os elementos estarão em ordem. Isto é feito
escolhendo-se o menor elemento entre os dois subvetores; o escolhido é transportado para um vetor auxiliar e,
repetindo-se esse procedimento para todos os elementos dos subvetores, garantimos que este subconjunto do vetor
original estará ordenado. Agora, repete-se o procedimento para todos os subvetores criados na primeira etapa.
Ao fim, o vetor original estará ordenado. Apresentamos um exemplo abaixo, no qual representamos a separação, em
cada etapa, por dois pontos:
65318724
6531:8724
6 5 : 3 1 :: 8 7 : 2 4
6 : 5 :: 3 : 1 ::: 8 : 7 :: 2 : 4
Agora que temos todos os subvetores com apenas um elemento, executamos o retorno da recursão, juntos cada
dupla de subvetores em ordem crescente:
5 6 : 1 3 :: 7 8 : 2 4
1356:2478
12345678
Pronto...
A análise da complexidade, caso algum candidato se aventure:
O custo computacional pode ser visto da seguinte forma: considerando-se n elementos no vetor original, cada
subdivisão o separa em uma potência de 2, ou seja:
n
n/2 + n/2
n/4 + n/4 + n/4 + n/4
…
n/2k + n/2k + … (2k vezes) … + n/2k
Perceba que n/2k = 1, o que leva a n = 2k ; ou k = log(n). Esse valor equivale à altura da árvore. Como o custo para
juntar cada etapa, no retorno da recursão, é n (o número de elementos do vetor), o custo de ordenação é de
O(nlogn).
Questão 12
Como exemplo de como fazer esta interação, implementamos uma simples calculadora aritimética para servir de
base para a solução:
<html>
<head>
<title>Calculadora Javascript</title>
<script language = javascript type = "text/javascript">
var plus,minus,divide,times
function initialise(){plus = document.calc.operator.options[0]
minus = document.calc.operator.options[1]
divide = document.calc.operator.options[2]
times = document.calc.operator.options[3]}
function calculate(){x = parseInt(document.calc.val1.value)
y = parseInt(document.calc.val2.value)
if(plus.selected)
document.calc.answer.value = x+y
if(minus.selected)
document.calc.answer.value = x-y
if(divide.selected)
document.calc.answer.value = x/y
if(times.selected)
document.calc.answer.value = x*y}
</script>
</head>
<body onLoad="initialise()">
<h2>Calculator</h2>
<br>
<form name="calc" action="post">
<input type=text name=val1 size=10>
<select name=operator>
<option value=plus>+
<option value=minus><option value=divide>/
<option value=times>*
</select>
<input type=text name=val2 size=10>
=
<input type=text name=answer size=10>
<input type=button value=answer onClick="calculate()">
</form>
</body>
</html>
Download

GABARITO – ISC02 Questão 1 Programa em C