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

时间:2014-03-01 21:31:35

标签: c++ graph adjacency-list

大家好:)今天我正在提炼我的图论和数据结构技能。我决定用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 ++中的指针和结构从来都不是我的强项。首先,这会处理顶点指向的顶点 - 但顶点本身呢?如何跟踪该顶点?当我遍历数组时,只知道指向哪个顶点,而不是知道它们的哪些点,这对我没有好处。每个列表中的第一个元素应该是该顶点,然后是之后的元素是它指向的顶点。但是,如何在主程序中访问列表的第一个元素? (对不起,如果这是令人费解或混乱,我很乐意改写)。

我希望能够遍历这个邻接列表,用图表做一些很酷的事情。例如,使用邻接列表表示来实现一些图论算法(排序,最短路径等)。

(另外,我对邻接列表有疑问。与使用数组列表有什么不同?为什么我不能在列表中的每个元素上都有一个包含数组的列表?)

5 个答案:

答案 0 :(得分:15)

您可以在节点中使用vector作为邻接列表。

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

如果图形在编译时是已知的,则可以使用array,但它有点“难度”。如果您只知道图形的大小(在编译时),您可以执行类似的操作。

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

要添加邻居,您可以执行类似的操作(不要忘记从零开始编号):

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

当然,你可以做无指针邻接列表并在表格“上方”工作。比你在节点中有vector<int>而你推的邻居数量。通过图表的两种表示,您可以实现所有使用邻接列表的算法。

最后,我可以补充一下。有些使用list而不是向量,因为删除时间为 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转到{{1} }。这是有向图。如果您想要无向,则应该向两个节点添加邻居的地址。您可以使用数字而不是指针。 0中的vector<unsigned int>并推回数字,没有地址。


众所周知,您不需要使用指针。这也是一个例子。

当顶点数量可能发生变化时,您可以使用节点矢量(class node)代替数组,而只使用resizing。其余的保持不变。例如:

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被复制到新位置(空间不足)时,您可能会在添加顶点时意外更改某些节点的地址。

处理它的一种方法是使用reserve,尽管你必须知道图的最大尺寸!但事实上,我鼓励你在使用指针时不要使用vector来保持顶点。如果您不了解实现,那么更安全的可能是自我管理(智能指针,例如。shared_ptrnew)。

vector

node* const graph = new node[size]; //<-- It is your graph. //Here no address change accidentally. 用作邻接列表始终正常!没有机会改变节点的地址。

答案 1 :(得分:3)

这可能不是一般的方法,但这就是我在大多数情况下如何处理邻接列表。 C ++具有STL库,它支持名为list的链表的数据结构。

假设图中有N个节点,为每个节点创建一个链表。

list graph[N];

现在graph[i]代表节点i的邻居。对于每个边缘i到j,请执行

graph[i].push_back(j);

最好的安慰是不处理指针,以便分割错误。

有关详情,请参阅http://www.cplusplus.com/reference/list/list/

答案 2 :(得分:1)

我建议您在节点结构中添加邻接列表 并将图结构定义为节点列表而不是邻接列表列表:)

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

答案 3 :(得分:0)

我会建议使用矢量和对的更通用和简单的方法:     #包括     #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 */

或别名样式(&gt; = 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>;

从这里: using vector and pairs (from tejas's answer)

有关其他信息,您可以参考topcoder的一个非常好的摘要: Power up c++ with STL

答案 4 :(得分:0)

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

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

映射将节点ID作为键,将节点本身作为值。这样,您可以在恒定时间内搜索图中的节点。

该节点包含邻接列表,在本例中为c ++ 11向量。它也可能是一个链表,虽然对于这个用例我不会看到效率上的差异。如果您希望以某种方式对其进行排序,列表会更好。

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

使用这种方法,您必须通过邻接列表,然后在ID上搜索地图以获取节点。

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

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