繁体   English   中英

在C ++中为有向图创建邻接列表

[英]Making an adjacency list in C++ for a directed graph

各位大家好:)今天我在图论和数据结构方面提炼技巧。 我决定用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 这是有向图。 如果您想要无向,则应该向两个节点添加邻居的地址。 您可以使用数字而不是指针。 class node vector<unsigned int>并推回数字,没有地址。


我们知道,您不需要使用指针。 这也是一个例子。

当顶点数量可能发生变化时,您可以使用节点vector<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( list<node*> )。 否则,您必须保留不存在的顶点。 在这里,订单很重要!


关于行nodes.emplace_back(); //adding node nodes.emplace_back(); //adding node ,没有指针的图表是安全的。 如果要使用指针,则主要不应更改图形的大小。 vector将被复制到新位置(空间外)时,您可能会意外地更改某些节点的地址,同时添加顶点。

处理它的一种方法是使用reserve ,尽管你必须知道图形的最大尺寸! 但事实上,我鼓励你在使用指针时不要使用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
};

我建议使用vector和pair的更一般和简单的方法:#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上搜索地图以获取节点。

作为替代方案,您可以拥有指向邻居节点本身的指针向量。 这将使您可以直接访问邻居节点,但是您无法使用地图将所有节点保留在图表中,并且您将无法在图表中轻松搜索条目。

正如您所看到的,在实现图表时您必须做出许多权衡决定,这些决定都取决于您的使用案例。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM