listas generalizadas

Propaganda
9 - GERENCIAMENTO DE MEMÓRIA
9.1 - LISTAS GENERALIZADAS
Conceito
Operações sobre listas
Exemplos
Representação Encadeada de Listas
Método dos ponteiros
Método de cópia
Exemplo
“List Headers”
Listas Generalizadas em C
Exemplo de implementação da operação tail
Exemplo de implementação da operação head
Exemplo de implementação da operação addon
Exemplo de implementação da operação settail
2
2
2
3
3
4
4
5
5
6
6
6
7
7
8
9.2 - GERENCIAMENTO AUTOMÁTICO DE LISTAS
Método do Contador de Referências
Coleta de Resíduos
Algoritmos para Marcação
Primeira versão
Segunda versão
Terceira versão
Tipo de registros para a marcação de blocos de memória
Estrutura de um nó no processo de marcação
Algoritmos das funções
Algoritmos para Compactação
Estrutura de um nó no processo de compactação
Algoritmos da função
9
9
9
11
11
11
11
12
12
12
17
17
18
9.3 - GERENCIAMENTO DINÂMICO DE MEMÓRIA
Conceito
Primeiro Ajuste
Melhor Ajuste
Pior Ajuste
Exemplos
21
21
21
21
21
21
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 1
9 - GERENCIAMENTO DE MEMÓRIA
9.1 - LISTAS GENERALIZADAS
 Conceito
Uma lista generalizada é um tipo abstrato de dados. A lista é uma seqüência de objetos denominados
elementos. Associado a cada elemento da lista existe um valor. Listas encadeadas referem-se à implementação
de listas por encadeamento.
Os elementos de uma lista não necessitam ser todos do mesmo tipo de dados.
A implementação mais comum de listas generalizadas é feita por encadeamento. Existem dois tipos de
ponteiros: externos e internos. Os primeiros são ponteiros não contidos nos nós da lista enquanto os ponteiros
internos o são.
Normalmente referenciam-se uma lista por meio de um ponteiro para a lista(ponteiro externo).
Elementos de listas que sejam também listas são representados por elementos cujos valores contenham
ponteiros para estas últimas listas.
Listas podem ser denotadas por uma enumeração de seus elementos, separados por vírgulas e
delimitada (a enumeração) por parênteses. Listas vazias podem ser denotadas por um símbolo especial ( , por
exemplo) ou por “( )”.
EXEMPLOS:
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 2
 Operações sobre listas
Operação
head (L)
tail (L)
first (L)
info (x)
next (x)
nodetype (x)
push (L, v)
addon (L, v)
setinfo (x, v)
sethead (L, v)
setnext (x, y)
settail (L1, L2)
Descrição
retorna o valor do primeiro elemento de L
retorna a lista obtida removendo-se o primeiro elemento de L. Uma
sublista de L é uma lista obtida pela aplicação de zero ou mais
operações tail a L.
retorna o primeiro elemento de L. L e first (L) indicam o mesmo nó
retorna o valor do elemento x. head (list) = info (first (list))
retorna o elemento sucessor de x na lista.
retorna o tipo de dados do valor do elemento x.
adiciona um elemento com valor v na frente da lista L, modificando
o valor de L (é um procedimento e não uma função)
função que retorna uma nova lista que tem v como head e L como
tail.
/* push (L, v) eqüivale a L = addon (L, v) */
atualiza o valor do elemento x de uma lista para v. set info (x, v) é
implementada por info (x) = v
atualiza o valor do primeiro elemento de L para v. sethead (L, v) =
setinfo (first (L), v) = = setinfo (L, v) ou a equivalente a
L = addon (tail (L), v)
altera a lista que contém o elemento x da forma
next (x) = y
concatena a lista L2 ao primeiro elemento da lista L1.
Operação inversa à operação tail.
settail (L1, L2) = setnext (first (L1), first (L2)) = setnext (L1, L2)
settail (L1, L2) eqüivale a L1 = addon (L2, head (L1))
Um nó n é acessível de uma lista L se existe uma seqüência de operações head e tail que, aplicada a L,
fornece uma lista na qual n seja seu primeiro elemento.
 Exemplos
L1 = (8, ‘x’, 300, 5, ‘y’)
L2 = (3, (7, 14, (13, 26, 39), ( ), 21), 30, (4, 40, 400))
L3 = (100, 200, 300)
L4 = (215, 230, 245)
head (L1) = 8
tail (L1) = (‘x’, 300, 5, ‘y’)
head (tail (L1)) = ‘x’
tail (tail (L1)) = (300, 5, ‘y’)
tail (L2) = (( 7, 14, (13, 26, 39, ( ), 21), 30, (4, 40, 400))
head (tail ( (L2)) = (7, 14, (13, 26, 39, ( ), 21)
head (head (tail (L2))) = 7
Operação
L5 = NULL
push (L5, 12)
push (L5, 14)
push (L5, 16)
L6 = addon (L5, 18)
sethead (L3, 50)
sethead (L3, L4)
settail (L1, L3)
Resultado
L5 = ( )
L5 = (12)
L5 = (14,12)
L5 = (16,14,12)
L6 = 18,16,14,12
L3 = (50,200,300)
L3 = ((215,230,245),200,300)
L1 = (8, ((215,230,245),200,300))
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 3
settail (L2, (3,6,9))
L2 = (3,3,6,9)
 Representação Encadeada de Listas
O conceito abstrato de listas é geralmente implementado por uma lista encadeada de nós.
Cada elemento da lista abstrata corresponde a um nó da lista encadeada. Cada nó da lista possui
campos info e next.
Um ponteiro para um nó de uma lista também representa a sub lista representada pelos nós entre o nó
apontado e o fim da lista original.
Operações básicas sobre listas tais como addon e tail podem ser implementados por dois métodos:
método dos ponteiros e método de cópia.
No método dos ponteiros sempre que uma lista Lx receber como sub lista uma lista Ly o que ocorre é
que o elemento qntecessor da sub lista tem seu ponteiro next apontado para Ly. Mo método de cópia o que se
faz é inserir após o antecessor da sub lista na lista Lx uma cópia da cada nó de Ly. Estes nós são duplicados
ficando os originais em Ly e uma cópia na sub lista de Lx.
EXEMPLO:
L7 = (8, 13, 5)
L8 = addon (L7,19)
 Método dos ponteiros
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 4
 Método de cópia
Por razões de eficiência a maioria dos sistemas de processamento de listas usa o método dos ponteiros.
Neste método o número de operações envolvidas na inclusão ou exclusão de um elemento a uma lista
independe do tamanho da lista.
Todavia essa eficiência do método obriga o usuário a tomar cuidado com o efeito que a mudança em
uma lista possa acarretar em outras listas.
Sistemas de processamento de listas que usem o método dos ponteiros dispõem também de operações
explícitas de cópia de listas.
Uma opção que atinge o projeto refere-se ao dilema:
Listas e elementos que aparecem mais de uma vez em uma lista generalizada
devem ser replicados ou devem ser apontados por ponteiros confluentes?
A operação crlist serve para congregar diversas operações addon.
 Exemplo
crlist (a1,a2,..., an) eqüivale a
L9 = NULL
L9 = addon (L9, a1)
L9 = addon (L9, a2)



L9 = addon (L9,an)
Quando as operações de listas são implementadas pelo método dos ponteiros é possível criar listas
recursivas.
L10 = crlist (30, 40, 50, 60)
L11 = crlist (100, L10, 200, L10, 60)
ou
L11 = (100, crlist (30, 40, 50, 60), 200, crlist (30, 40, 50, 60), 60)
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 5
 “List Headers”
“ List headers” ou cabeças de listas são nós que antecedem listas. Nestes nós especiais o atributo de
informação armazena informações globais sobre a lista (número de nós ou ponteiro para o final da lista, por
exemplo).
“Headers” podem fornecer uma terceira alternativa, abaixo descrita, de implementação de listas
generalizadas, além dos métodos dos ponteiros e método de cópia.
Os “list headers” sempre são posicionados no início de qualquer grupo de nós que deve ser
considerado uma lista. Ponteiros externos sempre apontam para nós tipo “header”.
Quaisquer parâmetros que representem listas devem ser implementados como ponteiros para o
“header” das respectivas listas.
Funções que retornem listas devem ser implementados retornando ponteiros para nós tipo “header”.
Geralmente os átomos são tratados pelo método de cópia e as sub listas são tratadas pelo método dos
ponteiros. Um problema complexo é descobrir quais os nós que podem ser liberados depois de operações que
os tornem sem utilização.
 Listas Generalizadas em C
Os nós de listas generalizadas podem conter dados de diversos tipos, além de ponteiros para outras
listas. Uma implementação que permite manipular esta diversidade é feita pela declaração de nós de listas
como sendo tipo Union contendo registros e ponteiros pra sub listas e, caso necessario, variantes tipo
caractere, inteiro, real, etc., como por exemplo:
define REG
define LST
1
2
struct nodetype {
int utype ;
/* utype pode valer REG ou LST */
union {
struct reg area_de_dados; /* o tipo de registro reg depende da natureza do problema */
struct nodetype *lstinfo;
} info;
struct nodetype *next;
};
/* a estrutura ou registro nodetype possui três atributos:
 tipo
 ou área de dados ou ponteiro para sub lista
 ponteiro para sucessordo tipo nodetype
*/
typedef struct nodetype *NODEPTR;
 Exemplo de implementação da operação tail
NODEPTR tail (NODEPTR list)
{
if (list == NULL){
prinft ( “Operação ilegal de tail em lista vazia \n”);
exit (1);
}
else
return (list->next); /* do registro apontado por list seleciona-se o atributo next
} /* end tail */
*/
Considere-se outra definição de tipo de dado ou estrutura.
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 6
struct {
int utype;
union {
struct reg area_de_dados; /* o tipo de registro reg depende da natureza do problema */
struct nodetype *lstinfo;
} info;
} infotype;
/* a estrutura ou registro infotype possui dois atributos:
 tipo
 ou área de dados ou ponteiro para sub lista */
typedef struct infotype * INFOPTR;
struct nodetype {
INFOPTR item;
struct nodetype * next;
};
/* a estrutura ou registro nodetype possui tres atributos:
 ponteiro para registro do tipo infotype
 ponteiro para sucessor do tipo nodetype
*/
typedef struct nodetype *NODEPTR;
 Exemplo de implementação da operação head
void head ( NODEPTR list, INFOPTR p_item)
{
if ( list == NULL) {
printf ( “Operação ilegal. Header de lista vazia \n”);
exit(1);
}
p_item = (INFOPTR) malloc (sizeof (struct infotype));
p_item->utype = list->item->utype; /* do registro tipo infotype apontado por p_item
seleciona-se o atributo utype;
do registro tipo nodetype apontado por list seleciona-se
o atributo item;
do registro tipo infotype apontado por list->item seleciona-se
o atributo utype
*/
p_item->info = list->item->info;
/* do registro tipo infotype apontado por p_item
seleciona-se o atributoinfo;
do registro tipo nodetype apontado por list seleciona-se
o atributo item;
do registro tipo infotype apontado por list->item seleciona-se
o atributo info
*/
return;
}
/* end head */
 Exemplo de implementação da operação addon
NODEPTR addon ( NODEPTR list, INFOPTR p_item)
}
NODEPTR novo;
novo = ( INFOPTR) malloc ( sizeof (struct nodetype));
novo->item->utype = p item->utype;
/* do registro tipo infotype apontado por p_item
seleciona-se o atributo utype;
do registro tipo nodetype apontado por novo seleciona-se
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 7
novo->item->info = p item->info;
novo->next = list;
return (novo);
}
o atributo item;
do registro tipo infotype apontado por novo->item seleciona-se
o atributo utype
*/
/* do registro tipo infotype apontado por p_item
seleciona-se o atributo info;
do registro tipo nodetype apontado por novo seleciona-se
o atributo item;
do registro tipo infotype apontado por novo->item seleciona-se
o atributo info
*/
/* do registro tipo nodetype apontado por novo
seleciona-se o atributo next
*/
/* end addon */
 Exemplo de implementação da operação settail
void settail ( NODEPTR plist, NODEPTR p_tail)
{
NODEPTR p;
p = plist->next;
plist é um ponteiro para lista; do registro apontado
por plist seleciona-se o atributo next */
plist->next = p_tail;
freelist (p);
}
/* end settail */
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 8
9.2 - GERENCIAMENTO AUTOMÁTICO DE LISTAS
O gerenciamento automático de listas normalmente é feito pelos métodos do contador de referências e
da coleta de resíduos.
 Método do Contador de Referências
Neste método cada nó possui um campo contador que armazena o número de ponteiros (internos e
externos) que apontam para este nó. Toda vez que o ponteiro para um nó é atualizado os contadores de
referências dos nós para os quais o ponteiro apontava e passou a apontar são atualizados.
Pelo método do contador de referências a operação L = tail(L) é implementada como
p = L;
L = next(L);
next(p) = NULL;
reduce(p);
Toda vez que o contador de referências de um nó se torna nulo este nó pode ser devolvido à lista de
nós livres. A atualização do contador de referências, no caso de redução pode ser feito como:
reduce (NODEPTR p)
{
if ( p != NULL) {
p->count --;
if ( p->count == 0) {
r = pnext;
reduce (r); /* Se p tem sucessor reduzir o contador de referências de p implica em
reduzir o contador de referências de todos os seus sucessores */
if ( nodetype (p) == LST) /* Se p é um cabeça de sub lista reduzir o contador de
referências de p implica em reduzir o contador de referências de
todos os elementos da sub lista */
reduce (head (p));
freenode (s);
}
}
Este método exige muito esforço do sistema a cada comando de manipulação de listas. Sempre que o
valor de um ponteiro é atualizado todos os nós préviamente acessíveis através daquele ponteiro podem
potencialmente ser liberados.
Frequentemente o esforço computacional de identificação dos nós a liberar não compensa o espaço
recuperado. Muitas vezes vale mais a pena esperar o final do programa e recuperar toda a memória utilizada.
Uma alternativa a este método consiste na utilização de contadores de referência apenas nos nós
cabeças de lista (“headers”). Mesmo assim é difícil tratar listas circulares ou recursivas, por exemplo.
 Coleta de Resíduos
Coleta de resíduos é um processo de coleta de todos os nós fora de uso e seu retorno à lista de espaço
disponível. Este processo é executado em duas fases.
- Fase de marcação
A fase de marcação serve para marcar todos os nós acessíveis por meio de ponteiros externos, que são
os nós que estão em uso. A marcação é feita pela incorporação de um atributo marca que recebe o valor
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 9
TRUE quando o nó é acessível por ponteiros externos. No início e no final do processo de coleta de resíduos
todos os atributos marca devem conter o valor FALSE.
- Fase de compactação
Na fase de compactação a memória é percorrida sistematicamente liberando todos os nós não marcados
e realocando os nós ainda em uso. A marcação dos nós exige o uso de atributos de marcação nos nós ou em
tabelas específicas.
A coleta de resíduos é invocada quando existe pouco espaço disponível e, em geral, exige a interrupção
de outros processos.
Sistemas de tempo real devem utilizar algoritmos mais elaborados que o de coleta de resíduos.
É possível que a coleta de resíduos ocorra quando quase todos os nós estejam em uso. Nessa situação
recupera-se muito pouco espaço. Em conseqüência logo a seguir pode haver necessidade de invocar
novamente o processo de coleta de resíduos que, mais uma vez, de pouco adiantará, caindo-se em um círculo
vicioso denominado “thrashing”. Para evitar este problema, sempre que um coletor de resíduos não
conseguir recuperar uma porcentagem especificada do espaço de memória, o processo que requereu memória
adicional é descontinuado.
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 10
 Algoritmos para Marcação
Os algoritmos para marcação de nós em uso serão apresentados em 3 versões em ordem crescente de
complexidade cujas etapas principais são as seguintes:
 Primeira versão
 Marcar inicialmente todos os nós imediatamente acessíveis.
 Efetuar diversos passos sequenciais sobre a memória.
 Em cada passo quando um nó marcado for encontrado deve-se marcar todos os nós apontados por
ponteiros saídos desse nó.




 Segunda versão
Suponha-se queum nó n1 foi marcado e contém ponteiro para um nó não marcado n2.
Marcar n2
Prosseguir com o nó sucessor de n1.
Se o endereço de n2 for menor que o endereço de n1, prosseguir com o nó sucessor de n2 em vez do
sucessor de n1.
 Terceira versão
Nesta versão, em vez de percorrer sequencialmente a memória, percorre-se as listas acessíveis. Usa-se
uma pilha auxiliar.
Ao percorrer uma lista acompanhando os ponteiros next examina-se os atributos tipo. Quando o tipo
for LIST então o valor do atributo lstinfo(ponteiro para uma sub lista) daquele nó é empilhado. Ao terminar
uma lista ou ao encontrar um nó já marcado efetua-se um desempilhamento e atravessa-se a lista apontada
pelo topo da pilha.
Uma solução alternativa para evitar o eventual transbordamento da pilha consiste em usar uma pilha de
tamanho máximo. Caso a pilha esteja na iminência de transbordar pode-se reverter ao método sequencial
anterior.
Pode-se também contornar a solução usando as próprias listas de nós como pilha. Para fazer isto
temporariamente desarrumam-se os ponteiros das listas e depois da marcação dos nós rearrumam-se as listas.
O problema fundamental do percurso de uma rede de listas encadeadas em uma só direção consiste em,
ao chegar ao final de uma lista, determinar qual o ponto de onde se deve reiniciar o percurso.
Uma das maneiras de resolver este problema consiste no empilhamento dos pontos aonde se iniciam as
listas fora do percurso original.
O modelo empregado para os nós tem 4 atributos:
Marca (TRUE ou FALSE)
Tipo (REG ou LST)
Next (ponteiro para sucessor na lista)
Info
reginfo
lstinfo
Considera-se p o nó a ser processado e top um ponteiro para o topo da pilha de nós percorridos, isto é,
top aponta o antecessor de p.
O ponterio next de top, ao invés de apontar p, como vinha fazendo, temporariamente aponta o
antecessor de top na lista. Assim pode-se retornar na lista.
Quando se está iniciando uma sub-lista o atributo lstinfo contém um ponteiro para o primeiro elemento
da sub lista e passa a apontar o antecessor do nó (pai da sub-lista) na lista original. Nessa situação troca-se o
seu tipo para STK (3).
Ao se desempilhar um nó se o tipo não for STK deve-se restaurar o atributo next. Se o tipo for STK
deve-se restaurar o atributo lstinfo e o tipo deve voltar a ser LST.
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 11
 Tipo de registros para a marcação de blocos de memória
reg
atributo
chave
outros
tipo
chave
demais atributos da entidade
 Estrutura de um nó no processo de marcação
Atributo
marca
É union ?
Não
Componentes
-
Tipo
inteiro
tipo
Não
-
inteiro
info
Sim
next
Não
reginfo
lstinfo
-
reg
inteiro
inteiro
Descrição
Indicador se o nó foi marcado como estando
em uso. Pode receber valores TRUE e FALSE
Indicador se o nó é um nó de informação ou é
um cabeça de sub lista.Pode receber os valores
REG (1), LST (2) e STK (3).
Informação armazenada no nó
Ponteiro para a sub lista encabeçada pelo nó
Ponteiro para sucessor do nó na lista encadeada
de nós
 Algoritmos das funções
Procedimento sem tipo marca1 (sem parâmetros)
Símbolo
Tipo
Escopo
Descrição
node
“array” de
global
nós ou blocos de memória
nodetype
acc
“array” de inteiros
ponteiros externos para os nós imediatamente
acessíveis
NUMACC
constante
global
número de nós acessíveis diretamente
NUMNODES
constante
global
número de nós disponíveis na memória
REG
constante
global
indicador de registro (1)
LST
constante
global
indicador de lista (2)
TRUE
constante
global
1
FALSE
constante
global
0
i
j
inteiro
inteiro
local
local
ponteiro para o nó corrente
ponteiro para o próximo nó a ser examinado
Finalidade
Efetuar a marcação dos nós em uso na memória. Nós em uso tem seu atributo marca ajustado
para TRUE. Este algoritmo varre seqüêncialmente a memória.
Início
Para i variando de 0 até NUMACC
node[acc[i]].marca  TRUE
Fim do Para
/* até NUMACC
i1
Enquanto i < NUMNODES
/* do “array” acc seleciona-se o elemento i;
do “array” node seleciona-se o elemento acc[i];
do registro selecionado por acc[i] seleciona-se o atributo next */
*/
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 12
/ * i é o nó corrente * /
j  i + 1 / * próximo nó a ser examinado * /
Se (node[i].marca)
/* do “array” node seleciona-se o elemento i ;
do registro selecionado por i seleciona-se o atributo marca */
então / * marcar os nós apontados por i * /
Se (node[i].tipo= LST e node[node[i].info.lstinfo].marca  TRUE)
/* do “array” node seleciona-se o elemento i ; do registro selecionado por i
seleciona-se o atributo tipo
do registro node[i] seleciona-se o atributo info.lstinfo;
do registro node[ node[i].info.lstinfo] seleciona-se o attibuto marca */
então / * A porção de informação de i aponta para um nó não marcado * /
node[node[i].info.lstinfo].marca  TRUE
Se (node[i].info.lstinfo < j)
/* do registro node[i] seleciona-se o atributo info.lstinfo */
então
/ * A busca prossegue na sucessão do menor endereço presente * /
j  node[i].info.lstinfo
Fim do Se
/* node[i].info.lstinfo < j */
Fim do Se
/* node[i]. tipo= LST e node[node[i]. lstinfo].marca  TRUE */
Se ( node[node[i].next].marca  TRUE)
/* do “array” node seleciona-se o registro node[i].next e, deste,
seleciona-se o atributo marca */
então
/ * O nó sucessor de node [i] ainda não está marcado * /
node[node[i].next].marca  TRUE
Se ( node[i].next< j)
então
j  node[i].next
Fim do Se /* node[i].next < j */
Fim do Se
/* node[node[i].next] marca)  TRUE */
Fim do Se
/* node[i].marca */
ij
Fim do Enquanto
/* i < NUMNODES */
Fim do procedimento
Procedimento sem tipo marca2 (sem parâmetros)
Símbolo
Tipo
Escopo
Descrição
node
“array” de
global
nós ou blocos de memória
nodetype
acc
“array” de inteiros
ponteiros externos para os nós imediatamente
acessíveis
NUMACC
constante
global
número de nós acessíveis diretamente
NUMNODES
constante
global
número de nós disponíveis na memória
REG
constante
global
indicador de registro (1)
LST
constante
global
indicador de lista (2)
TRUE
constante
global
1
FALSE
constante
global
0
s
p
pilha
inteiro
Procedimentos chamados
local
local
pilha de inteiros
ponteiro para o nó
empty
push
pop
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 13
Finalidade
Efetuar a marcação dos nós em uso na memória. Nós em uso tem seu atributo marca ajustado
para TRUE. Este algoritmo percorre as lista em vez de varrer seqüêncialmente a memória.
Quando é encontrado um nó de sub lista este nó é empilhado até a a conclusão do percurso na
lista corrente. Sucessivamente são desempilhados os nós cabeças de sub listas para que estas
possam ser percorridas e seus nós examinados para marcação ou não.
Início
Para i variando de 0 até NUMACC
/ * Marcar o próximo nó acessível e empilhá-lo * /
node[acc[i]].marca  TRUE
/* do registro selecionado por acc[i] seleciona-se marca */
push (s, acc[i])
Enquanto ( empty (s)  TRUE)
pop (s, p)
Enquanto (p  0)
Se ( node[p].tipo = LST e node[node [p].info.lstinfo].marca  TRUE)
/* do “array” node seleciona-se o elemento p ; do registro selecionado por p
seleciona-se o atributo tipo
do registro node[p] seleciona-se o atributo info.lstinfo;
do registro node[ node[p].info.lstinfo] seleciona-se o attibuto marca */
então
node[node [p].info.lstinfo].marca  TRUE
push (s, node[p].lstinfo)
Fim do Se
Se (node[node [p].next].marca = TRUE)
/* do “array” node seleciona-se o registro node[p].next e, deste,
seleciona-se o atributo marca */
então
/* o sucessor de p já está marcado */
p0
senão
/* marcar o sucessor de p */
p  node [p].next
node [p].marca  TRUE
Fim do Se
Fim do Enquanto /* end p  0 */
Fim do Enquanto /* end empty (stack)  TRUE */
Fim do Para
Fim do procedimento
Procedimento sem tipo marca3 (sem parâmetros)
Símbolo
Tipo
Escopo
Descrição
node
“array” de
global
nós ou blocos de memória
nodetype
acc
“array” de inteiros
ponteiros externos para os nós imediatamente
acessíveis
NUMACC
constante
global
número de nós acessíveis diretamente
NUMNODES
constante
global
número de nós disponíveis na memória
REG
constante
global
indicador de registro (1)
LST
constante
global
indicador de lista (2)
STK
constante
global
indicador de lista empilhada (3)
TRUE
constante
global
1
FALSE
constante
global
0
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 14
top
p
q
again
inteiro
inteiro
inteiro
rótulo
local
local
local
local
ponteiro para topo de pilha
ponteiro para o nó corrente
ponteiro para o nó sucessor do nó corrente
ponto de retorno para analise de listas
Finalidade
Efetuar a marcação dos nós em uso na memória. Nós em uso tem seu atributo marca ajustado
para TRUE. Este algoritmo utiliza como pilha de armazenamento dos cabeças das sub listas a
própria lista. Isto é feito utilizando, temporariamente, os ponteiros existentes nos nós (lstinfo e
next) para implementar a pilha. Após percorrer cada lista ou sub lista os atributos referenciados
são atualizados para seus valores correntes.
Início
Para i variando de 0 até NUMACC - 1
/ * Percorrer a lista de cada nó diretamente acessível * /
p  acc [i]
/ * inicializar pilha vazia * /
top  0
again:
/ * Atravessar a lista pelos ponteiros next marcando cada nó e empilhando-o até encontrar um nó
já marcado ou o final da lista. Supõe-se node [0].marca = TRUE
*/
Enquanto ((node[p].marca  TRUE) e (node[p].next  0))
node[p].marca  TRUE
/ * Empilhar node [p] armazenando em q um ponteiro para o próximo nó * /
q  node[p].next
node[p].next  top
top  p
/ * Avançar para o próximo nó * /
pq
Fim do Enquanto
/ * Neste ponto retornar através da lista desempilhando sucessivamente até alcançar um nó cujo atributo
lstinfo aponte para um nó não marcado ou terminar a lista
*/
Enquanto ( top  0)
/ * Restaurar lstinfo ou next para p e desempilhar * /
p  top
/ * Restaurar o atributo correto de node [p] * /
Se (node [p].tipo = STK)
então /* no primeiro desempilhamento de seu atributo tipo foi alterado para STK e
o nó foi reempilhado. Agora sua sub lista já foi percorrida */
node[p].tipo  LST / * lstinfo foi usado como ponteiro da pilha. Restaurar o atributo
tipo
*/
top  node[top].info.lstinfo / * Desempilhar */
node [p].info.lstinfo  q
/ * Restaurar o atributo lstinfo * /
qp
senão
/ * next foi usado como ponteiro para a pilha. Desempilhar * /
top  node[top].next / * Restaurar o atributo next * /
node[p].next  q
qp
/ * Verificar se é necessário percorrer sub lista
*/
Se (node[p].tipo= LST)
então / * lstinfo será usado como ponteiro da pilha * /
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 15
node[p].tipo STK
q  node [p].info.lstinfo
node [p].info.lstinfo  top
/ * Empilhar node [p] * /
top  p
pq
/ * Avançar para o próximo nó * /
goto again /* como para o mesmo valor de acc[i] existe outra lista a ser
percorrida volta-se ao ponto de início da travessia de lista, sem
desmontar a pilha */
Fim do Se
/* node[p].tipo= LST */
Fim do Se
/ * node[p].tipo= STK * /
Fim do Enquanto / * top  0 * /
Fim do Para
Fim do procedimento
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 16
 Algoritmos para Compactação
Na compactação de memória os nós marcados são compactados para as posições iniciais da memória.
Além da modificação de endereços dos nós há necessidade de atualização dos ponteiros internos e externos
para ajustamento aos novos endereços. Um problema que surge na compactação caracteriza-se pelo fato de, ao
se atualizar os ponteiros de um nó relocado, não há como se conhecer os novos endereços dos nós por ele
apontados se estes nós ainda não tiverem sido relocados. Resolve-se este problema efetuando-se duas
passagens sobre a memória. Na primeira delas criam-se listas de ajustamento, sendo uma para cada nó
apontado por qualquer outro, contendo todos os demais nós que apontam o nó proprietário da lista. As listas
de ajustamento são encadeadas pelos mesmos ponteiros que apontavam o nó proprietário e que serão
temporariamente modificados para que a lista possa ser criada. Na segunda passagem sobre a memória alocase novo endereço para cada nó, todos os ponteiros que mantém a lista de ajustamento desse nó recebem o
novo endereço e só então move-se o nó para o novo endereço.
 Estrutura de um nó no processo de compactação
Atributo
marca
É union ?
Não
Componentes
-
Tipo
inteiro
tipo
Não
-
inteiro
info
Sim
next
Não
reginfo
lstinfo
-
reg
inteiro
inteiro
header
Não
-
inteiro
headptr
Não
-
inteiro
infoptr
Não
-
inteiro
nextptr
Não
-
inteiro
Descrição
Indicador se o nó foi marcado como estando
em uso. Pode receber valores TRUE e FALSE
Indicador se o nó é um nó de informação ou é
um cabeça de sub lista.Pode receber os valores
REG (1), LST (2) e STK (3).
Informação armazenada no nó
Ponteiro para a sub lista encabeçada pelo nó
Ponteiro para sucessor do nó na lista encadeada
de nós
primeiro nó da lista de ajustamento ou lista de
nós que apontam o nó corrente
indicador de qual dos ponteiros do nó apontado
pelo header é empregado para fazer a conexão
à lista de ajustamento
indicador de qual dos ponteiros do nó apontado
por listinfo é empregado para fazer a conexão à
lista de ajustamento
indicador de qual dos ponteiros do nó apontado
por next é empregado para fazer a conexão à
lista de ajustamento
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 17
 Algoritmos da função
Procedimento sem tipo compacta (sem parâmetros)
Símbolo
Tipo
Escopo
Descrição
node
“array” de
global
nós ou blocos de memória
nodetype
i
newloc
nd
p
inteiro
inteiro
inteiro
inteiro
local
local
local
local
q
inteiro
local
source
caractere
local
ponteiro para nó
ponteiro para novo endereço de um nó
nó corrente em varredura seqüêncial
ponteiro para o nó corrente de uma lista de
ajustamento
ponteiro para o nó sucessor do nó corrente em
uma lista de ajustamento
indicador de qual a fonte da ligação do nó à
lista de ajustamento, sendo:
 ‘I’ para lstinfo
 ‘L’ para next
Finalidade
Efetuar a compactação da memória. Esta compactação deve ser precedida de uma marcação na
qual os nós em uso tem seu atributo marca ajustado para TRUE. Na compactação os nós
recebem novos endereços junto ao início da memória. Uma complicação adicional do processo
advém da necessidade de atualização dos ponteiros que estejam presentes nos nós deslocados. É
preciso percorrer duas vezes as listas de nós e utilizar listas auxiliares que contenham todos os
nós que apontam cada nó da lista. Esytas listas auxiliares, cujo número máximo é igual ao
número de nós presentes, são chamadas de listas de ajustamento.
Início
/ * Inicialização de atributos * /
Para i variando de 1 até MAXNODES - 1
node [i].header  0
node [i].headptr  ‘N’
node [i].infoptr  ‘N’
node[i].nextptr  ‘N’
Fim do Para
newloc  0
Para nd variando de 1 até MAXNODES - 1
/ * Só se considera nós marcados * /
Se (node[nd].marca = TRUE)
então / * Nova posição para o nó * /
newloc newloc + 1
/* para todos os nós que já foram encontrados apontando para nd neste passo,
ajustar o ponteiro apropriado para que passe a apontar a nova localização de nd */
p  node [nd].header
/ * cabeça de lista dos nós que apontam para nd * /
source  node[nd].headptr
Enquanto (p  0)
/ * Percorrer a lista de nós, até agora encontrados, que apontam para nd * /
Se ( source = ‘I’)
então / * O nó está encadeado à lista de ajustamento pelo ponteiro lstinfo * /
q  node [p].info.lstinfo
/ * Sucessor de p na lista de ajustamento * /
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 18
source  node[p].infoptr
node [p].info.lstinfo  newloc / * atualizar o ponteiro que anteriormente apontava nd* /
node[p].infoptr  ‘N’
/ * O nó p não mais faz parte da lista ajustamento * /
p q
senão / * O nó está encadeado à lista de ajustamento pelo ponteiro next * /
q  node[p].next
source  node[p].nextptr
node[p].next  newloc
node[p].nextptr  ‘N’
p q
Fim do Se / * source = ‘I’ */
Fim do Enquanto
/*p0*/
/ * Toda a lista de ajustamento já foi percorrida e ela torna-se desnecessária * /
node [nd].headptr ‘N’
node [nd].header  0
/* Criação da lista de ajustamento
*/
/ * Caso algum ponteiro de nd apontar para outro nó, como p, por exemplo. colocar nd
na lista de ajustamento encabeçada por node [p].header
*/
Se (node [nd].tipo = LST e node [nd].info.lstinfo 0)
então
p  node [nd].info.lstinfo / * p é o nó apontado por node [nd].info.lstinfo * /
node [nd].info.lstinfo  node [p].header
node [nd].infoptr  node [p].headptr
node [p].header  nd
/* aquí nasce a lista de ajustamento */
node [p].headptr  ‘I’
Fim do Se
/ * node [nd].tipo = LST * /
senão
p  node [nd].next
/ * p é o nó apontado por node [nd].next * /
node [nd].next  node [p].header
node [nd].nextptr  node [p].headptr
Se ( p  0)
então
node [p].header  nd /* aquí nasce a lista de ajustamento */
node [p].headptr  ‘L’
Fim do Se / * p  0 * /
Fim do Se / * node [nd].marca = TRUE * /
Fim do Para
/* nd variando de 1 até MAXNODES - 1 */
/ * A passagem agora concluída ajustou todos os ponteiros para nós com endereços superiores aos dos nós
varridos e criou listas de ajustamento para os nós com ponteiros para nós com endereços inferiores aos
varridos. Uma segunda passagem vai tratar as listas de ajustamento que restarem e mover os nós para suas
novas posições.
*/
newloc  0
Para nd variando de 1 até MAXNODES - 1
Se (node[nd].marca)
então
newloc  newloc + 1
p  node [nd].header
source  node [nd].headptr
Enquanto ( p  0)
/ * Percorrer a lista * /
Se ( source = ‘I’)
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 19
então
q  node[p].info.lstinfo
source  node[p].infoptr
node[p].info.lstinfo  newloc
node[p].infoptr  ‘N’
pq
senão
q  node [p].next
source  node [p].nextptr
node [p].next  newloc
node [p].nextptr  ‘N’
pq
Fim do Se / * source = ‘I’ * /
Fim do Enquanto / * p  0 * /
node [nd].headptr  ‘N’
node [nd].header  0
node [nd].marca  FALSE
node [newloc]  node [nd]
Fim do Se / * marca ([nd].node * /
Fim do Para
Fim do procedimento
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 20
9.3 - GERENCIAMENTO DINÂMICO DE MEMÓRIA
 Conceito
A alocação e deslocação de memória, ou seja, a atribuição de trechos de memória a processos é feita
em blocos de memória de tamanhos requisitados pelos respectivos processos.
A natureza dinâmica dessas ocorrências faz com que, em geral a memória fique fragmentada
intercalando blocos alocados e blocos livres. Esta fragmentação pode provocar a impossibilidade do
atendimento de uma solicitação pela inexistência de um bloco livre no tamanho desejado muito embora o
somatório das áreas livres fragmentadas supere o tamanho solicitado.
Pode-se gerenciar tal problema de diversas maneiras. No processo de compactação considera-se
disponível o espaço entre o final do último bloco de memória alocado e o final da memória. O início do
espaço disponível é apontado por freepoint. Sempre que uma solicitação de memória ultrapassa o espaço
disponível interrompe-se o processamento, compacta-se a memória, atualiza-se o ponteiro freepoint e
verifica-se então a possibilidade de atendimento da solicitação.
Outro processo consiste em manter os espaços liberados ou ainda não alocados em uma lista
encadeada. Esta lista é apontada por freepoint e mantida, como fila de prioridades, em ordem crescente de
endereço.
No gerenciamento dinâmico de memória os nós podem ser alocados e desalocados um de cada vez.
Contudo é frequente que surjam requisições de alocação de blocos de memória de tamanhos variáveis. Nestas
situações os blocos liberados são mantidos, também, em listas encadeadas.
O processo de alocação subsequente de blocos de memória pode ser feito de diversas maneiras, tais
como:
 Primeiro ajuste
 Melhor ajuste
 Pior ajuste
 Primeiro Ajuste
No método do primeiro ajuste a lista de blocos livres é percorrida sequencialmente até encontrar o
primeiro bloco livre cujo tamanho seja maior ou igual do que a quantidade de memória requisitada.
 Melhor Ajuste
O método do melhor ajuste busca o menor bloco livre cujo tamanho seja maior do que ou igual à
quantidade de memória requisitada.
 Pior Ajuste
O método do pior ajuste consiste na alocação de uma porção do maior bloco livre constante da lista de
blocos livres. Usando um pequeno número de grandes blocos para satisfazer a maioria das requisições muitos
blocos de tamanho moderado permanecem sem fragmentação. A não ser no caso da maioria de requisições de
memória em grandes blocos, o método do pior ajuste satisfaz a um maior número de requisições do que os
outros métodos.
 Exemplos
1) Considere-se a situação na qual existem blocos livres com tamanhos 200, 300 e 100 unidades de memória.
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 21
Deseja-se alocar blocos de memória com os tamanhos 150, 100, 125, 10 e 100 unidades. A seqüência de
alocação, pelos diversos processos é a seguinte.
Espaços
Livres
200
300
100
150
200
150
100
Espaços
Livres
200
300
100
100
0
25
0
150
50
300
100
Processo da Primeira Escolha
Solicitações
100
125
100
100
50
50
50 Não pode
75 Não pode
200
75
100
100
0 Não pode
150
50
300
100
Processo da Melhor Escolha
Solicitações
100
125
100
100
50
50
50 Não pode
300
175
75 Não pode
0
0 Não pode
0
Espaços
Livres
200
300
100
Processo da Pior Escolha
Solicitações
100
125
100
100
100
0
150
25
25
100
100
100
2) Considere-se a situação na qual existem blocos livres com tamanhos 110 e 54 unidades de memória.
Deseja-se alocar blocos de memória com os tamanhos 25, 70 e 50 unidades. A seqüência de alocação, pelos
diversos processos é a seguinte.
Espaços
Livres
110
54
Espaços
Livres
110
54
Espaços
Livres
110
54
Processo da Primeira
Escolha
Solicitações
25
70
50
15
85
15
54
54
4
Processo da Melhor
Escolha
Solicitações
25
70
50
110
40 Não pode
29 Não pode
29
Processo da Pior Escolha
Solicitações
25
70
50
15
85
15
54
54
4
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 22
O método do primeiro ajuste é mais eficiente se a lista de blocos disponíveis for mantida em ordem
crescente de endereços de memória. Caso a lista seja mantida em ordem crescente de tamanho de bloco a
busca por melhor ajuste se torna mais eficiente. Caso a lista seja mantida em ordem decrescente de tamanho
de bloco o método do pior ajuste é o mais eficiente.
Todavia não é prático manter a lista de blocos disponíveis classificada por tamanho. Na ausência de
outras considerações ou especificações é usual se dar preferência ao método do primeiro ajuste.
Estruturas de Dados - 2o Período de 1995 - Turma A1 - Listas Generalizadas- 5/28/2017 - Pag. 23
Download