构造随机网络并计算相关参数
使用C++手动构造一个随机网络并计算相关参数,然后使用python中的networkx进行绘制,随机网络为无向有权图,不具有实际的物理意义,但可以为构造实际的网络进行学习与参考。
- 使用C++11构造网络,节点数与边数可以指定,边随机生成,节点与边都有权
- 使用networkx绘制网络
构造方法
使用无向有权图表示网络,图使用邻接表表示,维护一个节点列表,每个节点后跟着一个链表记录边,同时也使用三元组记录边便于记录参数
RandmNetWork.h
//
// Created by lsl on 2022/3/3.
//
#ifndef RANDOM_Network_RANDOMNetwork_H
#define RANDOM_Network_RANDOMNetwork_H
#include "vector"
#include "random"
#include "map"
/**
* 随机生成无向网络,节点与边都有权,使用int表示,允许自环
* 并没有实际的物理意义,但生成方法是一样的
* 使用领接表表示(节点,边)集合
*/
class RandomNetwork {
/**
* 链表节点
*/
struct ArcNode {
int index; //节点
int weight;//边权重,表示链表首部节点a到该节点b的边a-b的权重
ArcNode *next;//下一个节点
};
/**
* 领接表顶点(链表首个节点集合)
* 还记录每个节点的统计信息
*/
struct VNode {
int index;//顶点(节点)
int weight;//节点权重
int degree;//节点度值
double clusteringCoefficient;//集聚系数
ArcNode *first;
};
public:
/**
* 边a-b
*/
struct ArcData {
int head;//a
int tail;//b
int weight;//a-b边权重
bool operator==(const ArcData data) const {
return head == data.head && tail == data.tail;
}
};
int nodeCount = 10;//节点数量,0-nodeCount
int arcCount = 10;//边数量,0-arcCount
int weightScope = 10;//权重范围,0-weightScope
//随机生成器
std::random_device e;
//权重随机器
std::uniform_int_distribution<unsigned> wu;
//节点随机器
std::uniform_int_distribution<unsigned> au;
//构造函数与析构
RandomNetwork(int nodeCount, int arcCount, int weightScope);
~RandomNetwork();
void Init();//初始化
void Display();//显示网络
std::vector<VNode> getNodeInfo();//获取节点信息
/**
* 一些统计函数
*/
//度分布:度值为k的节点占整个网络中节点书的比例<k.pk>,度:节点连边的数量
std::map<int, double> DegreeDistribution();
//计算集聚系数,集聚系数Ci=2ei/(ki(ki-1)),ei表示节点i的邻居之间的边数,ki表示节点i的度
void ClusteringCoefficient();
//计算度相关性,使用皮尔逊相关系数计算
double DegreeCorrelation();
//导出数据,文件txt
void ExportFile();
private:
std::vector<VNode> vset;//顶点表
std::vector<int> vsetVisited;//顶点访问标记数组
std::vector<ArcData> arcDataList;//三元组记录边的信息
void _CreateVNodeSet();//创建顶点集合
void _CreateRandomNetwork();//创建随机无向网络
void _InsertArc(ArcData arcData);//插入边
};
#endif //RANDOM_Network_RANDOMNetwork_H
RandmNetWork.cpp
//
// Created by lsl on 2022/3/3.
//
#include "RandomNetwork.h"
#include <random>
#include <fstream>
#include "iostream"
#include "map"
#include "algorithm"
#include <direct.h>
RandomNetwork::RandomNetwork(int nodeCount, int arcCount, int weightScope) {
this->nodeCount = nodeCount;
this->arcCount = arcCount;
this->weightScope = weightScope;
wu = std::uniform_int_distribution<unsigned>(0, weightScope - 1);
au = std::uniform_int_distribution<unsigned>(0, nodeCount - 1);
}
RandomNetwork::~RandomNetwork() {
//释放内存,new申请的内存必须释放,在堆内
std::cout << "释放内存" << std::endl;
for (VNode node: vset) {
ArcNode *arcNode = node.first;
ArcNode *tmp = arcNode;
while (tmp != nullptr) {
arcNode = tmp;
tmp = tmp->next;
delete arcNode;
}
}
}
/**
* 创建顶点集合
*/
void RandomNetwork::_CreateVNodeSet() {
for (int i = 0; i < nodeCount; i++) {
//节点数据
ArcNode *arcNode = new ArcNode();
arcNode->index = i;
//随机权重,简单随机
arcNode->weight = wu(e);
arcNode->next = nullptr;
//顶点
VNode node{};
node.index = i;
node.weight = arcNode->weight;
node.degree = 0;
node.clusteringCoefficient = -1;
node.first = arcNode;
//加入集合
vset.push_back(node);
}
}
/**
* 插入边
*/
void RandomNetwork::_InsertArc(RandomNetwork::ArcData arcData) {
//按照index顺序创建顶点表,所以可以直接随机访问
ArcNode *first = vset[arcData.head].first;//链表第一个节点
ArcNode *tmp = first->next;
//有边则不记录
while (tmp != nullptr) {
if (tmp->index == arcData.tail) {
return;
}
first = tmp;
tmp = tmp->next;
}
//记录边
ArcNode *arcNode = new ArcNode();
arcNode->index = arcData.tail;
arcNode->weight = arcData.weight;
arcNode->next = nullptr;
//插入边
first->next = arcNode;
//记录三元组,边的信息
if (std::find(arcDataList.begin(), arcDataList.end(), arcData) == arcDataList.end()) {
arcDataList.push_back(arcData);
}
//度值增加
if (arcData.head != arcData.tail) {
vset[arcData.head].degree++;
}
}
/**
* 创建随机无向网络
*/
void RandomNetwork::_CreateRandomNetwork() {
if (vset.empty()) {
std::cout << "需要创建顶点集合" << std::endl;
return;
}
for (int i = 0; i < arcCount; ++i) {
//随机生成边,简单随机
ArcData arcData{};
arcData.head = au(e);
arcData.tail = au(e);
arcData.weight = wu(e);
_InsertArc(arcData);
//需要注意会生成环,如果去掉环则去掉head==tail的数据即可
//插入边a-b,需要注意因为是无向图,所以还有边b-a
if (arcData.head != arcData.tail) {
ArcData another{};
another.head = arcData.tail;
another.tail = arcData.head;
another.weight = arcData.weight;
_InsertArc(another);
}
}
}
/**
* 初始化网络
*/
void RandomNetwork::Init() {
_CreateVNodeSet();//创建顶点集合
_CreateRandomNetwork();//创建随机网络
}
/**
* 显示网络
*/
void RandomNetwork::Display() {
for (VNode node: vset) {
ArcNode *arcNode = node.first;
while (arcNode != nullptr) {
std::cout << arcNode->index << "(" << arcNode->weight << ")->";
arcNode = arcNode->next;
}
std::cout << "null" << std::endl;
}
}
/**
* 获取节点信息,直接返回,ToDo 返回copy数据
* @return
*/
std::vector<RandomNetwork::VNode> RandomNetwork::getNodeInfo() {
return vset;
}
/**
* 统计函数
*/
/**
* 度分布
* @return 度值为k的节点占整个网络中节点书的比例<k.pk>
*/
std::map<int, double> RandomNetwork::DegreeDistribution() {
std::map<int, int> degreeMap;//<度值,度相同节点个数>
std::map<int, double> m;
for (VNode node: vset) {
if (degreeMap.find(node.degree) == degreeMap.end()) {
degreeMap.insert(std::make_pair(node.degree, 1));
} else {
degreeMap.find(node.degree)->second++;
}
}
for (VNode node: vset) {
if (m.find(node.degree) == m.end()) {
m.insert(std::make_pair(node.degree, (double) degreeMap.find(node.degree)->second / nodeCount));
}
}
return m;
}
/**
* 集聚系数Ci=2ei/(ki(ki-1)),ei表示节点i的邻居之间的边数,ki表示节点i的度
*/
void RandomNetwork::ClusteringCoefficient() {
for (auto &it : vset) {
//度为0或1不进行计算
if (it.degree == 0 || it.degree == 1)continue;
//所有邻居
std::vector<int> neighbors;
ArcNode *arcNode = it.first->next;
while (arcNode != nullptr) {
//需要注意,环不是邻居
if (arcNode->index != it.index) {
neighbors.push_back(arcNode->index);
}
arcNode = arcNode->next;
}
//计算邻居之间的边数,无向图,边会被重复计算,所以ei实际上就是2ei
int ei = 0;
if (neighbors.empty() || neighbors.size() < 2) {
ei = 0;//邻居节点至少有两个才行
} else {
for (ArcData arcData: arcDataList) {
//去掉环
if (arcData.head == arcData.tail)continue;
if (std::find(neighbors.begin(), neighbors.end(), arcData.head) != neighbors.end()
&& std::find(neighbors.begin(), neighbors.end(), arcData.tail) != neighbors.end()) {
ei++;
}
}
}
//计算集聚系数
double ci = (double) ei / it.degree / (it.degree - 1);
it.clusteringCoefficient = ci;
}
}
/**
* 计算度相关性,使用皮尔逊相关系数计算
* @return 度相关性
*/
double RandomNetwork::DegreeCorrelation() {
//将所有连边的两端的度值按大小排列,分为两个集合x,y,计算度相关性
std::vector<int> x;
std::vector<int> y;
std::vector<ArcData> arcList;
//不计算重复的边
ArcData check{};
for (auto &it:arcDataList) {
check.head = it.head;
check.tail = it.tail;
if (std::find(arcList.begin(), arcList.end(), check) != arcList.end()) {
continue;
}
check.head = it.tail;
check.tail = it.head;
if (std::find(arcList.begin(), arcList.end(), check) != arcList.end()) {
continue;
}
arcList.push_back(it);
int a = vset[it.head].degree;
int b = vset[it.tail].degree;
x.push_back(std::min(a, b));
y.push_back(std::max(a, b));
}
//计算协方差Cov(x,y)
double sum_multi = 0;
double sum_x = 0;
double sum_y = 0;
for (int i = 0; i < x.size(); i++) {
sum_multi += (x[i] * y[i]);
sum_x += x[i];
sum_y += y[i];
}
double e_xy = sum_multi / x.size();
double e_x = sum_x / x.size();
double e_y = sum_y / y.size();
double cov_xy = e_xy - (e_x * e_y);
//计算标准差
double p_x = 0;
double p_y = 0;
for (int i = 0; i < x.size(); i++) {
p_x += std::pow(x[i] - e_x, 2);
p_y += std::pow(y[i] - e_y, 2);
}
double b_x = std::sqrt(p_x / x.size());
double b_y = std::sqrt(p_y / y.size());
return cov_xy / b_x / b_y;
}
/**
* 导出数据到文件
*/
void RandomNetwork::ExportFile() {
std::ofstream outfile;
outfile.open("./nodes.txt", std::ios::out | std::ios::trunc);
//写入所有节点
outfile << "node" << std::endl;
for (auto node:vset) {
outfile << node.index << std::endl;
}
outfile.close();
//写入所有边
outfile.open("./edges.txt", std::ios::out | std::ios::trunc);
outfile << "head tail" << std::endl;
for (auto arc:arcDataList) {
outfile << arc.head << " " << arc.tail << std::endl;
}
outfile.close();
//写入度分布
outfile.open("./DegreeDistribution.txt", std::ios::out | std::ios::trunc);
outfile << "DegreeDistribution" << std::endl;
for (auto d: DegreeDistribution()) {
outfile << d.first << " " << d.second << std::endl;
}
outfile.close();
//写入集聚系数
outfile.open("./ClusteringCoefficient.txt", std::ios::out | std::ios::trunc);
outfile << "ClusteringCoefficient" << std::endl;
for (auto node: getNodeInfo()) {
outfile << node.index << " " << node.clusteringCoefficient << std::endl;
}
outfile.close();
}
main.cpp
#include <iostream>
#include "RandomNetwork.h"
#include "map"
using namespace std;
int main() {
cout << "输入节点数量>0(默认50):" << endl;
int nodeCount = 0;
cin >> nodeCount;
nodeCount = nodeCount == 0 ? 50 : nodeCount;
int edgeCount = 0;
cout << "输入边的数量>0(默认100):" << endl;
cin >> edgeCount;
edgeCount = edgeCount == 0 ? 100 : edgeCount;
//构造一个无向网络
RandomNetwork randomNetwork(nodeCount, edgeCount, 10);
randomNetwork.Init();
randomNetwork.Display();//节点(节点权重)->边(边权重)->边(边权重)...
//计算相关参数
std::cout << "网络度分布:度值:占据网络中节点的比例" << std::endl;
for (auto d: randomNetwork.DegreeDistribution()) {
std::cout << d.first << ":" << d.second << ",";
}
std::cout << std::endl;
std::cout << "集聚系数:节点:集聚系数(-1代表度值为0或1)" << std::endl;
randomNetwork.ClusteringCoefficient();
for (auto node: randomNetwork.getNodeInfo()) {
std::cout << node.index << ":" << node.clusteringCoefficient << ",";
}
std::cout << std::endl;
std::cout << "度相关性" << std::endl;
std::cout << randomNetwork.DegreeCorrelation() << std::endl;
//导出文件
randomNetwork.ExportFile();
cout<<"文件已导出";
system("pause");
return 0;
}
运行效果
直接运行random_graph.exe生成nodes、edges、DegreeDistribution、ClusteringCoefficient四个文件记录信息并使用networkx绘制,方法说明:
- Init():初始化网络
- Display():显示网络
- DegreeDistribution():网络度分布
- ClusteringCoefficient():集聚系数
- DegreeCorrelation():度相关性
- ExportFile():导出信息到文件
使用 cmake与make生成random_network.exe,之后调用draw.py读取文件绘制图形,需要networkx、matplotlib、pandas三个库依赖
draw.py
import networkx as nx
import matplotlib.pyplot as plt
import pandas as pd
nodes = pd.read_table("./nodes.txt")
edges = pd.read_table("./edges.txt")
deDis = pd.read_table("./DegreeDistribution.txt")
cluCoe = pd.read_table("./ClusteringCoefficient.txt")
G = nx.Graph()
# 添加节点
nodeList = []
for i in range(len(nodes)):
nodeList.append(nodes.iloc[i][0])
G.add_nodes_from(nodeList)
# 添加线
arcList = []
for i in range(len(edges)):
ns = edges.iloc[i][0]
edge = ns.split(' ', 2)
arcList.append((int(edge[0]), int(edge[1])))
G.add_edges_from(arcList)
# 绘制网络
plt.figure(figsize=(20, 20))
plt.subplot(2, 1, 1)
plt.title("network")
nx.draw(G, node_size=30, font_size=8)
# 度分布
de1 = []
de2 = []
for i in range(len(deDis)):
ns = deDis.iloc[i][0]
de = ns.split(' ', 2)
de1.append(int(de[0]))
de2.append(float(de[1]))
plt.subplot(2, 2, 3)
# 连通性
isolateNodes = list(nx.isolates(G))
isolateText = "isolate nodes:"
for i in range(len(isolateNodes)):
if i % 21 == 0: isolateText = isolateText + "\n";
isolateText = isolateText + str(isolateNodes[i]) + " "
plt.text(-0.2, 0.35, isolateText)
plt.title("degree-distribution")
plt.xlabel("degree")
plt.ylabel("k")
plt.bar(de1, de2, width=0.6)
# 集聚系数
cc1 = []
cc2 = []
for i in range(len(cluCoe)):
ns = cluCoe.iloc[i][0]
c = ns.split(' ', 2)
cc1.append(int(c[0]))
cc2.append(float(c[1]))
plt.subplot(2, 2, 4)
plt.title("clustering-coefficient")
plt.xlabel("node")
plt.ylabel("e")
plt.bar(cc1, cc2, width=0.6)
# 保存图片
plt.savefig("./ba.png")
plt.show()
效果图如下:
总结
在python中可以直接使用networkx快速生成一个随机网络并计算相关属性,本次作业使用手动计算但之后可以使用networkx进行更方便的处理