在C++中为有向图制作邻接列表

Making an adjacency list in C++ for a directed graph

本文关键字:列表 有向图 C++      更新时间:2023-10-16

大家好:) 今天,我正在完善我在图论和数据结构方面的技能。 我决定在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,从10和它自己,以及(如例如)从20。它是有向图。如果你想要无定向,你应该向两个节点添加邻居的地址。 您可以使用数字代替指针。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
};

我会推荐使用向量和对的更通用和更简单的方法: #include #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的一个非常好的摘要: 使用 STL 启动 C++

我的方法是使用哈希映射来存储图形中的节点列表

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

映射将节点 ID 作为键,将节点本身作为值。通过这种方式,您可以在恒定时间内在图形中搜索节点。

节点包含邻接列表,在本例中为 c++11 向量。它也可以是链表,尽管对于此用例,我看不到效率的差异。如果您想以某种方式对其进行排序,也许列表会更好。

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

使用此方法,您必须浏览邻接列表,然后在 ID 上搜索映射以获取节点。

作为替代方案,您可以有一个指向邻居节点本身的指针向量。这将使您能够直接访问邻居节点,但是您不能使用映射将所有节点保留在图形中,并且您将失去在图形中轻松搜索条目的可能性。

如您所见,在实现图形时,您必须做出很多权衡决策,这完全取决于您的用例。