CimNet 使用说明
如果你之前编写过 C 语言,但是从未接触过 C++ 语言,或者不了解 C++11 标准的新特性,可以跳转到章节“关于 C++ 语言”快速了解 CimNet 使用的语言特性。
文件结构
CimNet 工具包含于 cimnet
文件夹内,由以下文件组成:
文件 |
内容概述 |
---|---|
|
基础数据类型 |
|
网络异常类 |
|
通用无向/有向网络类 |
|
已实现的常用网络结构 |
|
MT随机数生成 |
一般情况下,你只需要引用 cimnet/network.h
这个头文件,就可以使用默认的基础数据类型、网络异常类和有向/无向通用网络类。已实现的常用网络结构全部继承于通用无向网络,网络的节点编号类型为整型。
如果你想使用优秀的随机数生成器,可以引用 cimnet/random.h
,也可以使用 C++ 标准库中的随机数函数。
使用网络
1. 创建网络
你可以用下面的语句创建一个不含任何点边的空的无向网络:
Network<> net;
这个网络用 Id
类型作为节点编号的标识(见 CimNet 接口的基本数据类型)。每个节点的编号应该互不相同。创建一个空的有向网络:
DirectedNetwork<> net;
你也可以指定自己节点类型:
Network<std::string> net;
这样一来,在网络 net 中你可以使用 std::string 来唯一标识一个节点。
2. 编辑网络结构
你可以向网络中添加节点:
net.add_node(1);
网络中就增加了一个以 1 为编号的节点。如果你指定了节点编号类型为 std::string ,也可以用以下方式添加节点:
net.add_node("A");
你可以使用 has_node(id)
方法判断网络中是否有以 id 为编号的节点,以上面的网络为例,下列语句:
std::cout << net.has_node("A") << std::endl;
std::cout << net.has_node("Not existed") << std::endl;
会打印
true
false
两个节点间的连接关系称为边。向网络中添加一条边:
net.add_edge(1, 2);
如果网络 net 为无向网络,可以使用 has_edge(id1, id2)
方法判断网络中是否存在边, id1 和 id2 的位置可以调换。你也可以使用 is_neighbor(id1, id2)
判断网络中 id1 与 id2 是否为邻居节点,它的作用和 has_edge
是一样的。
如果网络 net 为有向网络,可以使用 has_successor(id1, id2)
判断节点 id1 是否存在后继节点 id2 ,使用 has_predecessor(id1, id2)
判断节点 id1 是否存在前序节点 id2 。有向网络也有 has_edge
方法,它和 has_successor
方法是等效的。有向网络的 is_neighbor(id1, id2)
方法在 id1 和 id2 之间存在连边(无论是 id1 指向 id2 或 id2 指向 id1 )时返回 true 。如果有向网络 net 中存在一条以节点 1 指向节点 2 的边,下列表达式:
net.has_successor(1, 2)
net.has_predecessor(2, 1)
net.has_edge(1, 2)
net.is_neighbor(1, 2)
net.is_neighbor(2, 1)
的值均为 true 。
3. 存储节点/边数据
在存储数据前需要在模板类处指定存储的数据类型。我们定义如下两种类型:
typedef std::string NodeDescribe;
typedef struct {
int amount;
double weight;
} EdgeDetail;
并且以这种方式声明网络,并添加一条边:
Network<Id, NodeDescribe, EdgeDetail> net;
net.add_edge(1, 2);
网络中便有了两个点和一条边。接下来你可以这样在网络的节点中添加数据:
net.node(1) = "First node";
net[2] = "Second node;
上面两个语句都能用来添加数据。第一条语句使用 node(id)
方法返回了节点 id 存储的引用,这使得你可以通过引用修改内部存储。网络类也提供了下标的方式返回节点引用(即第二条语句所示),这使得你可以更方便地存取网络节点的数据。在网络的边上添加数据也可以通过类似的方式:
net.edge(1, 2) = {3, 3.14};
net(1, 2) = {3, 3.14};
同理,第一条语句使用调用函数的形式访问边数据的引用,第二条语句是一种更简便的方式——它重载了这个类的括号操作符。当然,由于 edge(id1, id2)
方法返回的是 EdgeDetail 结构体的引用,你可以使用 .
直接修改内部成员:
net.edge(1, 2).amount = 4;
net(1, 2).amount = 4;
点数据和边数据都能存储其它任意的你定义过的类的对象,但是每个点之间或每条边之间存储的类型要一致。
在添加节点和边的方法里也可以直接添加数据:
net.add_node(1, "First node");
net.add_node(2, "Second node");
net.add_edge(1, 2, {3, 3.14});
4. 网络拷贝和转换
CimNet 提供拷贝构造器完成网络的拷贝操作。你可以将一个网络及其内部数据完全复制给另一个网络,此后前一个网络的修改不影响拷贝后的网络数据。只需要将原始网络作为参数传入新网络的初始化参数列表中即可。
Network<Id, NodeDescribe, EdgeDetail> net;
// Add nodes and edges to net
Network<Id, NodeDescribe, EdgeDetail> net_copy(net);
DirectedNetwork<Id, NodeDescribe, EdgeDetail> di_net_copy(net);
需要注意拷贝网络的模板参数需要保持一致,它的类既可以是 Network
也可以是 DirectedNetwork
。如果原网络是无向网络且新网络是有向网络,所有无向边会转化成两条有向且指向相反的连边。如果将有向网络拷贝为无向网络,所有的有向边失去方向,变为无向边;双向边则会变为一条无向边。
5. 网络数据和遍历
在无向网络中使用 degree(id)
方法可以获知节点 id 的度。在有向网络中, in_degree(id)
返回节点 id 的入度, out_degree(id)
返回节点 id 的出度, degree(id)
返回了入度和出度的和。
number_of_nodes()
方法会返回网络的节点数量, number_of_edges()
方法会返回网络边的数量。 total_degree()
方法会返回网络的总度数,它从数值上等于网络总边数的两倍。
你也可以直接使用 std::cout 输出流打印网络信息:
std::cout << net << std::endl;
它会简要打印网络的节点数、边数和总度数信息。
调用 nodes()
方法可以获取网络中所有的节点编号( std::vector<NodeId> ), edges()
方法可以获取网络中所有的边,它是一个点对的集合容器( std::unordered_set<std::pair<NodeId, NodeId>> )。 neighbors(id)
返回节点 id 的所有邻居( std::vector<NodeId> )。可以参考下面的方式遍历网络和邻居的节点编号:
for (auto &node : net.nodes())
// Visit node
for (auto &edge : net.edges())
// Visit edge.first and edge.second
for (auto &neighbor : net.neighbors())
// Visit neighbor
其中由于边 edge 是 std::pair<NodeId, NodeId> 类型, C++11 中需要用 edge.first 和 edge.second 访问一条边中的两个节点编号。对于无向网络而言, edge.first 是一条边中较小的一个节点编号(数值较小或字符串字母序靠前的);有向网络中 edge.first 是前序节点, edge.second 是后继节点。
(如果你支持 C++17 及以上的编译环境,可以尝试将循环替换为 for (auto &[i, j] : net.edges())
,其中 i 和 j 等效于 edge.first 和 edge.second 。)
另外,对于无向网络而言, neighbors(id)
返回了节点 id 所有相邻节点编号。对于有向网络而言, neighbors(id)
返回了所有与节点 id 有关联(无论方向)的节点编号。有向网络还提供了 predecessors(id)
方法用来返回节点 id 的所有前序节点编号, successors()
方法用来返回节点 id 的所有后继节点编号,它们的遍历方式与 neighbors(id)
类似。
6. 已实现的网络结构
目前所有已实现的网络结构都是模板类,且节点编号都是 Id
类型的——所以模板只接受两个模板参数, NodeData 和 EdgeData ,它们默认都是 None
。这些网络的用法和通用网络类型基本一致,只是初始化时需要传入指定的参数。这里给出一个较为完整的程序实例:构建一个包含10个节点的规则网络,这个网络的每个节点都和周围6个邻居连边(即,每个节点向顺时针方向的3个邻居添加连边)。最后我们打印网络信息和一号节点的邻居节点编号。
1#include "cimnet/network.h"
2
3int main(void) {
4 RegularNetwork<> net(10, 3);
5 std::cout << net << std::endl;
6 std::cout << "Neighbors of node 1: ";
7 for (auto &n : net.neighbors(1))
8 std::cout << n << " ";
9 std::cout << std::endl;
10 return 0;
11}
这段代码的第1行引用了网络结构的头文件。第4行用模板类定义了规则网络 net ,第7行遍历了网络中1号节点的邻居,变量 n 的值为每次遍历到的一个邻居节点的编号。