Привет всем 🙂 Сегодня я совершенствую свои навыки в теории графов и структурах данных. Я решил сделать небольшой проект на 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 ++ никогда не были моей сильной стороной. Прежде всего, это заботится о вершинах, на которые указывает вершина — но как насчет самой вершины? Как я могу отслеживать эту вершину? Когда я зацикливаюсь на массиве, мне бесполезно знать только то, на что указывают вершины, а не знать, на какие точки в их. Первый элемент в каждом списке, вероятно, должен быть этой вершиной, а затем элементы после него являются вершинами, на которые он указывает. Но как я могу получить доступ к этому первому элементу списка в моей основной программе? (извините, если это запутанно или запутанно, я бы с удовольствием перефразировал).
Я хотел бы иметь возможность перебрать этот список смежности, чтобы сделать некоторые интересные вещи с графиками. Например, чтобы реализовать некоторые алгоритмы теории графов (сортировки, кратчайшие пути и т. Д.) С использованием представления списка смежности.
(Также у меня возник вопрос о списке смежности. Что отличается от простого использования списка массивов? Почему я не могу просто иметь список с массивом для каждого элемента в списке?)
Вы можете использовать вектор в узле, как список смежности.
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) время. Ошибка. Для большинства алгоритмов порядок в списке смежности не важен. Таким образом, вы можете стереть любой элемент из вектора в O (1) время. Просто поменяйте местами с последним элементом, pop_back является O (1) сложность. Что-то вроде того:
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
в 0
и к себе, и (как, например,) из 2
в 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 или просто новый).
node* const graph = new node[size]; //<-- It is your graph.
//Here no address change accidentally.
С помощью vector
как список смежности всегда отлично! Нет возможности изменить адрес узла.
Это может быть не очень общий подход, но именно так я обрабатываю список смежности в большинстве случаев. C ++ имеет библиотеку STL, которая поддерживает структуру данных для связанного списка, названного как list
,
Скажи у тебя N
узлы на графике, создать связанный список для каждого узла.
list graph[N];
Сейчас graph[i]
представляют соседей узла i. Для каждого ребра от i до j сделать
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
};
Я бы порекомендовал более общий и простой подход использования вектора и пар:
#включают
#включают
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;
...
}
При таком подходе вы должны пройти через список смежности, а затем найти карту по идентификатору, чтобы получить узел.
В качестве альтернативы, вы можете иметь вектор указателей на соседние узлы. Это даст вам прямой доступ к соседним узлам, но тогда вы не сможете использовать карту, чтобы сохранить все ваши узлы в графе, и вы потеряете возможность легко искать записи в вашем графе.
Как вы можете видеть, существует множество компромиссных решений, которые вы должны принять при реализации графика, все зависит от ваших вариантов использования.