大家好:)今天我正在提炼我的图论和数据结构技能。我决定用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 ++中的指针和结构从来都不是我的强项。首先,这会处理顶点指向的顶点 - 但顶点本身呢?如何跟踪该顶点?当我遍历数组时,只知道指向哪个顶点,而不是知道到它们的哪些点,这对我没有好处。每个列表中的第一个元素应该是该顶点,然后是之后的元素是它指向的顶点。但是,如何在主程序中访问列表的第一个元素? (对不起,如果这是令人费解或混乱,我很乐意改写)。
我希望能够遍历这个邻接列表,用图表做一些很酷的事情。例如,使用邻接列表表示来实现一些图论算法(排序,最短路径等)。
(另外,我对邻接列表有疑问。与使用数组列表有什么不同?为什么我不能在列表中的每个元素上都有一个包含数组的列表?)
答案 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_ptr或new)。
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);
最好的安慰是不处理指针,以便分割错误。
答案 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上搜索地图以获取节点。
作为替代方案,您可以拥有指向邻居节点本身的指针向量。这将使您可以直接访问邻居节点,但是您无法使用地图将所有节点保留在图表中,并且您将无法在图表中轻松搜索条目。
正如您所看到的,在实施图表时您必须做出许多权衡决定,这些决定都取决于您的使用案例。