Você está na página 1de 76

MANIPULAÇÃO DE DADOS EM MEMÓRIA SECUNDÁRIA

Principais diferenças entre memória principal e memória secundária:

Tipo MEMÓRIA PRINCIPAL MEMÓRIA SECUNDÁRIA


Capacidade de Armazenamento Menor Maior
Velocidade Maior Menor
Tipo de Armazenamento Livre Arquivos

Tipos de memórias:

● Memória primária ou principal – memória de alta velocidade para armazenamento de


programas e dados em uso pelo computador. São memórias com tempo de acesso muito
baixo, mas com custo por byte relativamente alto. A memória RAM é o principal exemplo de
memória primária. Na maioria das vezes são memórias voláteis (que se apagam quando a
máquina é desligada).

● Memória secundária – memória utilizada para armazenar grandes volumes de dados que
não são comportados na memória principal ou em que a relação custo/benefício não é
vantajosa. É, portanto, significativamente mais lenta, mas também mais barata que a memória
primária. Programas e dados que não estão em uso imediato pelo computador, mas que
devem estar facilmente acessíveis também são armazenados em memória secundária. O
disco rígido é o tipo de memória secundária mais usual nos dias de hoje.

● Memória terciária – memória utilizada para armazenar gigantescos volumes de dados,


quando o tempo de acesso não é um fator crítico. Exemplos de memórias terciárias são as
jukeboxes de CDs ou DVDs e os silos robotizados de fitas. A memória terciária também é
utilizada para fins de cópias de segurança (backups).

Quando um determinado sistema requer o acesso e/ou a manipulação de um grande volume de


dados, que não cabem na memória primária, ele deve usar uma memória secundária, como o
disco rígido, para armazenamento desses dados.

Salvar dados em blocos e sequencialmente ganha tempo no processamento devido a forma como
o disco rígido trabalha.

FLUXO DE ENTRADA E SAÍDA DE DADOS

● A escrita de dados em um arquivo do disco rígido é um fluxo de saída.


● A leitura de dados em um arquivo do disco rígido é um fluxo de entrada.

Observe que o fluxo é observado pela perspectiva do programa e ocorre de forma unilateral

ARQUIVOS COMO VETORES DE BYTES

Qualquer informação que desejamos escrever em um arquivo deve ser convertida em bytes. Um arquivo
é apenas uma sequência de bytes, sendo o programa o responsável por entender o que essa sequência
significa e como tratá-la.
Um “ponteiro” dentro do programa, define em qual posição será lido ou escrito um byte. Esse ponteiro
segue sequencialmente.
Existem algumas regras para ler a sequência de bytes e entender o que ela significa. Essas regras
variam de acordo com o sistema operacional e o sistema de codificação.

Normalmente o tamanho de cada arquivo é predefinido por padrão, aviso antecipado (a string tem 5
caracteres) ou final (Lucia\0).

Strings têm identificadores que as antecedem explicando quantos caracteres tem, sendo aqueles
acentuados contados como 2 caracteres. Outra possibilidade é ter um delimitador no final da string que
diz quando ela acaba.

A sequência de bytes de uma struct é chamada de registro em um arquivo.

ARQUIVOS DE DADOS ESTRUTURADOS

Arquivos de dados estruturados são arquivos que têm uma estrutura definida. Normalmente são
conjuntos de entidades com a mesma estrutura definida.

● Uma entidade é uma coisa ou objeto do mundo real, que pode ser físico (ex.: pessoa, carro,
produto, ...) ou abstrato (ex.: evento, cargo, matrícula, ...).
● Um atributo é uma propriedade que ajuda a descrever uma entidade. Por exemplo, se a
entidade representar um funcionário, então os atributos podem ser: nome, data de
nascimento, endereço, telefone e email; se a entidade representar um evento, então os
atributos podem ser: descrição, data, horário, local e observações.
● Um registro é uma sequência de bytes em um arquivo que contém/representa uma entidade.
● Um campo é uma parte do registro, também na forma de uma sequência de bytes, que
contém um atributo da entidade.
SERIALIZAÇÃO
Trata-se da transformação de uma entidade ou atributo em um registro ou campo.

● Existem alguns atributos que são considerados como falsos números. Isso acontece quando o
número ou a sequência é dada para o propósito de identificação, sem ter qualquer relação com o
seu subsequente ou antecessor. Como por exemplo o CPF, o RG, número telefônico, etc. Esse
tipo de atributo é aconselhável guardar em formato string e não int, por ocupar menos espaço.
● Caracteres podem ser substituídos por bytes, uma vez que cada número representa uma letra
dentro da programação. Isso é util quando o objetivo é poupar espaço e o usuário só vai utilizar
um char.
● O formato Char normalmente usa-se 2 bytes por caractere, mas quando se trata de string, cada
caractere usa apenas 1 byte.

O espaço utilizado na serialização segue a tabela:

Tipo Descrição Valores

byte Número inteiro de 8 bits com sinal -128 a 127

short Número inteiro de 16 bits com sinal -32.768 a +32.767

int Número inteiro de 32 bits com sinal -2.147.483.648 a 2.147.483.647

long Número inteiro de 64 bits com sinal -9.223.372.036.854.775.808 a


+9.223.372.036.854.775.807
float Número de ponto flutuante de 32 bits ±1,40129846e-45 a ±3,40282347e+38
com sinal
double Número de ponto flutuante de 64 bits ±4,94065645841246544e-324 a
com sinal ±1,79769313486231570e+308
char Caractere ASCII (1 byte) ou Unicode de Qualquer caractere
16 bits (2 bytes)
boolean Variável lógica que indica falso ou true ou false
verdadeiro (1 byte)

STRINGS DE TAMANHO FIXO

Algumas strings podem ter tamanhos pré determinados, como por exemplo o número de telefone, o cpf e
atributos do tipo.
● Pode ser utilizado a estatística para determinar quantos caracteres é o provável de ser utilizado
em algum atributo.
● Uma string de tamanho fixo sempre terá o mesmo tamanho.

STRING DE TAMANHO VARIÁVEL


As opções para utilizar strings de tamanho variável na serialização são:
● Utilizar 2 bytes com indicador de tamanho antes da string

Ind de Tamanho C o n c e i ç ã o
00 0B 43 6F 6E 63 65 69 C3 A7 C3 A3 6F

● Utilizar um delimitador no final da string

C o n c e i ç ã o \n
43 6F 6E 63 65 69 C3 A7 C3 A3 6F 0A

SISTEMA DE CODIFICAÇÃO

Trata-se de uma tabela representativa do valor em bytes para cada símbolo. Alguns sistemas de
codificação são:
● ASCII (1960) - American Standard Code for Information Interchange
Representação de 128 símbolos
65 = 0x41 = ‘A’
O 8º mais significativo era usando como bit de paridade.
Devido à falta de símbolos no sistema ASCII, foram criados sistemas complementares, que
iniciavam a partir do 129.
● Unicode - Sistema que abrange todas as línguas já criadas, inclusive línguas mortas e fictícias.
Mais de 100 mil símbolos

O unicode foi determinado como sistema de codificação universal, porém, existem 3 diferentes formatos:
● UTF-8 - Codificação Unicode de tamanho variável (1 a 4 bytes), que pode representar qualquer
caractere (code point) do padrão. Padrão da internet e na Web.
● UTF-16 - Codificação de 2 ou 4 bytes, usada principalmente para escrita em idiomas de países
asiáticos. (Quando usamos char, é utilizado essa codificação, por isso é usado 2 bytes).
● UTF-32 - Codificação de comprimento fixo 4 bytes.

A UTF-8 controla a quantidade de bytes através do primeiros bits, responsáveis por dizer quantos bytes
serão utilizados.

1 Byte - Tabela ASCII


2 Bytes - Caracteres Latinos, hebraicos, gregos..
3 Bytes - Caracteres chineses, japoneses, coreanos…
4 Bytes - Outros caracteres e símbolos
REGISTROS

Os registros, assim como as entidades, podem ter tamanho fixo ou variável. A forma como isso é
colocado em prática depende da programação.

Para trabalhar com registros de tamanho fixo temos as seguintes alternativas:


● Campo de tamanho fixo - Assim o espaço utilizado sempre será o mesmo. Como no caso dos
números telefônicos e CPFs.

ID NOME PREÇO
1 G E L A D E I R A 2750,00
2 P I A ‘ ‘ ‘ ‘ ‘ ‘ ‘ ‘ ‘ ‘ ‘ ‘ 395,00
3 V A S O ‘ ‘ ‘ ‘ ‘ ‘ ‘ ‘ ‘ ‘ 400,00
4 Bytes 9 Bytes 4 Bytes

● Registros de tamanho fixo com campos de tamanho variável - Trata-se de um registro com
tamanho máximo onde os campos preenchem os espaços vazios de outros campos de mesmo
tipo.

ID NOME PREÇO
1 L I V R O \ A U T O R '' '' 2750,00
2 E X E M P L O \ A U T O R 395,00
3 G O S T \ M I A '' '' '' '' '' 400,00
4 Bytes 9 Bytes 4 Bytes

Para registros de tamanho variável temos as alternativas:


● A quantidade de campos sendo fixo e sempre na mesma ordem. Ex: Id, nome, e-mail; Id, nome,
e-mail.
● Leitura/escrita dos campos em ordem pré determinada.
● Indicador de tamanho do registro.
● Uso de delimitador de registro.
CABEÇALHO DE ARQUIVO
É o número utilizado para identificar quantos registros terão no arquivo.

CABEÇALHO
3 1 5 L I V R O 5 A U T O R 2750,00 2
7 E X E M P L O 5 A U T O R 395,00
3 4 G O S T 3 M I A 400,00

ACESSO AOS REGISTROS


Para encontrar um registro específico é necessário passar por um tipo de acesso. Esse acesso
normalmente utiliza a chave de ordenação para encontrar um registro específico. A chave de ordenação
pode ser qualquer coisa utilizada para ordenar os demais registros.

IDENTIFICADORES
As chaves de ordenação podem ser divididas em dois tipos:
● Chaves candidatas
São os meios de identificar determinado indivíduo ou objeto. Como CPF para pessoas, chassi
para carros, nome do livro mais do autor para livros, ID e etc.
● Chaves Primárias
É a chave candidata escolhida.

Alguns problemas de chaves candidatas:


● Nome - Algumas pessoas tem o mesmo nome e algumas podem mudá-lo.
● E-mail - Pessoas trocam o e-mail.
● Telefone - São reutilizados
● CPF e CNPJ - Muito longos

Sendo assim, a melhor alternativa normalmente é utilizar identificadores(ID) próprios do sistema, pois não
são longos, não podem ser reutilizados, não tem significado e são sequenciais.
O ID é uma informação interna do sistema, utilizado em buscas internas. Mas externamente o usuário
utiliza atributos do objeto.

ARQUIVOS SEQUENCIAIS

Em arquivos sequenciais os registros são acessados de forma sequencial, na ordem em que foram
armazenados.
● Normalmente usado quando há poucas ou nenhuma movimentação de registros
● O objetivo é o acesso rápido a um conjunto de registros.
● Não são bons para acesso aleatório.

Alguns tipos de chave de ordenação nos arquivos sequenciais são:


● Data de criação
● Chave primária - Atributo que serve para identificar de forma exclusiva. Como ID.
● Qualquer outra ordem
● Nenhuma ordem

Observações:
● O arquivo eventualmente precisará ser reordenado.
● Arquivos sequenciais são geralmente usados como arquivos temporários. Como por exemplo
relatórios
.
OPERAÇÕES EM ARQUIVOS SEQUENCIAIS
CRUD

O que é passado para fazer as operações:


● ID ⬅ arquivo.create(novo.objeto)
● Objeto ⬅ arquivo.read(ID/CPF/chaves)
● ok ⬅arquivo.update(objeto.atualizado)
● ok ⬅ arquivo.delete(ID/CPF/chaves)

Durante a exclusão do arquivo não faz sentido excluí-lo de fato, pois dessa forma teríamos que reformular todos os
registros o que demanda muito tempo. A melhor forma de tratá-lo é colocando uma lápide (marca de exclusão) nele.
Essa lápide pode ser usada para uma recuperação de dados ou usada para transcrever algum outro registro por
cima. Os registros só são realmente apagados quando há uma reordenação.
Na tabela abaixo é utilizado um byte como lápide, onde o espaço em branco significa que o registro não
foi apagado e o asterisco que o registro foi apagado. Nesse caso o registro de ID 3 foi apagado.

3 '' 1 5 L I V R O 5 A U T
O R 2750,00 '' 2 7 E X E M P L O 5
A U T O R 395,00 '*' 3 4 G O S T
3 M I A 400,00

Para criar arquivos quando não tem ordem específica, todo novo registro é colocado no fim do arquivo.
Quando há ordem, é preciso uma reordenação.

01: algoritmo create(objeto)


02: mover o ponteiro para início do arquivo (cabeçalho)
03: ler quantos registros existem através do cabeçalho
04: atribuir objeto.ID ← últimoID + 1
05: mover o ponteiro para início do arquivo
06: escrever objeto.ID no cabeçalho (numero max de registros)
07: criar registro para o objeto
08: mover para o fim do arquivo
09: escrever registro
10: fim-algoritmo

read (de uma única entidade)


01: algoritmo read(ID)
02: mover o ponteiro para o primeiro registro (após o cabeçalho)
03: enquanto não atingir o fim do arquivo
04: ler próximo registro
05: se registro.lapide ≠ '*'
06: então extrair objeto do registro
07: se objeto.ID = ID
08: então retornar objeto e terminar
09: fim-se
10: fim-se
11: fim-enquanto
12: retornar objeto vazio // null
13: fim-algoritmo

read (de um conjunto de entidades)


01: algoritmo read(critérios)
02: criar conjunto vazio
03: mover o ponteiro para o primeiro registro (após o cabeçalho)
04: enquanto não atingir o fim do arquivo
05: ler próximo registro
06: se registro.lapide ≠ '*'
07: então extrair objeto do registro
08: se registro atender aos critérios
09: então adicionar objeto ao conjunto
10: fim-se
11: fim-se
12: fim-enquanto
13: retornar conjunto
14: fim-algoritmo

Upudate
Quando atualizamos um arquivo, devemos primeiro verificar se o espaço do registro cabe o novo arquivo
atualizado. Caso caiba, não há problema. Se sobrar, deixamos os bytes de sobra como lixo sem alterar o
tamanho do arquivo. Caso não caiba, marcamos o arquivo como excluído e criamos outro no final com o
mesmo ID.

01: algoritmo update(novoObjeto)


02: mover para o primeiro registro do arquivo (após cabeçalho)
03: enquanto não atingir o fim do arquivo
04: pos ← posição do ponteiro
05: ler próximo registro
06: se registro.lapide ≠ '*'
07: então extrair objeto do registro
08: se objeto.ID = novoObjeto.ID
09: então criar novoRegistro para novoObjeto
10: se novoRegistro.tamanho ⩽ registro.tamanho
11: então mover para pos
12: escrever novoRegistro mantendo ind.tam.
13: senão mover para pos
14: escrever lápide como excluído
15: mover para fim do arquivo
16: escrever novoRegistro
17: fim-se
18: retornar verdadeiro e terminar
19: fim-se
20: fim-se
21: fim-enquanto
22: retornar falso
23: fim-algoritmo

delete
01: algoritmo delete(ID)
02: mover o ponteiro para o primeiro registro (após o cabeçalho)
03: enquanto não atingir o fim do arquivo
04: pos ← posição do ponteiro
05: ler próximo registro
06: se registro.lapide ≠ '*'
07: então extrair objeto do registro
08: se objeto.ID = ID
09: então mover para pos
10: escrever lápide como excluído
11: retornar verdadeiro e terminar
12: fim-se
13: fim-se
14: fim-enquanto
15: retornar falso
16: fim-algoritmo
ORDENAÇÃO EXTERNA

● É o processo de ordenação de dados em arquivos.


● Adotado quando os dados são muito grandes para se trabalhar na memória principal.
● Prioriza o acesso sequencial aos arquivos.

INTERCALAÇÃO BALANCEADA
● É o algoritmo que ordena os registros por meio da intercalação de registros de várias fontes
balanceadas (arquivos temporários com tamanho aproximado). Como se o arquivo fosse dividido
em vários outros arquivos de tamanho semelhante onde o algoritmo ordena esses arquivos na
ordem apropriada.

A intercalação balanceada de M caminhos é um algoritmo de intercalação que opera com


arquivos temporários. Os dados são extraídos do arquivo principal em blocos que podem
ser ordenados em memória principal. Em seguida esses blocos são escritos nos arquivos
temporários. Como geralmente haverá muito mais blocos do que arquivos temporários,
cada arquivo temporário conterá uma fila desses blocos como, por exemplo:
Arquivo_temporário_1: B1 B4 B7 B10 ...
Arquivo_temporário_2: B2 B5 B8 B11 ...
Arquivo_temporário_3: B3 B6 B9 B12 ...

Para garantir uma boa distribuição dos blocos, eles são escritos alternadamente entre os
arquivos temporários.
Em seguida, toma-se um bloco de cada arquivo temporário gerando um segmento
ordenado maior. Serão, assim, gerados vários segmentos ordenados que serão
distribuídos por um segundo conjunto de arquivos temporários, também de forma
balanceada:
Arquivo_temporário_4: S1 S4 S7 S10 ...
Arquivo_temporário_5: S2 S5 S8 S11 ...
Arquivo_temporário_6: S3 S6 S9 S12 ...

Nesse exemplo, o segmento S1 será composto pela intercalação dos blocos B1, B2 e B3;
o segmento S2 será composto pela intercalação dos blocos B4, B5 e B6; e assim em
diante.
O processo se repete, com a intercalação de um segmento de cada arquivo temporário, gerando
segmentos ainda maiores e escritos em um novo conjunto de arquivos temporários (o primeiro
conjunto pode ser reaproveitado neste momento).
As intercalações de segmentos ordenados nos arquivos temporários só se acabam quando
restar apenas um único segmento ordenado contendo todos os dados. Ex:
Considere a seguir as chaves de ordenação de um arquivo original.

Arq Inteiro 28 5 40 12 2 7 1 35 30 38 22 19 13 4 11 29 6 27 33 31 8 3 25 42 44 20 41 34 18 14 10

Neste exemplo considere que cada bloco contém 4 registros e existem apenas dois arquivos temporários.
Cada bloco é ordenado em memória principal e logo em seguida jogado no arquivo temporário.

Arq Temp 1 5 12 28 40 19 22 30 38 6 27 31 33 20 34 41 44

Arq Temp 2 1 2 7 35 4 11 13 29 3 8 25 42 10 14 18

Os primeiros blocos do Arquivo Temporário 1 e Arquivo Temporário 2 são ordenados em memória


principal e jogado em um terceiro arquivo temporário de bloco de tamanho 8. E isso também acontece
com os demais blocos.

Arq Temp 3 1 2 5 7 12 28 35 40 3 6 8 25 27 31 33 42

Arq Temp 4 4 11 13 19 22 29 30 38 10 14 18 20 34 41 44
O mesmo acontece até que reste apenas um único arquivo.

Arq Temp 5 1 2 4 5 7 11 12 13 19 22 28 29 30 35 38 40

Arq Temp 6 3 6 8 10 14 18 20 25 27 31 33 34 41 42 44

Arq
Final 1 2 3 4 5 7 8 10 11 12 13 14 18 19 20 22 25 27 28 29 30 31 33 34 35 38 40 41 42 44

A análise de complexidade do algoritmo de intercalação balanceada é baseada na quantidade de vezes


que o programa precisa entrar na memória secundária para ler todos os registros. O tempo de ordenação
na memória principal é desconsiderado devido ao fato de que o seu tempo de demora é insignificante
quando comparado com o tempo necessário para acessar a memória secundária.

Sendo assim, o que observamos é o número de vezes que será preciso ler todos os registros.

Cálculo de complexidade:

● N = numero total de registros


● b = tamanho do bloco ordenado em memória principal
● m = quantidade de destinos usados na intercalação
De acordo com o ex anterior:

Passadas = 1 + [log2 (31/4)]

Passadas = 1 + [log2 8]

Passadas = 1 + 2^3 = 8

<> OTIMIZAÇÃO DO ALGORITMO <>


INTERCALAÇÃO COM SEGMENTOS DE TAMANHO VARIÁVEL

Quando geramos os blocos ordenados na fase de distribuição, pode ser que eles estejam
ordenados entre si. Por exemplo, considere que os seguintes blocos foram gerados na fase de
distribuição:
Arquivo_temporário_1: B1 B4 B7 B10 ...
Arquivo_temporário_2: B2 B5 B8 B11 ...
Arquivo_temporário_3: B3 B6 B9 B12 ...

Nesse caso, se todas as chaves do bloco B4 forem maiores que as chaves do bloco B1,
podemos considerar que há um único segmento maior contendo os registros de B1 e B4. Nesse
caso, a primeira intercalação deveria considerar a seguinte distribuição:
Arquivo_temporário_1: (B1+B4) B7 B10 ...
Arquivo_temporário_2: B2 B5 B8 B11 ...
Arquivo_temporário_3: B3 B6 B9 B12 ...

Ou seja, os registros do segmento (B1+B4) seriam intercalados com os registros dos blocos B2
e B3; os registros dos blocos B7, B5 e B6 seriam intercalados em seguida; e assim em diante.
Como nem todos os segmentos possuem chaves ordenadas entre si, dizemos que eles têm
tamanho variável.
Finalmente, esperamos, ao trabalharmos com segmentos maiores, que o número de
intercalações para a ordenação completa do arquivo seja menor.
Como podemos ver no exemplo acima, no momento em que o algoritmo começa a criar novos arquivos
temporários, é feito uma comparação entre o último número do bloco com o seu posterior, verificando o
seu tamanho. Se o número posterior for maior, essa comparação continua com os próximos números até
que essa afirmação já não seja verdadeira. A partir desse momento que a comparação com outro bloco
começa.

INTERCALAÇÃO COM SELEÇÃO POR SUBSTITUIÇÃO


A intercalação por substituição é utilizada no estágio de distribuição. Para aumentar a probabilidade dos
arquivos já estarem ordenados em sentido crescente, utilizamos o que chamamos de HEAP de mínimo.
Como podemos ver, o Heap aparenta com uma árvore binária, mas nesse caso o filho esquerdo não tem
qualquer relação com o direito, não importando se é maior ou menor que seus pais. A única importância
em um heap de mínimo é os pais serem menores que os filhos.
Também utilizamos as seguintes operações para encontra os filhos e os pais, sendo i substituído pela
posição em questão:

Exemplo de utilização do
Reap de mínimo em
um arquivo:
● O número do segmento equivale a qual arquivo temporário se refere.
● Primeiramente inserimos as chaves no heap (no exemplo é de tamanho 7, mas quanto maior for o
heap, melhor).
● Mudamos de número de segmentos quando a próxima chave a ser inserida é menor que a da raiz
do heap.

Para organizamos o heap, é feita a seguinte operação: (n-1)/2


Sendo n igual ao número da última posição. No exemplo, teríamos: (6-1)/2 = 2,5. Arredondamos para
menor, logo teríamos 2. Assim, descobriremos os últimos pais do heap. Começamos comparando o pai
de posição 2 com o filho da esquerda e depois da direita. Caso haja filhos menores que o pai, fazemos a
troca. Em seguida comparamos o pai de posição 1, com o mesmo processo, até chegarmos ao pai de
posição 0.

Em seguida, comparamos a raiz com a próxima chave, tiramos Caso a próxima chave seja maior,
pegamos a raiz do heap e colocamos no primeiro arquivo temporário.

Depois inserimos a próxima chave no heap, organizamos o heap novamente, fazemos a comparação
entre chave e raiz e mandamos a raiz, caso ela seja menor e a próxima chave, para o mesmo arquivo
temporário.
Observe que a organização do heap é feita de cima para

baixo.
Até então, a inserção dos arquivos ocorreu de forma cíclica. Mas se observarmos, a próxima chave é
menor do que a raiz. Dessa forma mudamos o número do segmento para 1. Para que a nossa inserção e
organização do heap continue a fazer sentido, temos que afundar todas as chaves de segmentos
maiores para o fundo do heap, para que assim possamos continuar com a inserção no arquivo
temporário de número 1. observe o exemplo:

Assim, continuamos a
inserção até que todas
as chaves com o número

de segmento igual a 0 sejam inseridas.


Continuamos o processo até o seguinte momento:

Iniciamos agora a inserção do segundo arquivo como fizemos como o primeiro.


A ideia é entender que o número de segmentos é sempre mais importante que o número da chave,
sendo assim, se o número de segmento é maior do que o número de seguimento do próximo iten, não
importa se o número da chave é menor, ele continua sendo maior.

Uma última questão de importância é que, quando acaba todos os elementos do vetor, temos que
começar o processo de esvaziamento do heap, que ocorre da seguinte maneira:
Esvaziamos na seguinte ordem: 6 - 5 - 2 - 4 - 3 - 1 - 0

ARQUIVO INDEXADO

Um arquivo indexado é um arquivo que possui um ou mais índices que permitem o acesso aleatório a um
registro, dada uma determinada chave.
ex: (Índice secundário denso direto)
Nesse exemplo, o ID dos arquivos se encontra fora de ordem, e por isso criamos um índice com apenas
o ID e as posições do arquivo. Se procurarmos o ID no Índice, de forma sequencial, encontraremos a
posição do arquivo desejado. O índice pode conter qualquer tipo de chave.

TIPOS DE ÍNDICE
● Primario ou Secundario
● Direto ou Indireto
● Denso ou Esparso

ÍNDICE PRIMÁRIO
Segue a mesma ordem do arquivo de dados. Utilizado quando a massa de dados é tão grande que a
melhor opção é fazer a busca no índice para depois ir para o arquivo de dados.
ÍNDICE SECUNDÁRIO
Não segue a mesma ordem do arquivo de dados

ÍNDICE DIRETO
Aponta diretamente para o endereço do registro
ÍNDICE INDIRETO
Aponta para um índice direto, normalmente baseado na chave primária (que por sua ver aponta para o
arquivo de dados). Podemos ter vários índices indiretos apontando para um único índice direto.

ÍNDICE DENSO
Todos os registros estão no índice
ÍNDICE ESPARSO
Nem todos os registros se encontram no índice.

OPERAÇÃO EM ARQUIVO INDEXADO

Os seguintes códigos são exemplos de operações com arquivos indexados


create
01: algoritmo create(objeto)
02: mover o ponteiro para início do arquivo (cabeçalho)
03: ler últimoID
04: objeto.ID ← últimoID + 1
05: mover o ponteiro para início do arquivo
06: escrever objeto.ID
07: criar registro para o objeto
08: mover para o fim do arquivo
09: pos ← posição do ponteiro
10: escrever registro
11: inserir o par (objeto.ID, pos) no índice
12: fim-algoritmo

read (de uma única entidade)


01: algoritmo read(ID)
02: pos ← buscar o ID no índice
03: se pos ≠ -1
04: então mover o ponteiro para pos
05: ler registro
06: se registro.lapide ≠ '*'
07: então extrair objeto do registro
08: retornar objeto e terminar
09: fim-se
10: fim-se
11: retornar objeto vazio // null
12: fim-algoritmo

update
01: algoritmo update(novoObjeto)
02: pos ← buscar o ID no índice
03: se pos ≠ -1
04: então mover o ponteiro para pos
05: ler registro
06: se registro.lapide ≠ '*'
07:
objeto então extrair objeto do registro // para algum teste do
08: criar novoRegistro para novoObjeto
09: se novoRegistro.tamanho ⩽ registro.tamanho
10: então mover para pos
11: escrever novoRegistro mantendo ind.tam.
12: senão mover para pos
13: escrever lápide como excluído
14: mover para fim do arquivo
15: pos ← posição do ponteiro
16: escrever novoRegistro
17: atualizar o endereço para o ID no índice
18: retornar verdadeiro e terminar
19: fim-se
20: fim-se
21: fim-se
22: retornar falso
23: fim-algoritmo

delete
01: algoritmo delete(ID)
02: pos ← buscar o ID no índice
03: se pos ≠ -1
04: então mover o ponteiro para pos
05: ler registro
06: se registro.lapide ≠ '*'
07:
objeto então extrair objeto do registro // para algum teste do
08: mover para pos
09: escrever lápide como excluído
10: remover o ID do índice
11: retornar verdadeiro e terminar
12: fim-se
13: fim-se
14: retornar falso
15: fim-algoritmo

RELACIONAMENTOS
Trata-se da relação de uma estrutura a outra

TIPOS DE RELACIONAMENTOS
● Um para um (1:1)
Liga uma entidade de um tipo, para uma entidade de outro tipo
Ex: 1 Pessoa : 1 CNH
● Um para muitos (1:N)
Liga uma entidade de um tipo, para varias outras entidades de outro tipo.
Ex: 1 Casa : N moradores
● Muitos para muitos (N:N)
Liga varias entidades de um tipo para varias outras entidades de outro tipo.
Ex: Ator N : N filmes // Filme N : N Atores

ÁRVORE B
É semelhante a Árvore Binária, porém, possibilita que os nodos da árvore tenham mais de uma chave.
Nesse exemplo, caso buscássemos a chave de número 10:
1. 29 = 10? Se não, 10 < 29?
2. Se sim, 8 = 10? Se não, 10 < 8?
3. Se não, 8 < 10 < 15 ?
4. Se sim 10 = 10?
5. Se sim, chave encontrada.
A ideia da árvore B é carregar da memória secundária uma página inteira, guardando-a na memória
principal para manipulação ou leitura. Assim que terminado o uso dessa página, a memória primária
descarrega toda essa informação e busca outra página até que as operações acabem. Dessa forma,
diminuímos a quantidade de acessos a memória secundária.

O que significa Ordem da Árvore B? Existem duas definições


● A Ordem de uma Árvore B é o mínimo de chaves que cabem dentro de cada nodo/página (menos
a raiz). No exemplo acima, a ordem seria 2. (Todas as páginas têm no mínimo 2 chaves, exceto a
raiz).

● A Ordem de uma Árvore B é o número máximo de filhos que uma página pode ter. No exemplo
acima, a ordem seria 5. (Cada página pode ter até 5 filhos. Obs: São as bolinhas). <= Mais usual

As regras da Árvore B são:


1. Cada página, exceto a raiz, deve ter pelo menos 50% de ocupação.
2. Cada página deve ter o número de filhos igual ao seu número de chaves + 1 (exceto folhas
porque não tem filhos).
3. Todas as folhas estão sempre no mesmo nível, pois a árvore cresce pra cima.

ESTRUTURA DA ÁRVORE B EM BYTES


N - Número de elementos ocupados na árvore, sendo o máximo igual ao número de filhos -1Pi
Ci - Chave do registro
Di - Dados (Ex, endereço do registro do arquivo)
Pi - Ponteiro para o enésimo filho (São os ponteiros que apontam para os filhos da página, contendo o
endereço das páginas subsequentes). Quando trata-se de folha, os ponteiros são nulos, normalmente
preenchidos por zeros.
Ex:

Obss: O ponteiro da raiz indica em qual endereço está a raiz.


As árvores, para serem eficientes, adotam paginas de tamanho fixo.

INSERÇÃO EM UMA ÁRVORE B


1. Localize a folha em que a chave deve ser inserida;
2. Se a chave couber na folha, inseri a chave e termine;
3. Se não couber,
1. Crie uma nova folha;
2. Mova a metade superior das chaves para essa nova folha;
3. Se a chave a ser inserida for menor que a primeira chave da segunda
(nova) folha,
1. Insira a nova chave na folha antiga (da esquerda);
2. Promova a maior chave dessa folha antiga, com o ponteiro direito
para a nova folha;
4. Senão,
1. Insira a nova chave na nova folha (da direita);
2. Promova a menor chave dessa nova folha, com o ponteiro direito
para a nova folha;
4. Se a chave promovida não couber na página pai, repita o algoritmo a partir do passo 2
acima.
Passo 1

Passo 3.1
Passo 3.2

Passo 3.3.1

Passo 3.3.2
REMOÇÃO EM UMA ÁRVORE B

A remoção em uma árvore B está sujeita a três casos diferentes:


1. Se o elemento estiver em uma folha e a folha tiver mais de 50% de ocupação, basta removê-lo

2. Se o elemento não estiver em uma folha, trocá-lo pelo seu antecessor e deletá-lo
3. Se a folha for ficar com menos de 50% de ocupação, mas a página irmã puder ceder uma chave,
faça a redistribuição de chaves e delete a chave correspondente.

4. Se a folha ficar com menos de 50% de ocupação e as páginas irmãs não puderem ceder uma
chave, é necessário fazer a fusão de duas folhas.
A seguir, os passos completos de uma remoção em árvore B:

ETAPA 1 - Remoção da chave em uma folha


1. Localize a chave a ser removida.
2. Se ela estiver em uma folha,
1. Então remova a chave.
2. Senão remova a chave e coloque em seu lugar a sua chave
antecessora (descendente de maior valor da sub-árvore esquerda, que
está em uma folha). Trate a exclusão como se fosse dessa chave
antecessora em uma folha.
ETAPA 2 - Cessão de chave de alguma folha adjacente
3. Se a folha tiver ficado com menos de 50% de ocupação,
1. Então veja se a folha adjacente direita (se existente) pode ceder uma
chave.
1. Se puder, faça a rotação.
2. Se não puder, veja se a folha adjacente esquerda (se existente) pode
ceder uma chave.
1. Se puder, faça a rotação.
ETAPA 3 - Fusão
4. Se a folha ainda estiver com menos de 50% de ocupação e nenhuma folha
adjacente puder ceder uma chave,
1. Se existir uma folha adjacente direita, faça a fusão entre as folhas.
2. Se não existir, faça a fusão com a folha adjacente esquerda.
5. Na fusão, a chave que divide as duas folhas deve ser demovida (descer para a
folha resultante da fusão).
ETAPA 4 - Propagação
6. Se, após a fusão, a página pai ficar com menos de 50 de ocupação, voltar à
ETAPA 2 considerando essa página.
7. As fusões podem ser propagadas até a raiz se necessário. Se a raiz tiver só dois
filhos que precisarem ser fundidos, então a árvore terá sua altura reduzida.

ÁRVORE B+
A árvore B+ é utilizada quando a busca a ser feita tem como objetivo retornar uma sequência de chaves,
e não apenas uma.
Funcionamento da Árvore B+:
● Todas as chaves válidas estão armazenadas nas folhas
Isso significa que as chaves antecessoras em nível são utilizadas apenas como objeto de
comparação para assim criar um caminho mais fácil para as chaves necessárias.
● Cada folha aponta para a próxima folha, permitindo assim, um acesso sequencial

● As folhas podem possuir uma estrutura diferente das páginas não folhas, por serem as únicas
páginas a carregar dados.
Enquanto os nodos podem carregar apenas um número em sua estrutura para direcionar a busca,
as páginas folhas podem conter toda a estrutura de dados necessária ou parte dela.

Observe que no fim de cada folha existe um ponteiro apontando para a próxima e que como os números
dos nodos não são realmente válidos, eles são duplicados. Na árvore B+ as únicas comparações feitas
são:
1. A página é uma folha?
1.1. Se sim, a chave é igual ao número da folha?
1. Retorna as chaves pedidas sequencialmente.
1.2 Se não, a chave é igual ao número à direita?
1.2.1 Se não, o processo é repetido até encontrar o número correspondente ou retornar -1.
2. Se não, a chave é menor que o número da folha? Se sim, desce pela direita, se não pela
esquerda.
3. O processo é repetido até encontrar uma página folha.
A chave a ser copiada é sempre a menor do filho direito.

Há a opção de substituir o 10 pelo 12


TABELA HASH

● Algoritmo cuja o custo do acesso é igual O(1).


● A ideia é ir diretamente ao recurso desejado.
● É muitas vezes utilizado como um índice que contém apenas endereços.
● É utilizado uma função de dispersão para definir a posição das chaves de forma aleatória.
● A posição da chave deve ser multiplicada pelo tamanho do índice para conseguirmos identificar
até qual posição vai o registro daquele índice.

● A função de dispersão pode ser qualquer função, mas vale considerar que quanto mais disperso
for seus resultados, melhor a funcionalidade da tabela.
● Para encontrarmos determinada chave no arquivo, basta fazer a função de dispersão e ir no
endereço resultante. Por exemplo, se quero a chave 1 e a função de dispersão é h(x) = 2x, basta
fazer 1x2=2 e logo ir ao endereço 2 da tabela.
● Ex de função de disperção - [ h(x)=x^2 ] Após feito isso, pegamos o meio do número como
resposta. 40^2=1600. Pegamos o número 60 por exemplo.

COLISÕES
Quando dois registros são mandados para o mesmo endereço, nós chamamos isso de colisão. Quando
dois registros colidem, temos duas alternativas, usamos o encadeamento interno ou o encadeamento
externo.

- Encadeamento Interno
É quando colocamos o endereço em colisão no mesmo índice que os demais. Assim, se uma
determinada posição está cheia, procuramos a próxima dentro do índice. Existem algumas formas cuja
busca é feita.
Sondagem Linear - É quando buscamos de forma sequencial. Assim, se a posição 2 está cheia,
buscamos na 3, e assim por diante. Outra observação importante é usar a lápide para elementos
apagados. Pois nesse tipo de sondagem, quando buscamos algum elemento que foi vítima de colisão e o
próximo endereço está vazio, isso significaria que esta chave não existe.

i = número de endereços saltados


h(k,i) = [h(k) +i] mod n n = numero máximo da tabela.
Sondagem Quadrática - ocorre quando elevamos o número das tentativas de busca ao quadro e então
somamos ao endereço original, usando assim o endereço endereço resultante. Como exemplo, se a
posição 2 está cheia, fazemos 2+12=3 , e se a posição 3 estiver cheia, fazemos 2+22=6 .

i = número de endereços saltados


h(k ,i)=h (k )+ i2 mod n n = número máximo da tabela

Duplo Hash - Calculamos a distância até a próxima posição a ser sondada através de uma segunda
função hash.

i = número de endereços saltados


h(k ,i)=[h(k )=i∗h' (k )]mod n n = número máximo da tabela
h’(k) = segunda função hash

- Encadeamento Externo
É a utilização de duas tabelas para o tratamento de dados. Dessa forma, no caso de colisão,
usamos outra tabela para enviar a chave.
Área de Extensão - Quando ocorre colisão, criamos uma área de extensão para onde mandamos o
arquivo colidido. Neste tratamento de dados, é necessário criar um elemento no registro que indique o
próximo endereço a ser buscado no caso de colisões.
Lista Encadeada - Fazemos duas tabelas, uma apenas com indicador de endereço, chamada cabeça.
Essa tabela só é responsável por indicar o endereço de uma outra. A outra tabela, contém a chave, o
endereço da chave e quando há colisões, o "próximo" indica onde o número colidido foi parar. Essa
tabela não precisa ser de tamanho, e por isso torna-se mais eficiente que as demais.
Buckets
Os Buckets são úteis quando buscamos mais de um registro. Dessa forma, cada endereço da tabela terá
não apenas uma chave e um endereço, mas vários outros.

O tratamento de colisão quando o bucket estiver cheio, pode ser dado da mesma forma que é feito com a
tabela hash comum, adicionando assim um campo com um indicador, por exemplo. Uma observação é
que a possibilidade de usar registros já preenchidos pela metade ou como no caso do registro de
endereço 8 da imagem, é mais eficiente do que o uso de registros completamente vazios.

Hash Extensível
Trata-se de uma forma eficaz de aumentar o tamanho do índice sem que seja necessário realocar todas
as chaves.

Neste exemplo, observe que no hash extensível é necessário o uso de um segundo índice que funciona
como ponteiro.
A letra p representa o nível de profundidade. Onde o p representa a profundidade global e o p’ a
profundidade local. A profundidade global também indica a quantidade máxima de bits utilizados para
definir a posição dos ponteiros. No caso do exemplo, apenas dois. Sendo assim, só é possível utilizar 4
ponteiros, indo do número 00 ao número 11.
No próximo caso, quando queremos adicionar a chave 20 ao bucket 0, percebemos que não há espaço.

Nesse caso, o código deve comparar a profundidade local com a profundidade global, caso sejam iguais,
significa que precisaremos estender o nosso Índice.

Ao estender o índice, perceba que os ponteiros dos endereços de 3 bits, continuam apontando para os
mesmos ponteiros dos endereços de 2 bits. Após isso, o algoritmo deve de imediato criar outro bucket,
separando os ponteiros do bucket em que tentamos colocar outra chave.
Dessa forma, como podemos ver, só precisamos re-alocar os endereços cuja foram afetados. Uma vez
que a função hash seja k mod 2 p , ao invés de 4 ficar na posição 0, teremos 4 mod 6 que vai dar 4. As
outras posições continuaram no mesmo lugar, pois mesmo que 31 mod 23=4, o ponteiro 4 do índice
continua apontando para o mesmo bucket.
Índice Invertido ou Lista Invertida
Trata-se de um índice onde buscamos dados para encontrar chaves. Por isso o chamamos de índice
invertido.

No Índice invertido, a primeira ação a ser tomada é criar um índice com todos os termos utilizados no
banco de dados. Para que isso seja eficaz, é necessário inserir todos os termos do título, por exemplo, no
índice no momento em que o dado é criado.
Antes de inserirmos um termo no índice, devemos averiguar se já existe o termo dentro do índice.
Quando já existe, devemos apenas adicionar a chave do termo correspondente ao registro. Quando
inserimos algum termo no índice, é necessário ordenar o índice outra vez.
O mesmo vale quando formos excluir algum registro. Nesse caso, precisamos observar se os termos do
registro excluído continuam sendo usados por outros registros. Caso isso não ocorra, o mais
recomendável é excluir esses termos.
Devemos também, excluir os termos desnecessários do índice. Os termos desnecessários são aqueles
que se repetem tantas vezes que não ajudam na busca dos arquivos.
COMPRESSÃO DE DADOS
COMPRESSÃO SEM PERDAS

• Remoção (recuperável) das redundâncias


• Aplicada a compressão de dados, textos, programas, ...
• Exemplos:

• Run-length
• Huffman
• Lempel-Ziv

COMPRESSÃO COM PERDAS

• Eliminação de detalhes
• Aplicada a compressão de imagens, áudio, vídeo, ...
• Exemplos:

• JPEG
• MP3
• MP4

TIPOS DE COMPRESSÕES

- REDUÇÃO DA QUANTIDADE DE SÍMBOLOS

• Um símbolo passa a representar um conjunto de outros símbolos


• Ex.:

• Ao invés de indexarmos cada letra, indexamos palavras


• Um pixel pode representar um conjunto de pixels

- REDUÇÃO DO TAMANHO DO SÍMBOLO

• Um símbolo pode ser representado com menos bits do que o usual


• Ex.:

• Podemos usar menos de 1 byte para representar uma letra


• Um pixel pode usar menos de 3 bytes
CODIFICAÇÃO ELIAS GAMMA

É uma codificação desenvolvida por Peter Elias para a redução de tamanho em bits dos números. Apesar
de tudo, ela só é realmente útil quando usada para números muito pequenos, como de 0 a 11. Na tabela
abaixo vemos a representação em binário e também em código elias gamma.

Os primeiros zeros são utilizados para representar a potência de um número dois enquanto os restantes
dos bits representam a quanto esse número dois potenciado será somado.

Para determinar o código de elias, podemos fazer os cálculos:

Encontramos log do do número na base dois e depois subtraímos o número por 2 elevado ao log do
número na base dois (que no caso acima será dois, assim teremos 5−22=1).
ENTROPIA

É uma forma de estimar com quantos bits é possível representar determinado símbolo. Dessa forma,
podemos identificar qual codificação é eficiente ou não.

Para descobrir a entropia de cada símbolo, usamos a porcentagem de sua frequência substituindo-a por
P x e depois multiplicamos pela quantidade de vezes que o símbolo aparece no arquivo. Abaixo vemos
outro exemplo:
CODIFICAÇÃO DE HUFFMAN

É uma codificação extremamente eficiente que tem como intuito diminuir o tamanho em bits dos
símbolos. Para isso Huffman criou uma fila de árvores.

Para isso ele primeiramente definiu a frequência ou a média da frequência que um determinado símbolo
aparece e criou uma pilha que ordenava árvores cuja só existiam raízes com o valor da frequência de
cada letra.

Logo em seguida ele pegou as duas árvores de menor valor e as fundiu em uma única árvore, onde a
raiz era a soma de seus dois números.

Em seguida ele ordenou a raiz dessa nova árvore na pilha e refez o processo até que existisse apenas
uma árvore.

No final da codificação teríamos:


Logo, para codificar utilizamos a tabela, substituindo os bits de cada símbolo pelo código gerado.

Para decodificar utilizamos a árvore, seguindo o caminho dado pelo código até encontrar a letra
correspondente.
COMPRESSÃO BASEADA EM DICIONÁRIOS
LZW

É uma codificação cujo o intuito é diminuir o tamanho em bits dos símbolos. Para isso utilizamos números
para representar um determinado símbolo ou um conjunto de símbolos.

CODIFICAÇÃO

Para a codificação adicionamos os símbolos básicos e depois buscamos a maior sequência que se inicia
com aquele símbolo, sempre adicionando ao dicionário um caractere a mais do que o existente. Observe
também que o número dado ao conjunto é de acordo com aquele conjunto que já existia previamente e
não como o novo conjunto criado. Deve-se fazer o dicionário à medida que codificamos a mensagem.

DECODIFICAÇÃO
Inicializamos o dicionário (com símbolos básicos).

• Decodificar o 1º índice, escrevê-lo na saída e armazená-lo em w.


• Colocar w? no dicionário.
• Repetir até o fim dos índices:
• Decodificar o primeiro símbolo s do próximo índice.
• Trocar o ? da última entrada no dicionário por s
• Decodificar o resto do índice, escrevê-lo na saída e armazená-lo em w.
• Colocar w ? no dicionário.
CRIPTOGRAFIA
Do grego: kryptós (escondido) + graphein (escrita)

Cifragem
É o processo de conversão de um texto claro para um código cifrado.

Decifragem
é o processo contrário, de recuperar o texto original a partir de um texto
cifrado.
TIPOS DE CIFRA
Cifras de substituição
Cada símbolo é substituído por outro símbolo
Cifra de César
Homenagem a Júlio César (100 a.C. - 44 a.C.) que usava um alfabeto assim

Cifra de Vigenère
Cifra polialfabética

Inventada por Giovan Batista Belsa em 1553, apesar de ter sido atribuída durante muito tempo a Blaise
de Vigenère. Semelhante à Cifra de César, mas cada letra é deslocada um diferente número de
posições, de acordo com uma senha.
Para codificarmos uma mensagem, precisamos utilizar a tabela acima e uma palavra qualquer como
chave.

No exemplo a seguir, codificamos a frase “fim de semana” com a chave “caro”:

FIMDESEMANA
(FIM DE SEMANA)

CAROCAROCAR
(CARO)

HIDRGSUACNR

Primeiro, removemos os espaços. Depois, substituímos as letras pela palavra chave. Por último,
buscamos na tabela em x a letra cifrada com caro e em y a letra original. Dessa forma, basta substituí-la
pela letra correspondente.

Cifras de transposição
Os símbolos são trocados de lugar

Cifra das colunas


A informação é escrita em uma matriz, linha a linha. Em seguida, as colunas são extraídas, na ordem dos
valores dos caracteres da palavra chave.
Exemplo:
TIPOS DE CRIPTOGRAFIA

Criptografia simétrica – a mesma chave criptográfica é usada na cifragem e na decifragem.


Criptografia assimétrica – chaves diferentes são usadas na cifragem e na decifragem, mas,
geralmente, o processo é bidirecional.
CRIPTOGRAFIA SIMÉTRICA
Cifras de Fluxo

Há muitos exemplos de comunicação em que os dados são transferidos na medida em que essa
comunicação ocorre. Os dois exemplos mais simples disso são as ligações (por telefone ou
videoconferência) e as aplicações de streaming (como Spotify e Netflix). Nesses casos, os dados vão
sendo consumidos na medida em que vão chegando. E quando a comunicação ou transferência precisa
ser criptografada, não dá para esperar chegar toda a informação, decifrá-la e, somente então, apresentá-
la. Precisamos decifrar os dados na medida em que eles vão chegando. Na outra ponta da comunicação,
isto é, no lado do emissor, os dados precisam ser cifrados na medida em que são enviados. Precisamos,
portanto, de uma técnica de cifragem de fluxo.

A cifragem de fluxo é feita bit a bit (ou símbolo a símbolo).


Ex.: Substituição simples

• Cifra de chave simétrica que combina os bits de um fluxo de bits (bitstream) com os bits de uma chave
(keystream)
• A encriptação geralmente é feita por meio de uma simples operação XOR:

One Time Pad

• Desenvolvido em 1917 por Gilbert Vernam nos laboratórios da Bell, para cifrar fluxos.
• A chave é uma string de bits aleatória do mesmo tamanho da mensagem a ser criptografada.
• É inquebrável (matematicamente comprovado), desde que a chave seja realmente aleatória, mantida
em segredo e usada apenas uma vez.

C - Cifra
K - Key
M - Mensagem
• O OTP requer chaves muito longas, difíceis de serem gerenciadas e mantidas em sigilo.
• Os algoritmos usam, portanto, um gerador de chaves pseudoaleatórias usando uma chave semente de
64, 128, 256 ou mais bits.
Cifra de Blocos

A ideia por traz da cifra de blocos é juntar um tanto de símbolos, sejam eles caracteres, dígitos, bytes ou
mesmo bits, e codificá-los de uma só vez. Geralmente os blocos são bem pequenos, variando de 64 a
512 bits. Assim, uma mensagem, arquivo ou conjunto de dados será composto por uma quantidade muito
grande de blocos. Eles serão codificados um a um de forma independente. Bem, de forma independente
se você quiser, porque existe uma técnica para se aumentar a segurança em que o bloco anterior é
usado na cifragem do bloco seguinte.
O processo da cifragem de blocos pode ser bem simples. Por exemplo, podemos usar alguma cifra de
transposição como a cifra de colunas. Na verdade, podemos fazer uma série de operações que
combinem transposições, substituições, XORs e ainda outras operações.

Se o mesmo bloco se repetir, os blocos cifrados serão iguais, facilitando a percepção de um padrão. Para
evitar isso, existem algumas técnicas como a realimentação, em que o bloco anterior é usado na
cifragem do bloco atual.
CRIPTOGRAFIA ASSIMÉTRICA

Nesse tipo de criptografia, cada pessoa ou entidade tem duas chaves — uma ele tornará pública, isto é,
todo mundo ficará conhecendo a chave, e a outra será privada, isto é, apenas o seu dono deve conhecê-
la. É por isso que a criptografia assimétrica também é chamada de criptografia de chave pública. Mas,
enfim, se você quiser mandar uma mensagem para uma pessoa, precisa usar a chave pública dessa
pessoa. E somente ela vai conseguir decifrar a mensagem usando a sua chave privada.

Veja o exemplo da figura abaixo. Nela, Paulo quer mandar um documento sigiloso para Carolina. Para
isso, Paulo deve cifrar o documento usando a chave pública da Carolina, pois, assim, somente ela vai
conseguir decifrar o documento. E, se ela quiser responder de forma sigilosa para o Paulo, deverá usar a
chave pública dele, para que ele possa decifrar usando a sua própria chave privada.

• Criação de um função unidirecional, facilmente computada, mas difícil de ser invertida.


• Exemplo:
• É fácil multiplicar dois números primos grandes, mas é difícil fatorar o resultado para descobrir os
números originais (sem se ter pelo menos um deles).
• Um algoritmo assimétrico pode ser até 10.000 vezes mais lento do que um algoritmo simétrico.

O principal algoritmo de criptografia assimétrica que temos hoje é o RSA, criado por Ron Rivest,
Adi Shamir e Leonard Adleman em 1977.
ASSINATURA DIGITAL

Uma assinatura digital é uma forma de a gente validar um documento ou uma mensagem
usando a criptografia assimétrica. Só que dessa vez, a gente usa a criptografia numa direção
oposta, isto é, ciframos algum documento com a nossa chave privada e todo mundo vai
conseguir abrir esse documento com a nossa chave pública — ele não será um documento
sigiloso. No entanto, se esse documento realmente puder ser aberto com a nossa chave pública,
então ele só pode ter sido cifrado pela nossa chave privada (à qual apenas a gente tem acesso).
Assim, garantimos a autoria. Ao mesmo tempo, se o documento puder ser aberto sem nenhuma
dificuldade, então ele está íntegro, isto é, ninguém adulterou esse documento.
Na verdade, a assinatura digital não é usada para cifrar todo o documento, pois ele não precisa
ser sigiloso mesmo. O que ciframos com a nossa chave privada é apenas um código hash desse
documento, gerado por alguma função hash especial. Esse código hash cifrado é a nossa
"assinatura digital" que garante a autenticidade (autoria e integridade) do documento.
Assim, quando alguém quiser testar a autenticidade do documento, ele gera o código hash
novamente e compara com o código hash decifrado da assinatura digital (usando a nossa chave
pública). Se forem iguais, então o documento é autêntico.
CERTIFICADOS DIGITAIS
Para a gente saber qual é a chave pública de uma determinada entidade — como uma loja virtual ou um
banco on-line — a gente precisa ter acesso à sua chave pública. Essa chave pública pode ser
encontrada no certificado digital, que nada mais é que um documento que dá autenticidade à chave.
Esse documento diz que chave é essa, quem é o seu proprietário e quem emitiu o certificado. Esse
documento pode ser usado de diversas formas, mas ele também precisa ser instalado no site da
empresa, para que possa ser acessado pelos usuários que navegam por esse site (usando o protocolo
HTTPS).

Um certificado digital é um arquivo que contém a identidade de um indivíduo ou entidade e a sua


chave pública. O certificado digital é emitido por uma entidade certificadora, a partir de uma rede de
confiança ( web of trust ) descentralizada.

Para esclarecer as coisas, observe o exemplo abaixo:

Assim que o cliente solicita uma conexão segura, o servidor/estabelecimento retorna um certificado digital
com a sua chave pública. Logo, o cliente envia uma chave simétrica criptografada com a chave pública
do servidor. Dessa forma, apenas o servidor poderá decodificar a chave enviada, pois ele é o dono da
chave privada da mensagem. Assim, o servidor confirma o recebimento da chave simétrica, agora
através de uma criptografia simétrica, e ambos utilizam dessa criptografia para manter a comunicação.
CASAMENTO DE PADRÕES

O casamento de padrões é uma busca por uma cadeia de símbolos em uma sequência maior.
• Ex.: busca de palavras em textos
Os algoritmos são identificados como:
• Pattern searching
• String searching
• String matching

O casamento de padrões não se limita à busca de texto.


• Exemplos:
busca de DNA, detecção de intrusão, correção ortográfica, detecção de plágio, casamento de voz, etc.

Casamento exato de padrões


• Força Bruta
• KMP
• Aho Corasick
• Boyer Moore
• Casamento aproximado de padrões
• Distância de Levenshtein

FORÇA BRUTA

A forma mais simples de casamento de padrões é a força bruta. Nesse caso, a gente testa a sequência
de símbolos do padrão em TODAS as posições possíveis do documento. Obviamente, não é uma boa
ideia fazermos a busca usando a força bruta, mas é importante conhecê-la para que a gente possa
entender as vantagens das outras formas. No casamento por força bruta, o resultado de um teste do
padrão em uma posição não é aproveitado em um teste posterior. Assim, não há nenhuma otimização
nessa forma de busca.
ALGORITMO KMP

• Criado por Donald Knuth, James Morris e Vaughan


Pratt em 1977
• Baseado em uma variação de um diagrama de estados

Ao invés de voltar para o início da palavra, voltamos para o último prefixo.


ALGORITMO BOYER MOORE
• Criado por Robert S. Boyer e J. Strother Moore em 1977
• Comparação de caracteres do padrão são feitas da direita para a esquerda
• São feitos dois testes a cada passo:
• Deslocamento por caractere ruim
• Deslocamento por sufixo bom
• O deslocamento final será o maior dos dois
É importante destacar que quando o valor do caractere ruim e do sufixo bom são diferentes, devemos
considerar o maior deles.
Caso o algoritmo queira continuar procurando padrões, vale lembrar que o deslocamento sera sempre de
uma posição

Aho Corasick

É uma variação do algoritmo KMP que permite testar vários padrões em uma única busca.

No exemplo acima procuramos 4 termos ao mesmo tempo: he, she, his e hers. Para o funcionamento do
algoritmo, organizamos em uma espécie de esqueleto. Nesse esqueleto perceba que se em qualquer
momento o algoritmo chegar nos estados 2, 5, 7 e 9, significa que uma das palavras, respectivamente,
foram encontradas: he, she, his e hers.
Abaixo podemos ver as transições que ocorrem quando há falha ou quando um termo é encontrado com
sucesso. O algoritmo sempre funciona de forma que quando regredimos a alguma outra posição,
tentamos avançar para a próxima logo em seguida ou regredir ainda mais.

Casamento Aproximado de Padrões


É algo que busca encontrar padrões aproximados e não necessariamente exatos. Observe abaixo
algumas de suas aplicações.

Corretores ortográficos, Comparação de DNA, Filtragem de SPAM, OCR (optical character recognition).

Para compreendermos o casamento aproximado de padrões, precisaremos ter uma ideia básica sobre o
que é programação dinâmica. A programação dinâmica é um método de solução de problemas por meio
de sua decomposição em problemas menores.

Na programação dinâmica, salvamos o resultado de cada problema decomposto para que possamos
usar em problemas futuros idênticos. Um exemplo que podemos citar é Fibonacci, onde o resultado é a
soma do fibonacci dos dois antecessores do número requerido.

Nesse exemplo podemos perceber que para o resultado de F(5) são feitos vários cálculos iguais.
Voltando para o casamento aproximado, usamos a seguinte equação:

A minha distância de edição de casaco para cascão é o valor da posição 6x6 do vetor, ou seja, 2.
Obss: Nos comparamos se as letras são iguais, e não os números. Ex: S = S ent C = 0;

Você também pode gostar