邻接表在c++中的实现
Adjacency list implementation in C++
我正在寻找c++中图形的简洁精确的邻接表表示。我的节点就是节点id。我是这样做的。只是想知道专家们是怎么想的。有没有更好的办法?
这是类的实现(没什么特别的,现在不关心公共/私有方法)
#include <iostream>
#include <vector>
#include <fstream>
#include <sstream>
using namespace std;
class adjList {
public:
int head;
vector<int> listOfNodes;
void print();
};
void adjList :: print() {
for (int i=0; i<listOfNodes.size(); ++i) {
cout << head << "-->" << listOfNodes.at(i) << endl;
}
}
class graph {
public:
vector<adjList> list;
void print();
};
void graph :: print() {
for (int i=0; i<list.size(); ++i) {
list.at(i).print();
cout << endl;
}
}
main函数逐行解析输入文件。其中每行解释如下:
<source_node> <node1_connected_to_source_node> <node2_connected_to_source_node <node3_connected_to_source_node> <...>
正文:
int main()
{
fstream file("graph.txt", ios::in);
string line;
graph g;
while (getline(file, line)) {
int source;
stringstream str(line);
str >> source;
int node2;
adjList l;
l.head = source;
while (str >> node2) {
l.listOfNodes.push_back(node2);
}
g.list.push_back(l);
}
file.close();
g.print();
getchar();
return 0;
}
我知道我应该在adjList类中添加adddge()函数,而不是直接从main()中修改它的变量,但是,现在我只是想知道最好的结构。
编辑:我的方法有一个缺点。对于具有大量节点的复杂图,node将确实是一个结构/类,在这种情况下,我将通过存储整个对象来复制值。在这种情况下,我想我应该使用指针。例如,对于无向图,我将在adjList中存储节点对象的副本(节点1和2之间的连接意味着1的邻接表将有2,反之亦然)。我可以通过在adjList中存储节点对象的指针而不是整个对象来避免这个问题。检查从这种方法中获益的dfs实现。我需要确保每个节点只被访问一次。拥有同一节点的多个副本将使我的生活变得更加困难。没有?
在这种情况下,我的类定义将改变如下:
#include <iostream>
#include <vector>
#include <fstream>
#include <sstream>
#include <map>
using namespace std;
class node {
public:
node() {}
node(int id, bool _dirty): node_id(id), dirty(_dirty) {}
int node_id;
bool dirty;
};
class adjList {
public:
node *head;
vector<node*> listOfNodes;
void print();
~adjList() { delete head;}
};
void adjList :: print() {
for (int i=0; i<listOfNodes.size(); ++i) {
cout << head->node_id << "-->" << listOfNodes.at(i)->node_id << endl;
}
}
class graph {
public:
vector<adjList> list;
void print();
void dfs(node *startNode);
};
void graph::dfs(node *startNode) {
startNode->dirty = true;
for(int i=0; i<list.size(); ++i) {
node *stNode = list.at(i).head;
if (stNode->node_id != startNode->node_id) { continue;}
for (int j=0; j<list.at(i).listOfNodes.size(); ++j) {
if (!list.at(i).listOfNodes.at(j)->dirty) {
dfs(list.at(i).listOfNodes.at(j));
}
}
}
cout << "Node: "<<startNode->node_id << endl;
}
void graph :: print() {
for (int i=0; i<list.size(); ++i) {
list.at(i).print();
cout << endl;
}
}
这就是我如何实现main()函数。我使用地图来避免重复的对象。只在之前没有定义的情况下创建一个新对象。根据对象的id检查对象是否存在。
int main()
{
fstream file("graph.txt", ios::in);
string line;
graph g;
node *startNode;
map<int, node*> nodeMap;
while (getline(file, line)) {
int source;
stringstream str(line);
str >> source;
int node2;
node *sourceNode;
// Create new node only if a node does not already exist
if (nodeMap.find(source) == nodeMap.end()) {
sourceNode = new node(source, false);
nodeMap[source] = sourceNode;
} else {
sourceNode = nodeMap[source];
}
adjList l;
l.head = sourceNode;
nodeMap[source] = sourceNode;
while (str >> node2) {
// Create new node only if a node does not already exist
node *secNode;
if (nodeMap.find(node2) == nodeMap.end()) {
secNode = new node(node2, false);
nodeMap[node2] = secNode;
} else {
secNode = nodeMap[node2];
}
l.listOfNodes.push_back(secNode);
}
g.list.push_back(l);
startNode = sourceNode;
}
file.close();
g.print();
g.dfs(startNode);
getchar();
return 0;
}
第二个编辑在Ulrich Eckhardt建议将邻接表放在节点类之后,我认为这是一个更好的数据结构来存储图并执行dfs(), dijkstra()之类的操作。请注意邻接表是在节点类中合并的。
#include <iostream>
#include <vector>
#include <fstream>
#include <sstream>
#include <map>
using namespace std;
class node {
public:
node() {
}
node(int id, bool _dirty): node_id(id), dirty(_dirty) {
//cout << "In overloaded constn";
}
int node_id;
bool dirty;
vector<node*> listOfNodes;
};
class graph {
public:
vector<node*> myGraph;
void dfs(node* startNode);
};
void graph::dfs(node* startNode) {
startNode->dirty = true;
for (int j=0; j<startNode->listOfNodes.size(); ++j) {
if (!startNode->listOfNodes.at(j)->dirty) {
dfs(startNode->listOfNodes.at(j));
}
}
cout << "Node: "<<startNode->node_id << endl;
}
我们能做得更好吗?
有一些地方可以改进,但总的来说,您的方法是合理的。注:
- 您使用
int
作为容器的索引,这将给您一些编译器的警告,因为容器的大小可能超过int
可表示的大小。请使用size_t
。 - 将
for (int i=0; i<list.size(); ++i)
重写为for(size_t i=0, size=list.size(); i!=size; ++i)
。使用!=
代替<
可以用于迭代器。读取和存储一次大小使调试更容易,甚至可能更高效。 - 在要打印的循环中,有
list.at(i).print();
。list.at(i)
将验证索引是否有效,如果无效则引发异常。在这个非常简单的例子中,我确信索引是有效的,所以使用list[i]
会更快。此外,它隐式地记录索引是有效的,而不是您期望它无效。 -
print()
函数应保持不变。 我不明白 - 如果你不关心节点级的封装(这是合理的!),你也可以把它变成一个结构体,这样可以节省一些输入。
- 存储指针而不是索引是棘手的,但可以提高速度。问题是,为了进行读取,你可能需要一个指针指向一个还不存在的顶点。有一种方法可以在不使用额外存储的情况下做到这一点,它需要首先将索引存储在指针值中(使用reinterpret_cast),然后在读取之后,对数据进行第二次传递,将这些值调整为实际地址。当然,你也可以使用第二遍来验证你没有任何边缘到根本不存在的顶点(这是
at(i)
函数变得有用的地方),所以这第二遍来验证一些保证是一件好事。
int head
是什么。这是节点的某种ID吗?而不是ID简单的索引在graph::list
?如果它是索引,则可以使用元素的地址减去第一个元素的地址按需计算,因此不需要冗余存储它。此外,考虑在读取时验证索引,这样你就不会有任何边指向不存在的顶点。对于显式请求,这里有一个如何在指针中存储索引的示例:
// read file
for(...) {
size_t id = read_id_from_file();
node* node_ptr = reinterpret_cast<node*>(id);
adjacency_list.push_back(node_ptr);
}
/* Note that at this point, you do have node* that don't contain
valid addresses but just the IDs of the nodes they should finally
point to, so you must not use these pointers! */
// make another pass over all nodes after reading the file
for(size_t i=0, size=adjacency_list.size(); i!=size; ++i) {
// read ID from adjacency list
node* node_ptr = adjacency_list[i];
size_t id = reinterpret_cast<size_t>(node_ptr);
// convert ID to actual address
node_ptr = lookup_node_by_id(id);
if(!node_ptr)
throw std::runtime_error("unknown node ID in adjacency list");
// store actual node address in adjacency list
adjacency_list[i] = node_ptr;
}
我很确定这在一般情况下是有效的,尽管我不能100%确定这是否保证有效,这就是为什么我不愿意在这里发布这篇文章。然而,我希望这也能清楚地说明为什么我问"头"到底是什么。如果它实际上只是容器中的索引,则几乎不需要它,无论是在文件内部还是在内存中。如果它是从文件中检索到的某个节点的某种名称或标识符,那么您绝对需要它,但是您不能将它用作索引,那里的值也可以以1或1000开始它们的id,您应该捕获并处理它们而不会崩溃!
- 如果没有malloc,链表实现将失败
- 如何在c++中实现处理器调度模拟器
- 如何在c++中使用引用实现类似python的行为
- 实现无开销push_back的最佳方法是什么
- 使用简单类型列表实现的指数编译时间.为什么
- 如何在BST的这个简单递归实现中消除警告
- 实现一个在集合上迭代的模板函数
- 我应该实现右值推送功能吗?我应该使用std::move吗
- 如何正确实现和访问运算符的各种自定义枚举器
- C++Union/Struct位域的实现和可移植性
- 这个极客对极客的trie实现是否存在内存泄漏问题
- 在c++中实现LinkedList时,应出现未处理的错误
- 为左值和右值的包装器实现C++范围
- 使用模板进行堆栈实现; "name followed by :: must be a class or namespace"
- 使用GSoap实现ONVIF
- 在用于格式4的arm模拟器中实现功能时的一个问题
- 用于AVX的ln(x)的实现,m256
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 在C++中,如何在类和函数(可能是模板化的)的头中编写完整的实现
- std::random_device是如何实现的