Skip to content

Implementação de package feito inteiramente em Dart com métodos de inserção e exclusão de vértices e arestas para criação e manipulação de grafos orientados e não orientados. Pode ser usado em Dart/Flutter.

License

Notifications You must be signed in to change notification settings

hdgiacon/graphs_toolkit

Repository files navigation

Graphs Toolkit

Idiomas:

Português Inglês

 

    Representação da estrutura de dados grafo por meio de lista de adjacência.

    Estrutura implementada inteiramente em Dart com métodos de inserção e exclusão de vértices e arestas para criação e manipulação de grafos orientados e não orientados.

 

Conteúdo

 

O que é um Grafo

    Um grafo é uma estrutura de dados composta por dois elementos, um conjunto de vértices (ver definição) e um conjunto de arestas (ver definição). Podem ser do tipo orientado (ver definição) ou não orientado (ver definição).

 

Orientado

    Grafos orientados, também conhecidos por digrafos, são aqueles nos quais as suas arestas possuem um sentido definido, ou seja, de um vértice u pode-se chegar em v mas oposto não ocorre, não há uma relação simétrica. Graficamente podem ser representados da seguinte forma:

 

 

    Já que possuem um sentido definido, podem os seus vértices podem ter arestas que saem e chegam neles mesmos:

 

 

Não Orientado

    Grafos não orientados são aqueles nos quais as suas arestas não possuem um sentido definido, pode-se ir tanto de u para v quanto de v para u, ou seja, ocorre a relação de simetria. Graficamente podem ser representados da seguinte forma:

 

 

O que é um Vértice

    Um vértice, também conhecido por nó, e a unidade fundamental para a composição de um grafo. A partir das suas composições com arestas muitos problemas podem ser resolvidos através da sua modelagem. No caso da implementação deste package, um vértice possui um identificador para diferenciá-lo dos demais, representado pelo atributo label. Pode armazenar um valor numérico em value e possui uma lista de arestas nos quais indica com quais outros vértices este está conectado.

 

 

    Caso um value não seja definido inicialmente, nesta implementação ele terá o valor 0.

 

O que é uma Aresta

    Uma aresta tem a função de conectar dois vértices além de definir de qual tipo um grafo será, orientado ou não orientado.

    Arestas orientadas possuem um sentido definido, ou seja, há somente um caminho a ser seguido:

 

 

    Arestas não orientadas não possuem um sentido definido, podendo conectar dois vértices no sentido de ida e volta:

 

 

    Em ambos os tipos, as arestas podem armazenar um peso numérico:

 

    Caso um peso não seja passado, nesta implementação ele será nulo.

 

Métodos Básicos de um Grafo

    Tanto grafos orientados quanto os não orientados possuem atributos e métodos em comum. A partir disso, muitas funcionalidades presentes neste Package estão implementadas para os dois tipos de estrutura, mostradas nesta sessão.

 

GetV

    Este método consiste em buscar um vértice no grafo através do seu identificador. Caso exista, ele será retornado, caso contrário, será lançado um [StateError].

  final myGraph = OrientedGraph();

  myGraph.addVertex(newVertex: Vertex(label: '1'), connectedTo: ['2', '3']);
  myGraph.addVertex(newVertex: Vertex(label: '2'));
  myGraph.addVertex(newVertex: Vertex(label: '3'));

  myGraph.getV('1');  // return Vertex(label: '1')

  myGraph.getV('w');  // throw `[StateError]`

 

Adicionar Vértices do Modo Básico

    Como a estrutura padrão para grafos são listas, então adicionando novos vértices e arestas a elas, o grafo será modelado. Um primeiro método para isso consiste em primeiramente adicionar os vértices necessários ao grafo:

    final myGraph = OrientedGraph();

    myGraph.vertices.add(Vertex(label: 'u'));
    myGraph.vertices.add(Vertex(label: 'v'));
    myGraph.vertices.add(Vertex(label: 'y'));
    myGraph.vertices.add(Vertex(label: 'x'));
    myGraph.vertices.add(Vertex(label: 'w'));
    myGraph.vertices.add(Vertex(label: 'z'));

    Após todos os vértices serem definidos, as suas arestas precisam ser adicionadas. Para isso em cada vértice e preciso chamar a função addEdge para que a conexão entre eles seja feita. Recomenda-se utilizar o método getV para encontrar o vértice desejado para uma melhor leitura do código, entre o vértice de origem e o de destino.

    myGraph.getV('u').addEdge(connectedTo: myGraph.getV('v'));
    myGraph.getV('u').addEdge(connectedTo: myGraph.getV('x'));

    myGraph.getV('v').addEdge(connectedTo: myGraph.getV('y'));

    myGraph.getV('y').addEdge(connectedTo: myGraph.getV('x'));

    myGraph.getV('x').addEdge(connectedTo: myGraph.getV('v'));

    myGraph.getV('w').addEdge(connectedTo: myGraph.getV('y'));
    myGraph.getV('w').addEdge(connectedTo: myGraph.getV('z'));

    myGraph.getV('z').addEdge(connectedTo: myGraph.getV('z'));

    Vale ressaltar que, como aqui todos os vértices são feitos de maneira manual, caso seja um grafo não orientado, e necessário fazer as arestas de volta também, para que a estrutura computacional corresponda a modelagem.

 

Adicionar Vértices com addVertex

    Uma segunda maneira de se povoar um grafo e através do método addVertex. Esta função consiste em encapsular toda a criação de um vértice e criação das suas arestas para outros vértices mesmo que eles ainda não tenham sido criados.

    final myGraph = OrientedGraph();

    myGraph.addVertex(newVertex: Vertex(label: 'u'), connectedTo: ['v', 'x']);
    myGraph.addVertex(newVertex: Vertex(label: 'v'), connectedTo: ['y']);
    myGraph.addVertex(newVertex: Vertex(label: 'y'), connectedTo: ['x']);
    myGraph.addVertex(newVertex: Vertex(label: 'x'), connectedTo: ['v']);
    myGraph.addVertex(newVertex: Vertex(label: 'w'), connectedTo: ['y', 'z']);
    myGraph.addVertex(newVertex: Vertex(label: 'z'), connectedTo: ['z']);

    No exemplo acima, quando o vértice u e criado, tanto o vértice v quando x ainda não existem no grafo, mas este método os armazena em uma lista de espera, até que eles sejam definitivamente criados, assim as arestas necessárias serão criadas e o grafo seguir a modelagem desejada.

    Além de definir as conexões com arestas baseados em connectedTo, também e possível passar pesos para as arestas através do parâmetro edgeWeight. Este parâmetro consiste em uma lista de valores para os pesos, dessa forma, posicionalmente com a lista connectedTo, as arestas terão um peso definido a cada uma. O valor null também pode ser atribuído a peso de arestas.

    final myGraph = OrientedGraph();

    myGraph.addVertex(newVertex: Vertex(label: 'u'), connectedTo: ['v', 'x'], edgeWeight: [null, 2]);
    myGraph.addVertex(newVertex: Vertex(label: 'v'), connectedTo: ['y'], edgeWeight: []);
    myGraph.addVertex(newVertex: Vertex(label: 'y'), connectedTo: ['x'], edgeWeight: [null]);
    myGraph.addVertex(newVertex: Vertex(label: 'x'), connectedTo: ['v']);
    myGraph.addVertex(newVertex: Vertex(label: 'w'), connectedTo: ['y', 'z'], edgeWeight: [6]);
    myGraph.addVertex(newVertex: Vertex(label: 'z'), connectedTo: ['z'], edgeWeight: [8]);

    No exemplo acima existem 4 maneiras de atribuir valores nulos para o peso de arestas: não utilizando o parâmetro edgeWeight (linha 6), utilizando uma lista vazia (linha 4), utilizando explicitamente null (linhas 3 e 5) e omitindo o valor do ultimo peso (linha 7)

    Dentre as vantagens que este método possui em relação ao básico, pode-se citar principalmente a redução na quantidade de código, principalmente em grafos não orientados, pois neles a adição de arestas é sempre dobrada, como mostra o exemplo abaixo:

    final myGraph = NotOrientedGraph();

    myGraph.vertices.add(Vertex(label: 'u'));
    myGraph.vertices.add(Vertex(label: 'v'));
    myGraph.vertices.add(Vertex(label: 'y'));
    myGraph.vertices.add(Vertex(label: 'x'));
    myGraph.vertices.add(Vertex(label: 'w'));
    myGraph.vertices.add(Vertex(label: 'z'));

    myGraph.getV('u').addEdge(connectedTo: myGraph.getV('v'));
    myGraph.getV('v').addEdge(connectedTo: myGraph.getV('u'));

    myGraph.getV('u').addEdge(connectedTo: myGraph.getV('x'));
    myGraph.getV('x').addEdge(connectedTo: myGraph.getV('u'));

    myGraph.getV('v').addEdge(connectedTo: myGraph.getV('y'));
    myGraph.getV('y').addEdge(connectedTo: myGraph.getV('v'));

    myGraph.getV('y').addEdge(connectedTo: myGraph.getV('x'));
    myGraph.getV('x').addEdge(connectedTo: myGraph.getV('v'));

    myGraph.getV('x').addEdge(connectedTo: myGraph.getV('y'));
    myGraph.getV('v').addEdge(connectedTo: myGraph.getV('x'));

    myGraph.getV('w').addEdge(connectedTo: myGraph.getV('y'));
    myGraph.getV('y').addEdge(connectedTo: myGraph.getV('w'));

    myGraph.getV('w').addEdge(connectedTo: myGraph.getV('z'));
    myGraph.getV('z').addEdge(connectedTo: myGraph.getV('w'));


    final myGraph2 = NotOrientedGraph();

    myGraph2.addVertex(newVertex: Vertex(label: 'u'), connectedTo: ['v', 'x']);
    myGraph2.addVertex(newVertex: Vertex(label: 'v'), connectedTo: ['y', 'x']);
    myGraph2.addVertex(newVertex: Vertex(label: 'y'), connectedTo: ['x', 'w']);
    myGraph2.addVertex(newVertex: Vertex(label: 'x'));
    myGraph2.addVertex(newVertex: Vertex(label: 'w'), connectedTo: ['z']);
    myGraph2.addVertex(newVertex: Vertex(label: 'z'));

    myGraph == myGraph2; // true

    Outra vantagem que este método possui é a legibilidade do código, pois em um mesmo comando sabe qual vértice está sendo criado, para quais outros ele possui uma aresta e os pesos declarados para cada aresta.

 

Excluir um Vértice

    Exclui um vértice do grafo pelo seu identificador juntamente com as arestas que saem e chegam nele.

  final myGraph = NotOrientedGraph();

  myGraph.addVertex(newVertex: Vertex(label: 'u'), connectedTo: ['v', 'x'], edgeWeight: [null, 2]);
  myGraph.addVertex(newVertex: Vertex(label: 'v'), connectedTo: ['y'], edgeWeight: []);
  myGraph.addVertex(newVertex: Vertex(label: 'y'), connectedTo: ['x'], edgeWeight: [null]);
  myGraph.addVertex(newVertex: Vertex(label: 'x'), connectedTo: ['v']);
  myGraph.addVertex(newVertex: Vertex(label: 'w'), connectedTo: ['y', 'z'], edgeWeight: [6]);
  myGraph.addVertex(newVertex: Vertex(label: 'z'), connectedTo: ['z'], edgeWeight: [8]);

  print(myGraph);

  // (u) - [ (v) (x) ]
  // (v) - [ (y) ]
  // (y) - [ (x) ]
  // (x) - [ (v) ]
  // (w) - [ (y) (z) ]
  // (z) - [ (z) ]

  myGraph.excludeVertex(vertexLabel: 'v');

  print(myGraph);

  // (u) - [ (x) ]
  // (y) - [ (x) ]
  // (x) - [ ]
  // (w) - [ (y) (z) ]
  // (z) - [ (z) ]

    Caso o vértice não seja encontrado no grafo, uma mensagem de erro será mostrada:

 

 

    Para mais informações sobre o erro ocorrido, execute a sua aplicação no modo debug e um log abaixo desta mensagem de erro também será mostrado, com informações sobre a exceção lançada e o estado atual da pilha de chamadas (StackTrace).

 

First

    Retorna o primeiro vértice da lista de vértices do grafo.

  final myGraph = NotOrientedGraph();

  myGraph.addVertex(newVertex: Vertex(label: 'u'), connectedTo: ['v', 'x'], edgeWeight: [null, 2]);
  myGraph.addVertex(newVertex: Vertex(label: 'v'), connectedTo: ['y'], edgeWeight: []);
  myGraph.addVertex(newVertex: Vertex(label: 'y'), connectedTo: ['x'], edgeWeight: [null]);
  myGraph.addVertex(newVertex: Vertex(label: 'x'), connectedTo: ['v']);
  myGraph.addVertex(newVertex: Vertex(label: 'w'), connectedTo: ['y', 'z'], edgeWeight: [6]);
  myGraph.addVertex(newVertex: Vertex(label: 'z'), connectedTo: ['z'], edgeWeight: [8]);

  myGraph.first   // Vertex(label: 'u')

 

Last

    Retorna o ultimo vértice da lista de vértices do grafo.

  final myGraph = NotOrientedGraph();

  myGraph.addVertex(newVertex: Vertex(label: 'u'), connectedTo: ['v', 'x'], edgeWeight: [null, 2]);
  myGraph.addVertex(newVertex: Vertex(label: 'v'), connectedTo: ['y'], edgeWeight: []);
  myGraph.addVertex(newVertex: Vertex(label: 'y'), connectedTo: ['x'], edgeWeight: [null]);
  myGraph.addVertex(newVertex: Vertex(label: 'x'), connectedTo: ['v']);
  myGraph.addVertex(newVertex: Vertex(label: 'w'), connectedTo: ['y', 'z'], edgeWeight: [6]);
  myGraph.addVertex(newVertex: Vertex(label: 'z'), connectedTo: ['z'], edgeWeight: [8]);

  myGraph.last   // Vertex(label: 'z')

 

Has Cycle

    Método que retorna verdadeiro se houver algum ciclo no grafo.

  final myGraph = NotOrientedGraph();

  myGraph.addVertex(newVertex: Vertex(label: 'u'), connectedTo: ['v', 'x'], edgeWeight: [null, 2]);
  myGraph.addVertex(newVertex: Vertex(label: 'v'), connectedTo: ['y'], edgeWeight: []);
  myGraph.addVertex(newVertex: Vertex(label: 'y'), connectedTo: ['x'], edgeWeight: [null]);
  myGraph.addVertex(newVertex: Vertex(label: 'x'), connectedTo: ['v']);
  myGraph.addVertex(newVertex: Vertex(label: 'w'), connectedTo: ['y', 'z'], edgeWeight: [6]);
  myGraph.addVertex(newVertex: Vertex(label: 'z'), connectedTo: ['z'], edgeWeight: [8]);

  myGraph.hasCycle   // true

 

Breadth First Search - Bfs

    Busca em largura em um grafo consiste em calcular a distância para todos os vértices alcançáveis a partir de um vértice de origem. Esse método causa um efeito colateral no grafo, gerando uma árvore de busca em largura.

    A árvore resultante e definida através do parâmetro ancestor em cada vértice, no qual armazena o vértice anterior e a distância calculada e guardada em value. Conforme o algoritmo percorre o grafo, o parâmetro visited em cada vértice se torna verdadeiro.

 

Depth First Search - Dfs

    Busca em profundidade em um grafo consiste em, a cada vértice do grafo, explorar o quanto for possível as suas listas de adjacência até ir para o próximo vértice não visitado. Esse método causa um efeito colateral no grafo, gerando uma floresta de busca em profundidade, ou seja, contém várias árvores de busca em profundidade.

    Os parâmetros value, ancestor e visited armazenam a distância calculada, o vértice anterior e a identificação de visitado respectivamente.

 

Métodos de um Grafo Orientado

    Grafos orientados possuem características e métodos exclusivos de sua estrutura, nas quais serão mostradas nesta sessão.

 

Num of Edges

    Retorna o número de arestas presentes em um grafo orientado, somando a quantidade de arestas presente em cada vértice.

 

Get all Edges

    Retorna uma lista com todas as arestas presentes em um grafo orientado.

 

Is Strongly Connected

    Verifica se o grafo orientado é fortemente conexo, ou seja, se para cada par de vértices (u,v), v e acessível a partir de u

 

 

Is DAG

    Verifica se o grafo é um DAG (grafo acíclico orientado)

 

 

To String

    Método no qual mostra uma versão simplificada do grafo na linha de comando utilizando listas de adjacência.

    print(myGraph.toString());

    (1) -> [ (2) (3) ]
    (2) -> [ ]
    (3) -> [ ]

    Omitindo a chamada do método toString() dentro de print também funciona:

    print(myGraph);

    (1) -> [ (2) (3) ]
    (2) -> [ ]
    (3) -> [ ]

 

PrintGraph

    Método mais robusto para mostrar o conteúdo de um grafo orientado na linha de comando. Mostra utilizando listas de adjacência com alguns parâmetros opcionais. Valores nulos não são mostrados.

    print(myGraph.printGraph());

      (1) -----> (2)
          -----> (3)

      (2)

      (3) -----> (3)

    Os valores associados a cada vértice podem ser mostrados com o parâmetro vertexValue, seguindo o padrão (label:value):

    print(myGraph.printGraph(vertexValue: true));

      (1:5) -----> (2:4)
            -----> (3:10)

      (2:4)

      (3:10) -----> (3:10)

    Os pesos de cada aresta também podem ser mostrados com o parâmetro edgeWeight:

    print(myGraph.printGraph(edgeWeight: true));

    (1) --1--> (2)
        --2--> (3)

    (2)

    (3) --8--> (3)

 

Métodos de um Grafo Não Orientado

    Grafos não orientados possuem características e métodos exclusivos de sua estrutura, nas quais serão mostradas nesta sessão.

 

Num of Edges

    Retorna o número de arestas presentes em um grafo não orientado, seguindo a equação abaixo:

  numero_total_de_vertices ~/ 2

    pois para grafos não orientados, as arestas repetidas mas com sentido oposto são consideradas as mesmas.

 

Get all Edges

    Retorna uma lista com todas as arestas presentes em um grafo não orientado orientado, incluindo arestas de ida e de volta.

 

Is Connected

    Verifica se um grafo não orientado e conexo, ou seja, se a partir de um vértice pode-se chegar a todos os outros.

 

 

Is Tree

    Verifica se um grafo não orientado é acíclico e conexo.

 

 

Is Forest

    Verifica se um grafo é não orientado e acíclico

 

 

To String

    Método no qual mostra uma versão simplificada do grafo na linha de comando utilizando listas de adjacência.

    print(myGraph.toString());

    (1) -- [ (2) (3) ]
    (2) -- [ (1) ]
    (3) -- [ (1) ]

    Omitindo a chamada do método toString() dentro de print também funciona:

    print(myGraph);

    (1) -- [ (2) (3) ]
    (2) -- [ (1) ]
    (3) -- [ (1) ]

 

PrintGraph

    Método mais robusto para mostrar o conteúdo de um grafo não orientado na linha de comando. Mostra utilizando listas de adjacência com alguns parâmetros opcionais. Valores nulos não são mostrados.

    print(myGraph.printGraph());

    (1) ----- (2)
        ----- (3)

    (2) ----- (1)

    (3) ----- (1)

    Os valores associados a cada vértice podem ser mostrados com o parâmetro vertexValue, seguindo o padrão (label:value):

    print(myGraph.printGraph(vertexValue: true));

    (1:5) ----- (2:4)
          ----- (3:10)

    (2:4) ----- (1:5)

    (3:10) ----- (1:5)

    Os pesos de cada aresta também podem ser mostrados com o parâmetro edgeWeight:

    print(myGraph.printGraph(edgeWeight: true));

    (1) --1-- (2)
        --2-- (3)

    (2) --5-- (1)

    (3) --10-- (1)

 

Métodos de um Vértice

    Métodos exclusivos de um vértice, alguns deles podem ser somente para grafos orientados e, caso sejam utilizados em grafos não orientados, uma mensagem de erro será retornada.

 

Add Edge

    Cria uma nova aresta e a adiciona na lista de arestas daquele vértice.

    final myGraph = OrientedGraph();

    myGraph.vertices.add(Vertex(label: 'u'));
    myGraph.vertices.add(Vertex(label: 'v'));

    myGraph.getV('u').addEdge(connectedTo: myGraph.getV('v'));

    A nova aresta pode ser criada com peso, caso não seja passado, será null.

    myGraph.getV('u').addEdge(connectedTo: myGraph.getV('v'), weight: 5);

    Se a aresta já existir, a exceção EdgeAlreadyExistsException será levantada.

  EdgeAlreadyExistsException(
    'Edge between $label and ${connectedTo.label} already exists',
  );

 

Exclude Edge

    Remove uma aresta da lista de arestas deste vértice pelo identificador do vértice de destino.

    final myGraph = OrientedGraph();

    myGraph.vertices.add(Vertex(label: 'u'));
    myGraph.vertices.add(Vertex(label: 'v'));

    myGraph.getV('u').addEdge(connectedTo: myGraph.getV('v'));

    myGraph.getV('u').excludeEdge('v');

    Caso a aresta não seja encontrada no vértice, a seguinte exceção será lançada:

  EdgeNotFoundException(
    'Edge between (vertex $label) and (vertex $destinyLabel) not found!!!!!!',
  );

 

Vertices of Edges List

    Retorna a lista de adjacência do vértice a partir da edgesList.

    final myGraph = OrientedGraph();

    myGraph.addVertex(newVertex: Vertex(label: 'u'), connectedTo: ['v', 'x']);

    myGraph.getV('u').verticesOfEdgesList   //[Vertex(label: 'v'), Vertex(label: 'x')]

 

Is Sinkhole

    Verifica se naquele vértice há somente arestas que chegam nele, ou seja, não há arestas saindo. Válido somente para grafos orientados.

    final myGraph = OrientedGraph();

    myGraph.addVertex(newVertex: Vertex(label: '1'), connectedTo: ['2', '3']);
    myGraph.addVertex(newVertex: Vertex(label: '2'));
    myGraph.addVertex(newVertex: Vertex(label: '3'));

    myGraph.getV('2').isSinkhole;   // true
    myGraph.getV('3').isSinkhole;   // true

 

Is Generator

    Verifica se naquele vértice há somente arestas que saem dele, ou seja, não há arestas chegando. Válido somente para grafos orientados.

    final myGraph = OrientedGraph();

    myGraph.addVertex(newVertex: Vertex(label: '1'), connectedTo: ['2', '3']);
    myGraph.addVertex(newVertex: Vertex(label: '2'));
    myGraph.addVertex(newVertex: Vertex(label: '3'));

    myGraph.getV('1').isSinkhole;   // true

 

Entry Degree

    Número de arestas que entram no vértice.

    final myGraph = OrientedGraph();

    myGraph.addVertex(newVertex: Vertex(label: '1'), connectedTo: ['2', '3']);
    myGraph.addVertex(newVertex: Vertex(label: '2'));
    myGraph.addVertex(newVertex: Vertex(label: '3'));

    myGraph.getV('1').entryDegree;   // 0

 

Exit Degree

    Número de arestas que saem do vértice.

    final myGraph = OrientedGraph();

    myGraph.addVertex(newVertex: Vertex(label: '1'), connectedTo: ['2', '3']);
    myGraph.addVertex(newVertex: Vertex(label: '2'));
    myGraph.addVertex(newVertex: Vertex(label: '3'));

    myGraph.getV('1').entryDegree;   // 2

About

Implementação de package feito inteiramente em Dart com métodos de inserção e exclusão de vértices e arestas para criação e manipulação de grafos orientados e não orientados. Pode ser usado em Dart/Flutter.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages