Создание списка смежности в C++ для ориентированного графика

Привет всем:) сегодня я совершенствую свои навыки в области теории графов и структур данных. Я решил сделать небольшой проект на C++, потому что прошло некоторое время с тех пор, как я работал на C++.

Я хочу сделать список смежности для ориентированного графа. Другими словами, что-то, что выглядит так:

0-->1-->3
1-->2
2-->4
3-->
4-->

это будет направленный граф с v0 (вершиной 0), имеющим ребро к V1 и V3, V1, имеющим ребро к V2, и V2, имеющим ребро к V4, например это:

V0----->V1---->V2---->V4
 |
 |
 v
 V3 

Я знаю, что для этого мне нужно создать список смежности на C++. список смежности в основном является массив связанных списков. Хорошо, давайте посмотрим псевдо-код c++:

#include <stdio>
#include <iostream>
using namespace std;

struct graph{
//The graph is essentially an array of the adjList struct.  
node* List[];

};

struct adjList{
//A simple linked list which can contain an int at each node in the list.

};

struct node {
int vertex;
node* next;
};

int main() {
//insert cool graph theory sorting algorithm here
}

как вы можете сказать, этот псевдокод в настоящее время далеко не на высоте. И это то, с чем я хотел помочь-указатели и структуры на C++ никогда не были моей сильной стороной. Прежде всего, это заботится о вершинах, которые a вершина указывает на ... но как насчет самой вершины? Как я могу отслеживать эту вершину? Когда я перехожу через массив, мне не поможет только знать, на какие вершины указывают, а также знать, какие точки to них. Первый элемент в каждом списке, вероятно, должен быть этой вершиной, а затем элементы после этого-вершины, на которые он указывает. Но тогда, как я могу получить доступ к этому первому элементу списка в моей основной программе? (извините, если это запутано или запутанный, я бы с удовольствием перефразировал).

Я хотел бы иметь возможность зацикливаться на этом списке смежности, чтобы сделать некоторые интересные вещи с графиками. Например, для реализации некоторых алгоритмов теории графов (сортировка, кратчайший путь и т. д.) с использованием представления списка смежности.

(кроме того, у меня был вопрос о списке смежности. Что отличается от простого использования списка массивов? Почему я не могу просто иметь список с массивом в каждом элементе списка?)

5 ответов


вы можете использовать вектор в узле, как список смежности.

class node {
  int value;
  vector<node*> neighbors;
 };

если график известен во время компиляции, вы можете использовать массив, но это "немного" сложнее. Если вы знаете только размер графа (во время компиляции) вы можете сделать что-то подобное.

template<unsigned int N>
class graph {
  array<node, N> nodes;
 }

чтобы добавить соседа, вы делаете что-то вроде этого (не забывайте нумерацию с нуля):

nodes[i].neighbors.push_back(nodes+j); //or &nodes[j]

конечно, вы можете сделать список смежности без указателя и работать "над столом. Чем у тебя vector<int> в узле и вы нажимаете номер соседа. При обоих представлениях графика вы можете реализовать все алгоритмы, которые используют список смежности.

и, наконец, я мог бы добавить. Некоторые используют список вместо вектора, потому что удаление в O (1) времени. Ошибка. Для большинства алгоритмов порядок в списке смежности не важен. Таким образом, вы можете удалить любой элемент из vector в O (1) времени. Просто поменять это с последним элементом,pop_back и O (1) сложности. Что-то вроде этого:--25-->

if(i != number_of_last_element_in_list) //neighbors.size() - 1
 swap(neighbors[i], neighbor.back());
neighbors.pop_back();

конкретный пример (у вас есть вектор в узле, C++11 (!)):

//creation of nodes, as previously
constexpr unsigned int N = 3;
array<node,N> nodes; //or array<node, 3> nodes;
//creating edge (adding neighbors), in the constructor, or somewhere
nodes[0].neighbors = {&nodes[1]};
nodes[1].neighbors = {&nodes[0], &nodes[1]};
//adding runtime, i,j from user, eg. i = 2, j = 0
nodes[i].neighbors.push_back(&nodes[j]); //nodes[2].neighbors = {&nodes[0]};

я считаю, что это ясно. От 0 вы можете пойти к 1 С 1 to 0 и к себе, и (как в например.) от 2 to 0. Это направленный граф. Если вы хотите неориентироваться, вы должны добавить адреса соседей обоих узлов. Можно использовать числа вместо указателей. vector<unsigned int> на class node и отодвигая номера, ни адреса.


как мы знаем, что вам не нужно использовать указатели. Вот пример этого.

когда количество вершин может измениться, вы можете использовать вектор узлов (vector<node>) вместо массива, и просто изменение размера. Остальное остается неизменным. Например:

vector<node> nodes(n); //you have n nodes
nodes.emplace_back(); //you added new node, or .resize(n+1)
//here is place to runtime graph generate
//as previously, i,j from user, but now you have 'vector<unsigned int>' in node
nodes[i].neighbors.push_back(j);

а вы не могу стереть узел, это нарушения нумерации! Если вы хотите что-то стереть, вы должны использовать список (list<node*>) указателей. В противном случае вы должны сохранить несуществующие вершины. Здесь порядок имеет значение!


по линии nodes.emplace_back(); //adding node, безопасно с графиком без указателей. Если вы хотите использовать указатели, вы преимущественно не стоит изменить размер графика. Вы можете случайно изменить адрес некоторых узлов, при добавлении вершины, когда vector будет скопирован на новое место (из космоса).

один способ справиться с этим-использовать заповедник, хотя вы должны знать максимальный размер графа! Но на самом деле я призываю вас не использовать vector для сохранения вершин, когда вы используете указатели. Если вы не знаете реализации, более безопасным может быть управление собственной памятью (например, смарт-указатели. shared_ptr или просто new).

node* const graph = new node[size]; //<-- It is your graph.
//Here no address change accidentally.

используя vector as список смежности всегда штраф в размере! Нет никаких шансов измените адрес узла.


это может быть не очень общий подход, но именно так я обрабатываю список смежности в большинстве случаев. C++ имеет библиотеку STL, которая поддерживает структуру данных для связанного списка с именем list.

скажите, что у вас есть N узлы на графике создайте связанный список для каждого узла.

list graph[N];

теперь graph[i] представляют соседей узла i. Для каждого ребра i до j, do

graph[i].push_back(j);

самый лучший комфорт никакая регуляция указателей как недостаток сегментации ошибки.

дополнительные ссылки http://www.cplusplus.com/reference/list/list/


Я предлагаю вам добавить в структуру узла, список смежности И определите структуру графика как список узлов вместо списка списков смежности:)

struct node {
    int vertex;
    node* next;
    adjList m_neighbors;
};
struct graph{
    //List of nodes
};

Я бы рекомендовал более общий и простой подход к использованию вектора и пар: #включать # include

typedef std::pair<int, int> ii; /* the first int is for the data, and the second is for the weight of the Edge - Mostly usable for Dijkstra */
typedef std::vector<ii> vii;
typedef std::vector <vii> WeightedAdjList; /* Usable for Dijkstra -for example */
typedef std::vector<vi> AdjList; /*use this one for DFS/BFS */

или стиль псевдонима (>=C++11):

using ii = std::pair<int,int>;
using vii = std::vector<ii>;
using vi = std::vector<int>;
using WeightedAdjList = std::vector<vii>;
using AdjList = std::vector<vi>;

отсюда: использование вектора и пар (из ответа tejas)

для получения дополнительной информации вы можете обратиться к очень хорошей сводке topcoder: включение питания c++ с помощью STL


мой подход состоял бы в том, чтобы использовать хэш-карту для хранения списка узлов в графике

class Graph {
private:
  unordered_map<uint64_t, Node> nodeList;
  ...
}

карта принимает идентификатор узла как ключ, а сам узел как значение. Таким образом, вы можете искать узел на графике в постоянное время.

узел содержит список смежности, в данном случае как вектор c++11. Это также может быть связанный список, хотя для этого случая использования я не видел бы разницы в эффективности. Возможно, список был бы лучше, если бы вы хотели его сохранить как-то разобрались.

class Node{
    uint64_t id;     // Node ID
    vector<uint64_t> adjList;  
...
}

при таком подходе вы должны пройти через список смежности, а затем искать карту на ID, чтобы получить узел.

в качестве альтернативы вы можете иметь вектор указателей на сами соседние узлы. Это даст вам прямой доступ к соседним узлам, но тогда вы не сможете использовать карту, чтобы сохранить все свои узлы в графике, и вы потеряете возможность легко искать записи в своем графике.

Как вы можете видеть, существует много компромиссных решений, которые вы должны принять при реализации графика, все зависит от ваших вариантов использования.