Árvore B - Conceito e Código

Ver o tópico anterior Ver o tópico seguinte Ir em baixo

Árvore B - Conceito e Código

Mensagem por Evandro Abu Kamel em Seg 29 Mar 2010, 18:05

Em computação, Árvore B ou B-Tree é uma estrutura de dado pertencente ao grupo das árvores, e é muito utilizada em banco de dados e em sistemas de arquivos.

Para inserir ou remover variáveis de um nó, o nó não poderá ultrapassar sua ordem e nem ser menor que sua ordem dividida por dois. Árvores B não precisam ser rebalanceadas como são frequentemente as árvores de busca binária com Árvore AVL. Árvores B têm vantagens substanciais em relação a outros tipos de implementações quanto ao tempo de acesso e pesquisa aos nós.

Uma árvore B de ordem "m" (máximo de filhos para cada nó) é uma árvore que atende as seguintes propriedades:

1. Cada nó tem no máximo "m" filhos;
2. Cada nó (exceto a raíz e as folhas) tem no mínimo "m/2" filhos;
3. A raiz tem pelo menos dois filhos se a mesma não for uma folha;
4. Todas as folhas aparecem no mesmo nível e não carregam informação;
5. Um nó não-folha com "k" filhos deve ter k-1 chaves.

Para ler o resto do artigo visite a WikiPédia: http://pt.wikipedia.org/wiki/%C3%81rvore_B

Segue abaixo o código da mesma em C:

Código:
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#define m 2
#define mm 4
#define reservado "cls"

typedef int TipoChave;
typedef struct
{
   TipoChave Chave;
} Registro;

typedef struct Pagina_str *Apontador;
typedef struct Pagina_str
{   
   int n;
   Registro r[mm];
   Apontador p[mm + 1];
} Pagina;

typedef Apontador TipoDicionario;
void Inicializa(TipoDicionario *Dicionario)
{
   *Dicionario = NULL;
}   /* Inicializa */

void Pesquisa(Registro *x, Apontador Ap)
{
   int i;

   if (Ap == NULL)
   {
      printf("Erro: Registro nao esta presente\n");
      getchar();
      getchar();
      return;
   }
   i = 1;
   while (i < Ap->n && x->Chave > Ap->r[i - 1].Chave)
      i++;
   if (x->Chave == Ap->r[i - 1].Chave)
   {
      *x = Ap->r[i - 1];
      return;
   }
   if (x->Chave < Ap->r[i - 1].Chave)
      Pesquisa(x, Ap->p[i - 1]);
   else
      Pesquisa(x, Ap->p[i]);
} /* Pesquisa */

void InsereNaPagina(Apontador Ap, Registro Reg, Apontador ApDir)
{
   int k;
   int NaoAchouPosicao;

   k = Ap->n;
   NaoAchouPosicao = k > 0;
   while (NaoAchouPosicao)
   {
      if (Reg.Chave >= Ap->r[k - 1].Chave)
      {
         NaoAchouPosicao = 0;
         break;
      }
      Ap->r[k] = Ap->r[k - 1];
      Ap->p[k + 1] = Ap->p[k];
      k--;
      if (k < 1)
         NaoAchouPosicao = 0;
   }
   Ap->r[k] = Reg;
   Ap->p[k + 1] = ApDir;
   Ap->n++;
}   /*InsereNaPagina*/

void Ins(Registro Reg, Apontador Ap, int *Cresceu, Registro *RegRetorno, Apontador *ApRetorno)
{
   Apontador ApTemp;
   int i, j;

   if (Ap == NULL)
   {
      *Cresceu = 1;
      *RegRetorno = Reg;
      *ApRetorno = NULL;
      return;
   }
   i = 1;
   while (i < Ap->n && Reg.Chave > Ap->r[i - 1].Chave)
      i++;
   if (Reg.Chave == Ap->r[i - 1].Chave)
   {
      printf(" Erro: Registro ja esta presente\n");
      getchar();
      getchar();
      *Cresceu = 0;
      return;
   }
   if (Reg.Chave < Ap->r[i - 1].Chave)
      Ins(Reg, Ap->p[i - 1], Cresceu, RegRetorno, ApRetorno);
   else
      Ins(Reg, Ap->p[i], Cresceu, RegRetorno, ApRetorno);
   if (!*Cresceu)
      return;
   if (Ap->n < mm)
   {   /* Pagina tem espaco */
      InsereNaPagina(Ap, *RegRetorno, *ApRetorno);
      *Cresceu = 0;
      return;
   }
   /* Overflow: Pagina tem que ser dividida */
   ApTemp = (Apontador) malloc(sizeof(Pagina));
   ApTemp->n = 0;
   ApTemp->p[0] = NULL;
   if (i <= m + 1)
   {
      InsereNaPagina(ApTemp, Ap->r[mm - 1], Ap->p[mm]);
      Ap->n--;
      InsereNaPagina(Ap, *RegRetorno, *ApRetorno);
   }
   else
      InsereNaPagina(ApTemp, *RegRetorno, *ApRetorno);
   for (j = m + 2; j <= mm; j++)
      InsereNaPagina(ApTemp, Ap->r[j - 1], Ap->p[j]);
   Ap->n = m;
   ApTemp->p[0] = Ap->p[m + 1];
   *RegRetorno = Ap->r[m];
   *ApRetorno = ApTemp;
}   /*Ins*/


void Insere(Registro Reg, Apontador *Ap)
{
   int Cresceu;
   Registro RegRetorno;
   Apontador ApRetorno;
   Apontador ApTemp;

   Ins(Reg, *Ap, &Cresceu, &RegRetorno, &ApRetorno);
   if (Cresceu) { /* Arvore cresce na altura pela raiz */
      ApTemp = (Apontador) malloc(sizeof(Pagina));
      ApTemp->n = 1;
      ApTemp->r[0] = RegRetorno;
      ApTemp->p[1] = ApRetorno;
      ApTemp->p[0] = *Ap;
      *Ap = ApTemp;
   }
}   /*Insere*/

void Reconstitui(Apontador ApPag, Apontador ApPai, int PosPai, int *Diminuiu)
{
   Apontador Aux;
   int DispAux, j;

   if (PosPai < ApPai->n) {   /* Aux = Pagina a direita de ApPag */
      Aux = ApPai->p[PosPai + 1];
      DispAux = (Aux->n - m + 1) / 2;
      ApPag->r[ApPag->n] = ApPai->r[PosPai];
      ApPag->p[ApPag->n + 1] = Aux->p[0];
      ApPag->n++;
      if (DispAux > 0) {   /* Existe folga: transfere de Aux para ApPag */
         for (j = 1; j < DispAux; j++)
            InsereNaPagina(ApPag, Aux->r[j - 1], Aux->p[j]);
         ApPai->r[PosPai] = Aux->r[DispAux - 1];
         Aux->n -= DispAux;
         for (j = 0; j < Aux->n; j++)
            Aux->r[j] = Aux->r[j + DispAux];
         for (j = 0; j <= Aux->n; j++)
            Aux->p[j] = Aux->p[j + DispAux];
         *Diminuiu = 0;
      }
      else
      { /* Fusao: intercala Aux em ApPag e libera Aux */
         for (j = 1; j <= m; j++)
            InsereNaPagina(ApPag, Aux->r[j - 1], Aux->p[j]);
         free(Aux);
         for (j = PosPai + 1; j < ApPai->n; j++)
         {    /* Preenche vazio em ApPai */
            ApPai->r[j - 1] = ApPai->r[j];
            ApPai->p[j] = ApPai->p[j + 1];
         }
         ApPai->n--;
         if (ApPai->n >= m)
            *Diminuiu = 0;
      }
   }
   else
   { /* Aux = Pagina a esquerda de ApPag */
      Aux = ApPai->p[PosPai - 1];
      DispAux = (Aux->n - m + 1) / 2;
      for (j = ApPag->n; j >= 1; j--)
         ApPag->r[j] = ApPag->r[j - 1];
      ApPag->r[0] = ApPai->r[PosPai - 1];
      for (j = ApPag->n; j >= 0; j--)
         ApPag->p[j + 1] = ApPag->p[j];
      ApPag->n++;
      if (DispAux > 0) {   /* Existe folga: transfere de Aux para ApPag */
         for (j = 1; j < DispAux; j++)
            InsereNaPagina(ApPag, Aux->r[Aux->n - j], Aux->p[Aux->n - j + 1]);
         ApPag->p[0] = Aux->p[Aux->n - DispAux + 1];
         ApPai->r[PosPai - 1] = Aux->r[Aux->n - DispAux];
         Aux->n -= DispAux;
         *Diminuiu = 0;
      }
      else
      {   /* Fusao: intercala ApPag em Aux e libera ApPag */
         for (j = 1; j <= m; j++)
            InsereNaPagina(Aux, ApPag->r[j - 1], ApPag->p[j]);
         free(ApPag);
         ApPai->n--;
         if (ApPai->n >= m)
            *Diminuiu = 0;
      }
   }
}   /* Reconstitui */

void Antecessor(Apontador Ap, int Ind, Apontador ApPai, int *Diminuiu)
{
   if (ApPai->p[ApPai->n] != NULL)
   {
      Antecessor(Ap, Ind, ApPai->p[ApPai->n], Diminuiu);
      if (*Diminuiu)
         Reconstitui(ApPai->p[ApPai->n], ApPai, ApPai->n, Diminuiu);
      return;
   }
   Ap->r[Ind - 1] = ApPai->r[ApPai->n - 1];
   ApPai->n--;
   *Diminuiu = ApPai->n < m;
}   /* Antecessor */

void Ret(TipoChave Ch, Apontador *Ap, int *Diminuiu)
{
   int Ind, j;
   Apontador WITH;

   if (*Ap == NULL)
   {
      printf("Erro: registro nao esta na arvore\n");
      getchar();
      getchar();
      *Diminuiu = 0;
      return;
   }
   WITH = *Ap;
   Ind = 1;
   while (Ind < WITH->n && Ch > WITH->r[Ind - 1].Chave)
      Ind++;
   if (Ch == WITH->r[Ind - 1].Chave)
   {
      if (WITH->p[Ind - 1] == NULL) {   /* Pagina folha */
         WITH->n--;
         *Diminuiu = WITH->n < m;
         for (j = Ind; j <= WITH->n; j++)
         {
            WITH->r[j - 1] = WITH->r[j];
            WITH->p[j] = WITH->p[j + 1];
         }
         return;
      }
      Antecessor(*Ap, Ind, WITH->p[Ind - 1], Diminuiu);
      if (*Diminuiu)
         Reconstitui(WITH->p[Ind - 1], *Ap, Ind - 1, Diminuiu);
      return;
   }
   if (Ch > WITH->r[Ind - 1].Chave)
      Ind++;
   Ret(Ch, &WITH->p[Ind - 1], Diminuiu);
   if (*Diminuiu)
      Reconstitui(WITH->p[Ind - 1], *Ap, Ind - 1, Diminuiu);
}   /* Ret */


void Retira(TipoChave Ch, Apontador *Ap)
{
   int Diminuiu;
   Apontador Aux;

   Ret(Ch, Ap, &Diminuiu);
   if (Diminuiu && (*Ap)->n == 0) { /* Arvore diminui na altura */
      Aux = *Ap;
      *Ap = Aux->p[0];
      free(Aux);
   }
}   /* Retira */


void Imprime(Apontador p, int Nivel)
{
   int i;

   if (p == NULL)
      return;
   for (i = 1; i <= Nivel; i++)
      printf("      ");
   for (i = 0; i < p->n; i++)
      printf("%4d", p->r[i].Chave);
   putchar('\n');
   for (i = 0; i <= p->n; i++)
      Imprime(p->p[i], Nivel + 1);
}

int main()
{
   Apontador *arv;
   Registro reg;
   char tecla;
   system(reservado);
   arv=(Apontador*) malloc(sizeof(Apontador));
   Inicializa(arv);
   system(reservado);
   printf("MENU DE OPCOES\n");
   printf("--------------\n");
   while(1)
   {
      system(reservado);
      printf("MENU DE OPCOES\n");
      printf("--------------\n");
      printf("1. Insere\n");
      printf("2. Remocao\n");
      printf("3. Visualizar\n");
//      printf("4. Pesquisa\n");
      printf("5. Sair\n");
      printf("--> ");
      scanf("%c", &tecla);
      if (tecla=='5')
         break;
      switch(tecla)
      {
         case '1':
            while(1)
            {
                system(reservado);
                printf("INSERCAO\n");
                printf("--------\n");
                printf("Digite o valor da chave a ser inserida: (999 para finalizar)\n--> ");
                scanf("%d", ®.Chave);
                if (reg.Chave==999)
                   break;
                Insere(reg, arv);
             }
         break;
         case '2':
            while(1)
            {
                system(reservado);
                printf("REMOCAO\n");
                printf("-------\n");
                printf("Digite o valor da chave a ser removida: (999 para finalizar)\n--> ");
                scanf("%d", ®.Chave);
                if (reg.Chave==999)
                   break;
                Retira(reg.Chave, arv);
             }
         break;
         case '3':
            system(reservado);
            printf("IMPRESSAO\n");
            printf("---------\n");
            Imprime(*arv, mm);
            getchar();
            getchar();
         break;
      }
   }
   getchar();
}

Mas ainda não testei o programa.
Espero que isso dê uma clareada nas ideias sobre árvore B.

Até a próxima.

_________________
"Faça as coisas o mais simples que você puder,
porém, não as mais simples." Albert Einstein

avatar
Evandro Abu Kamel
Administrador
Administrador

Número de Mensagens : 222
Idade : 28
Data de inscrição : 11/03/2009

Ver perfil do usuário http://forum.clubedosistema.com

Voltar ao Topo Ir em baixo

Árvore B em Arquivo (C++)

Mensagem por Evandro Abu Kamel em Seg 29 Mar 2010, 18:38

Introdução

Imagine que uma Árvore AVL contenha tantos dados que já não caiba mais em memória. Então, podemos definir uma estratégia em que cada nó da Árvore AVL (que armazena no máximo 1 elemento) ocupe uma posição diferente dentro de um mesmo arquivo de dados. Para acessar um filho de um nó, por exemplo, lemos em qual posição o filho se encontra, vamos até a posição determinada e o carregamos em memória. Isto soluciona o problema da memória, pois teremos de carregar apenas a informação (nó) com o qual queremos trabalhar, não a árvore toda.

Mas então imagine que queremos percorrer a árvore em busca de um elemento qualquer. Agora imagine que o elemento não está na árvore e a árvore tem, vamos dizer, n níveis de altura. Só relembrando, não esqueça de que a memória secundária pode ser ordens de magnitude mais lenta do que a memória principal. No mínimo, teremos que fazer n leituras em disco para buscar a informação que queremos. Ou melhor, para concluir que não pudemos encontrar a informação que queríamos.

Para n pequeno, é evidente que isto não faz diferença - o tempo de acesso a um disco rígido é, geralmente, de alguns milisegundos. Mas lembrando que estamos falando de árvores realmente grandes, o acesso ao disco pode tornar-se um gargalo considerável.

Nesse contexto, a estrutura AVL torna-se bastante ineficiente. Como então solucionar este problema?

R: Diminuindo o número de seeks necessários para se alcançar uma informação.


A Árvore B (ou B-Tree) emprega uma estrutura paginada ideal para minimizar o acesso em disco. Cada um de seus nós contem não um elemento, mas vários; quantos o desenvolvedor quiser. A cada acesso, várias, e não apenas uma informação são lidas de cada vez. Nisso, a informação torna-se muito mais condensada nos primeiros níveis, e altura da árvore diminui consideravelmente.

Vejamos o que a Wikipedia tem a dizer sobre este assunto:

Em ciência da computação, Árvore B ou B-Tree é uma estrutura de dados árvores que são muito utilizadas em banco de dados e sistema de arquivos.

Para inserir ou remover variáveis de um nó, o nó não poderá ultrapassar sua ordem e nem ser menor que sua ordem dividida por dois. Árvores B não precisam ser rebalanceadas como são freqüentemente as árvores de busca binária com Árvore AVL. Árvores B têm vantagens substanciais em relação a outros tipos de implementações quanto ao tempo de acesso e pesquisa aos nós.


No entanto, preste bastante atenção ao fato de que cada autor trata a ordem da Árvore B de uma maneira diferente. No livro Algoritmos - Teoria e Prática, por exemplo, e se não me engano, os autores se referem não à ordem da Árvore, mas à seu grau t, definido como o número mínimo de filhos que um nó não-raiz pode possuir. Assim, o número mínimo de elementos será igual à t-1 e o máximo igual à 2t-1. Com isso, acabam efetivamente definindo a ordem da árvore (a que me refiro como sendo o número máximo de filhos de um nó) como 2t e o número máximo de elementos como 2t-1. Perfeitamente dentro das regras enunciadas acima no trecho retirado da Wikipedia, se não fosse por um pequeno detalhe.

Ao fazer isto, os autores limitam a ordem de suas árvores a termos pares (que estejam na forma 2t), o que simplifica seus algoritmos de inserção e remoção, mas acabam por não mais descrever à uma Árvore B de ordem geral.

O código que apresentamos e documentamos a seguir se adéqua à Árvores B de quaisquer ordem.


Propósito

Como bem sabemos, o propósito de uma Árvore B é otimizar o acesso e a manipulação da informação armazenada em discos; serve geralmente como estrutura de índice à outros arquivos de registros no qual o índice pode se tornar muito grande para caber em memória, em que neste contexto (atuando como índice) a árvore armazena uma chave e a posição relativa em arquivo (relative record number ou byte offset) em que o registro se encontra no arquivo de registros.

Somente lembrando, o emprego de Árvores B em detrimento de Árvore AVL só faz sentido quando estamos lidando com um conjunto de informações tão grande que não caiba interamente em memória principal; caso utilizássemos uma Árvore AVL para tratar tal conjunto de informações a performance cairia muito devido ao número de acessos à disco realizados pelo sistema operacional que, na melhor das hipóteses, estaria utilizando sua memória virtual para gerenciar a falta de memória.


Definições

Antes de prosseguir, vamos primeiro enunciar algumas definições para que não haja confusão no decorrer deste artigo:

A ordem m da árvore está definida como o número máximo de filhos que um único nó pode possuir;

Logo, o número máximo de elementos num nó e dado por m-1 e o máximo de filhos por m. Já o número mínimo de elementos que um nó pode possuir é dado pela fórmula floor((m-1) / 2).

A Árvore B está implementada em arquivo, então definimos "Offset" como um typedef para long int. Para representar o equivalente em arquivo para o ponteiro nulo (NULL) utilizaremos o valor constante NULLOFFSET como -1L (menos-um na forma de long int). Esta é uma prática comum pois a posição 0, em arquivo, é uma posição válida, ao contrário de seu equivalente em memória.


Implementação

O código que apresentamos e documentamos a seguir se adequa à Árvores B de quaisquer ordens. A implementação foi feita em C++ no Microsoft Visual Studio, sem utilizar as extensões .NET/CLI (apesar da solução, em si, possuir código misto pois estava atrelada à uma interface gráfica em WinForms).

Clique aqui para ver o código e a documentação completos da Árvore B (BTree) em Arquivo (em C++) gerados pela ferramenta Doxygen. O código é, claro, livre para quaisquer adaptações desde que permaneça sob a Licença MIT e suas informações autorais sejam mantidas. Mas lembre-se de que o código tem fins meramente educativos e não deve ser utilizado num ambiente onde a performance é um fator crítico. Ou, melhor, não deve ser utilizado em outro lugar fora um trabalho acadêmico simples.
Diagrama de Classes


O Nó (BTreeNode)

O nó da Árvore B, aqui chamado de BTreeNode, pode conter vários elementos, em contraste com a Árvore AVL, em que pode haver apenas um. A Árvore B pode ser vista como uma generalização da Árvore AVL para ordens superiores, ou a Árvore AVL como uma especialização da Árvore B para ordem 2 (m=2).

Assim, o nó deve conter um vetor de elementos e um vetor de filhos, onde estes filhos, ao invés de conter ponteiros em memória, contem ponteiros em arquivo (Offsets) para os próximos nós.


Representação de uma Árvore B [Wikipedia]

É nesta classe que estão contidos os métodos split() e merge(), que divide um nó em dois e intercala dois nós em um único nó, respectivamente. Estes métodos são chamados durante a operação de Inserção ou de Remoção pela classe principal BTree, que é a classe gerenciadora dos nós. Mais informações serão apresentadas à seguir.


A Lista de Disponíveis (DStack)

Como a estrutura da Árvore B é mantida em arquivo, não basta alocarmos/desalocarmos nós em memória utilizando new e delete como fazíamos na Árvore AVL. Temos que alocar uma posição no arquivo que acomoda a árvore e armazenar o nó nesta posição; ao deletarmos um nó temos que, de algum modo, avisar que a posição que antes acomodava o nó agora está vaga. Para isto, utilizamos outra estrutura em arquivo denominada lista de disponíveis, que é responsável por registrar, na forma de uma lista encadeada, onde estão os registros disponíveis e assim indicar qual a próxima posição vaga no arquivo. Para simplificação, esta lista é, na verdade, geralmente implementada como uma pilha.

Assim, suponha que a pilha está vazia (a posição inicial registrada é NULLOFFSET) e a posição 241 foi desocupada. Então ao chamar o método push(241) a lista de disponíveis deverá caminhar até a posição 241, escrever NULLOFFSET e então registrar que 241 é o começo da pilha.

Agora suponha que 340 foi desocupada. Então o método push(340) irá caminhar até a posição 340, escrever 240, que é o endereço do nó que antes era começo da pilha, e registrar que a pilha começa agora na posição 340. Assim por diante, push(500) caminhará até a posição 500, escreverá o endereço do começo anterior (340) e registrará que a posição inicial é, agora, 500. Assim, os nós vão se encadeando - cada posição disponível carrega uma referência a próxima posição disponível ou a NULLOFFSET se não há nada mais além daquele ponto.

Para remover, pop() simplesmente deverá ler quem é a posição inicial, e, caso seja diferente de NULLOFFSET, caminhar até esta posição, ler qual é o endereço do próximo nó (ou NULLOFFSET, caso não haja mais nós) e então registrar esta posição como a nova posição inicial.

Clique aqui para visualizar a implementação da lista de disponíveis em C++.


A Árvore (BTree)

A classe BTree, ou Árvore B, deve ser responsável por gerenciar seus nós, alocando, dealocando, carregando e descarregando, tentando o máximo possível minimizar a quantidade de nós carregados em memória de cada vez. É nela que estão contidos os métodos de inserção e remoção, que em nossa implementação são recursivos.

Abaixo está uma pequena descrição sobre o funcionamento da inserção e remoção, porém note que seus códigos correspondentes são, na verdade, bem mais elaborados que esta pequena introdução. Felizmente, há comentários no decorrer do código para auxiliar em seu entendimento.
Inserção / Split

O método de inserção da classe BTree pode cair no caso em que não é mais possível armazenar informação num nó pois este está cheio. Quando isto acontece, chama-se o método-membro Split do nó que está cheio, que dividirá o nó em dois e retornará uma referência em arquivo (Offset) ao novo nó criado durante a divisão. O método que controla a inserção está presente na classe BTree, enquanto o split pertence ao BTreeNode.
Remoção / Merge

Analogamente a inserção, o método de remoção da classe BTree pode cair no caso em que é necessário remover um elemento de um nó mas isto violaria o número mínimo de elementos que um nó deve conter. Neste caso, a árvore pode encontrar o elemento sucessor (imediatamente seguinte) ao elemento que estamos tentando remover, trocar os dois e então remover o substituto recursivamente, ou, caso não seja possível emprestar de ninguém, realizar uma intercalação entre dois nós mínimos formando um novo nó completo. Esta intercalação é conhecida como merge. O método que controla a remoção está presente na classe BTree, enquanto o merge pertence ao BTreeNode.


Código fonte em C++

O código fonte pode ser navegado na íntegra clicando-se aqui. Para fazer download de uma solução Visual Studio 2008 contendo o fonte, clique aqui.http://www.comp.ufscar.br/~cesarsouza/archive/Guides/%c1rvore%20B%20em%20Arquivo/doc/html/class_data_structures_1_1_d_stack.html

Fonte: http://crsouza.blogspot.com/2008/12/rvore-b-em-arquivo-em-c.html

_________________
"Faça as coisas o mais simples que você puder,
porém, não as mais simples." Albert Einstein

avatar
Evandro Abu Kamel
Administrador
Administrador

Número de Mensagens : 222
Idade : 28
Data de inscrição : 11/03/2009

Ver perfil do usuário http://forum.clubedosistema.com

Voltar ao Topo Ir em baixo

Re: Árvore B - Conceito e Código

Mensagem por LeonardoNG em Ter 23 Out 2012, 05:17

Muito bom Very Happy .
avatar
LeonardoNG
Membro
Membro

Número de Mensagens : 1
Idade : 25
Data de inscrição : 23/10/2012

Ver perfil do usuário

Voltar ao Topo Ir em baixo

Re: Árvore B - Conceito e Código

Mensagem por Conteúdo patrocinado


Conteúdo patrocinado


Voltar ao Topo Ir em baixo

Ver o tópico anterior Ver o tópico seguinte Voltar ao Topo


 
Permissão deste fórum:
Você não pode responder aos tópicos neste fórum