实验题目:生成树、环路空间、断集空间的求解
实验目的:
1、掌握无向连通图生成树的求解方法;
2、掌握基本回路系统和环路空间的求解方法;
3、掌握基本割集系统和断集空间的求解方法;
4、了解生成树、环路空间和断集空间的实际应用。
实验要求:
1、给定一无向简单连通图的相邻矩阵(例如:)。
2、输出此图的关联矩阵M。
3、求此图所有生成树的个数。
4、输出其中任意一颗生成树的相邻矩阵(默认第i行对应顶点vi)和关联矩阵(默认第i行对应顶点vi,第j列对应边ej)。
5、求此生成树对应的基本回路系统(输出形式如:{e1e4e3,e2e5e3})。
6、求此生成树对应的基本割集系统(输出形式如:{{e1,e4},{e2,e5},{e3,e4,e5}})。
*加分题:
7、求此生成树对应的环路空间(输出形式如:{,e1e4e3,e2e5e3,e1e4e5e2})。
8、求此生成树对应的断集空间(输出形式如:{,{e1,e4},{e2,e5},{e3,e4,e5},{e1,e2,e4,e5},{e1,e3,e5},{e2,e3,e4},{e1,e2,e3}})。
相关思路
- 求生成树的个数:这里借助关联矩阵M先得到 n - 1 阶行列式,然后根据该行列式是否满秩,若满秩,则相应的边构成生成树;
- 基本回路系统:对于一个特定的生成树,每一个弦都对应一个基本回路,取一个弦加入树枝集合,然后在这个集合中找出对应的回路集合;
- 基本割集系统:对于一个特定的生成树,每个树枝都对应一个基本割集,取一个树枝加入弦集合,去掉这个集合中所有的边之后的图必然是不连通的,但是,割集需要满足极小性,所以还需要根据弦添加后图是否连通去除冗余的弦,如果加入这个弦之后图仍不连通,说明这个弦就是冗余的;
- 环路空间:环路系统中的回路彼此环合得到,包括空集,对于两两环合和三个回路的情况比较容易处理,若回路数量多于3个,先将要进行环合的回路序列选出,然后将环合结果加入空间;
- 断集空间:割集系统中的割集彼此环合得到,包括空集,处理方式同上;
模拟代码
// 1、给定一无向简单连通图的相邻矩阵(例如:)。
// 2、输出此图的关联矩阵M。
// 3、求此图所有生成树的个数。
// 4、输出其中任意一颗生成树的相邻矩阵(默认第i行对应顶点vi)和关联矩阵(默认第i行对应顶点vi,第j列对应边ej)。
// 5、求此生成树对应的基本回路系统(输出形式如:{e1e4e3,e2e5e3})。
// 基本回路必然包含加入的那个弦
// 所以,从弦的一个顶点出发,经过生成树上的边,如果能够回到另一个顶点
// 这条路径就是基本回路
// 因为输出要求用边表示回路,这里每次每次存储一条边,如果最后能回到出发的边,就把这条回路加入基本回路系统
// 如果不能,就回退,这里每个弦只可能对应一个回路,所以一旦找到就返回。
// DfS函数的设想:
// 参数:
// 边路径栈,记录每一条边
// 压缩版的关联矩阵(因为输出的边的序号是初始关联矩阵中的序号,所以这里的压缩关联矩阵也是初始矩阵的)
// 初始起点(即终点)
// 当前起点
// 边是否经过的数组
// 存储基本回路系统的数组,找到回路后就把路径存入
// 树枝集合
// 返回值:为bool型,如果找到了,就返回TRUE
// 每次遍历树枝集合,寻找没有经过的边,如果没有找到,说明上一次走错了,回退
// 如果找到了,判断是否已经构成回路,如果没有,继续递归,如果找到了,返回
// 6、求此生成树对应的基本割集系统(输出形式如:{{e1,e4},{e2,e5},{e3,e4,e5}})。
// *加分题:
// 7、求此生成树对应的环路空间(输出形式如:{,e1e4e3,e2e5e3,e1e4e5e2})。
// 8、求此生成树对应的断集空间(输出形式如:{,{e1,e4},{e2,e5},{e3,e4,e5},{e1,e2,e4,e5},{e1,e3,e5},{e2,e3,e4},{e1,e2,e3}})。
// 对于环路空间和断集空间,关键是实现环合运算,这里不再考虑环路中边的顺序问题,把每个环路当成一个集合即可
#include <bits/stdc++.h>
using namespace std;
int n = 0; // 顶点数
int m = 0; // 边数
// 传入的参数为行列式的阶数和行列式数组,返回值为矩阵的秩
int rankOfDeterminant(int n, vector<vector<int> > matrix)
{
// 求二进制矩阵的秩,即消元,最后看斜对角线上有几个1
int row = 0;
for(int col=0; col < n && row < n; col++, row++) // 从每一列开始,将每一列消到只有 1 个 1 或者全 0
{
int i = 0;
for(i = row; i < n; ++i) // 寻找这一列第一个非 0 的行
{
if(matrix[i][col] != 0)
break;
}
if(n == i)
--row;
else
{
swap(matrix[row], matrix[i]);
for(int k = i+1; k < n; k++)
{
if(0 != matrix[k][col])
{
for(int j = col; j < n; j++)
{
matrix[k][j] ^= matrix[row][j];// 用第 r 行的 1 消除这一列上其余全部行的 1
}
}
}
}
}
return row;
}
// 传入的参数依次为待选序列的起始值j,还要选取的序列数,所有序列数组,当前序列数组,可供选择的数目m
// 类似m选n的所有序列问题
void selectSequence(int j, int num, vector<vector<int> >& sequences, vector<int> sequence, int tm)
{
if (num == 0) // 选好一个序列
{
sequences.push_back(sequence);
return;
}
else if (j + num > tm) // 已经不能选取一个 n - 1 序列了
{
return;
}
else
{
// 选择j
sequence.push_back(j); // 这里的j是边的序号
selectSequence(j + 1, num - 1, sequences, sequence, tm);
// 不选j
sequence.pop_back();
selectSequence(j + 1, num, sequences, sequence, tm);
}
}
// 根据相邻矩阵得到关联矩阵
void getIncidenceMatrix(vector<vector<int> > &A, vector<vector<int> > &M)
{
// 先算出边数,然后给边编号,因为是简单图,所以每两个顶点之间只有一个边,遍历下三角即可
// 行列式的是通过 m 列选 n - 1列生成的
int t = 0;
for (int i = 1; i < n; ++i)
{
for (int j = 0; j < i; ++j)
{
if (A[i][j] == 1)
{
M[i][t] = 1;
M[j][t] = 1;
t++;
}
}
}
}
void printMatrix(vector<vector<int> > &M)
{
for (int i = 0; i < int(M.size()); ++i)
{
printf("%d", M[i][0]);
for (int j = 1; j < int(M[0].size()); ++j)
{
printf(" %d", M[i][j]);
}
printf("\n");
}
}
int numOfSpanningTree(vector<vector<int> > & M, vector<vector<int> > &sequences, unordered_set<int>& store)
{
// 去掉关联矩阵第一行,依次排查所有的 n - 1 阶子方阵,行列式非零的即为生成树
int ret = 0;
for (int t = 0; t < int(sequences.size()); ++t)
{
vector<vector<int> > a(n - 1, vector<int>(n - 1, 0));
for (int i = 1; i < n; ++i)
{
for (int j = 0; j < n - 1; ++j)
{
a[i - 1][j] = M[i][sequences[t][j]];
}
}
if (rankOfDeterminant(n - 1, a) == n - 1)
{
ret++;
store.insert(t); // 记录对应生成树的n - 1阶行列式所在索引号
}
}
return ret;
}
void branchSet(vector<int>& sequence, unordered_set<int>& branches)
{
// 求树枝的集合
for (int i = 0; i < int(sequence.size()); ++i)
{
branches.insert(sequence[i]);
}
}
void stringSet(unordered_set<int> &strings, unordered_set<int>& branches)
{
// 求弦的集合
for (int i = 0; i < m; ++i)
{
if (branches.count(i) == 0)
{
strings.insert(i);
}
}
}
void basicCutSet(vector<unordered_set<int> >& cutSystem, unordered_set<int>& branches, unordered_set<int>& strings)
{
// 基本割集系统
for (auto it = branches.begin(); it != branches.end(); ++it)
{
unordered_set<int> temp(strings);
temp.insert(*it);
cutSystem.push_back(temp);
}
}
void endpointsOfEdge(unordered_map<int, vector<int> > &endpoints, vector<vector<int> >& M)
{
// 边和顶点绑定
for (int j = 0; j < m; ++j)
{
for (int i = 0; i < n; ++i)
{
if (M[i][j] == 1)
{
endpoints[j].push_back(i);
}
}
}
}
bool dfs(int initS, int newS, vector<int> &usedEdge, stack<int> &edgePath,unordered_map<int, vector<int> > &endpoints, vector<unordered_set<int> >& loopSystem, unordered_set<int>& branches)
{
if (newS == initS) // 当前起点为初始起点时,说明已经走出了一条回路
{
unordered_set<int> temp;
while (!edgePath.empty())
{
int t = edgePath.top();
edgePath.pop();
temp.insert(t);
}
loopSystem.push_back(temp);
return true;
}
bool flag = false; // 是否能走通
for (auto it = branches.begin(); it != branches.end(); ++it)
{
if (usedEdge[*it] == 0) // 未用过的边
{
int tnewS = newS;
if (endpoints[*it][0] == newS) // 可以走通
{
tnewS = endpoints[*it][1]; // 更新当前起点
}
else if (endpoints[*it][1] == newS)
{
tnewS = endpoints[*it][0];
}
else
{
continue;
}
usedEdge[*it] = 1;
edgePath.push(*it);
flag = true;
if (dfs(initS, tnewS, usedEdge, edgePath, endpoints, loopSystem,branches)) // 找到了返回
{
return true;
}
else
{
flag = false;
usedEdge[*it] = 0;
edgePath.pop();
}
}
}
return flag;
}
void basicCircuitSet(vector<unordered_set<int> >& loopSystem, unordered_set<int>& branches, unordered_set<int>& strings, unordered_map<int, vector<int> > &endpoints)
{
for (auto it = strings.begin(); it != strings.end(); ++it)
{
int initS = endpoints[*it][0];
int newS = endpoints[*it][1];
vector<int> usedEdge(m, 0);
usedEdge[*it] = 1;
stack<int> edgePath;
edgePath.push(*it);
if (dfs(initS, newS, usedEdge, edgePath, endpoints, loopSystem, branches))
{
}
else
{
cout << "dfs不可能找不到的,哪里出错了呢?" << endl;
}
}
}
// 传入的参数分别是边集数组,输出设置选项,choice为0输出的事基本断集系统
// 为1输出的为基本回路系统,为2输出的为回路空间,为3输出的为断集空间
void printEdgeSet(vector<unordered_set<int> > &edgeSet, int choice)
{
if (edgeSet.size() == 0)
{
if (choice == 0 || choice == 1)
{
printf("null set\n");
}
else
{
printf("{null set}\n");
}
}
else if (choice == 0 || choice == 3) // 基本断集系统或断集空间
{
printf("{");
if (choice == 3) // 断集空间还要输出一个空集
{
printf("null set,");
}
for (int i = 0; i < int(edgeSet.size()); ++i)
{
printf("{");
auto it = edgeSet[i].begin();
printf("e%d",*it + 1);
++it;
while(it != edgeSet[i].end())
{
printf(",e%d", *it + 1);
++it;
}
printf("}");
if (i != int(edgeSet.size()) - 1)
{
printf(",");
}
}
printf("}\n");
}
else // 基本回路系统或环路空间
{
printf("{");
if (choice == 2) // 环路空间还要输出空集
{
printf("null set,");
}
for (int i = 0; i < int(edgeSet.size()); ++i)
{
auto it = edgeSet[i].begin();
printf("e%d",*it + 1);
++it;
while(it != edgeSet[i].end())
{
printf("e%d", *it + 1);
++it;
}
if (i != int(edgeSet.size()) - 1)
{
printf(",");
}
}
printf("}\n");
}
}
void cyclization(unordered_set<int>& a, unordered_set<int> &b, unordered_set<int>& temp)
{
for (auto it = a.begin(); it != a.end(); ++it)
{
if (b.count(*it) == 0)
{
temp.insert(*it);
}
}
for (auto it = b.begin(); it != b.end(); ++it)
{
if (a.count(*it) == 0)
{
temp.insert(*it);
}
}
}
// dfs遍历二位数组中所有的点
void dfs(int t, vector<vector<int>> &tP, vector<int>& visited)
{
for (int i = 0; i < n; ++i)
{
if (visited[i] == 0 && tP[t][i] == 1) // i没有访问过,顶点t和i之间有边
{
visited[i] = 1;
dfs(i, tP, visited);
}
}
}
// 根据邻接矩阵判断是否是连通图
bool isConnected(vector<vector<int>> &tP, vector<int>& visited)
{
// 先排除有孤立点的
if (n == 1)
{
return false;
}
// 从v1开始搜索
visited[0] = 1;
dfs(0, tP, visited);
for (int i = 0; i < n; ++i)
{
if (visited[i] == 0) // 有顶点没有访问到
{
return false;
}
}
return true;
}
int main()
{
// 输入相邻矩阵
cout << "输入顶点数:" << endl;
cin >> n;
vector<vector<int> > A(n, vector<int>(n, 0)); // 相邻矩阵
cout << "输入相邻矩阵:" << endl;
for(int i=0; i<n; i++)
{
for(int j=0; j<n; j++)
{
cin >> A[i][j];
if (A[i][j] != 0)
{
m++;
}
}
}
m /= 2; // 这里用握手定理求边数
vector<vector<int> > M(n, vector<int>(m, 0)); // 关联矩阵
getIncidenceMatrix(A, M);
cout << "输出关联矩阵:" << endl;
printMatrix(M);
vector<vector<int> > sequences; // 记录 n - 1阶行列式的序列集
vector<int> sequence; // 记录n - 1 阶行列式
selectSequence(0, n - 1, sequences, sequence, m);
// cout << "sequences大小" << sequences.size() << endl;
cout << "生成树的数量:" << endl;
unordered_set<int> store; // 存储所有生成树的n - 1阶行列式的位置
printf("%d\n", numOfSpanningTree(M, sequences, store));
int t = *store.begin(); // 这里选取其中一个生成树对应的n - 1阶行列式
vector<vector<int> > tA(n, vector<int>(n, 0)); // 该树的相邻矩阵
vector<vector<int> > tM(n, vector<int>(n - 1, 0)); // 其中一个生成树的关联矩阵
for (int i = 0; i < n; ++i) // i是顶点序号
{
for (int j = 0; j < n - 1; ++j)
{
sequence.push_back(sequences[t][j]); // sequences[t][j]是边序号
tM[i][j] = M[i][sequences[t][j]]; // 这里对生成树中的 n - 1 条边进行了重新编号
}
}
printf("任意一个生成树的关联矩阵:\n");
printMatrix(tM);
for (int j = 0; j < n - 1; ++j) // 遍历边
{
int x = -1;
int y = -1;
for (int i = 0; i < n; ++i) // 遍历顶点
{
if (tM[i][j] == 1)
{
if (x == -1)
{
x = i;
}
else
{
y = i;
}
}
}
tA[x][y] = 1;
tA[y][x] = 1;
}
printf("任意一个生成树的相邻矩阵:\n");
printMatrix(tA);
unordered_set<int> strings; // 存储弦序号
unordered_set<int> branches; // 存储树枝序号
vector<unordered_set<int> > cutSystem; // 存储该生成树的基本断集系统
vector<unordered_set<int> > loopSystem; // 存储该生成树的基本回路系统
unordered_map<int, vector<int> > endpoints; // 压缩版的关联矩阵,值是端点序号
endpointsOfEdge(endpoints, M);
branchSet(sequence, branches);
stringSet(strings, branches);
printf("branches:%d\n", branches.size());
printf("strings:%d\n", strings.size());
// 基本割集系统:先把一个树枝和所有弦放入一个假割集,
// 再对邻接矩阵操作,去掉所有弦和一个树枝,再尝试添加弦,如果添加后仍然是不连通的,就添加这条弦
// 然后,在假割集中去掉添加的弦,最终得到真的割集
unordered_set<int> temp;
vector<vector<int> > P(A); // 复制邻接矩阵
for (auto it = strings.begin(); it != strings.end(); ++it)
{
temp.insert(*it);
// 对邻接矩阵进行操作,去掉所有弦
int v1 = endpoints[*it][0];
int v2 = endpoints[*it][1];
P[v1][v2] = 0;
P[v2][v1] = 0;
}
for (auto it = branches.begin(); it != branches.end(); ++it)
{
unordered_set<int> fSet(temp);
vector<vector<int>> tP(P);
vector<int> visited(n, 0);
// 对邻接矩阵操作,去掉一个树枝,这时的邻接矩阵应该是不连通的,否则报错
fSet.insert(*it); // 加入一个树枝的序号
int v1 = endpoints[*it][0];
int v2 = endpoints[*it][1];
tP[v1][v2] = 0;
tP[v2][v1] = 0;
if (isConnected(tP, visited))
{
cout << "出错了,去除所有的弦和一个树枝后不可能是连通的!" << endl;
exit(1);
}
for (auto p = strings.begin(); p != strings.end(); ++p)
{
// 判断加上这个弦后的邻接矩阵是否连通,仍不连通则在假割集中将其删除,连通了就再在邻接矩阵中将其去掉
int v1 = endpoints[*p][0];
int v2 = endpoints[*p][1];
tP[v1][v2] = 1;
tP[v2][v1] = 1;
for (int i = 0; i < n; ++i)
visited[i] = 0;
if (!isConnected(tP, visited)) // 仍不连通,在假割集中将这条弦去掉
{
fSet.erase(*p);
}
else // 连通了说明这条弦是真割集的一部分
{
tP[v1][v2] = 0;
tP[v2][v1] = 0;
}
}
cutSystem.push_back(fSet);
}
printf("基本割集系统:\n");
printEdgeSet(cutSystem, 0);
// 基本回路系统
basicCircuitSet(loopSystem, branches, strings, endpoints);
printf("基本回路系统:\n");
printEdgeSet(loopSystem, 1);
vector<unordered_set<int> > loopSpace;
vector<unordered_set<int> > cutSpace;
// 求环路空间,环合运算并不是两两环合,0个就是空集,1个就是本身,
// 所以仍然有一个n选m的过程,即哪些基本回路之间进行环合运算,这些基本回路两两环合得到最终的边集
for (auto i = loopSystem.begin(); i != loopSystem.end(); ++i)
{
loopSpace.push_back(*i); // 一个环合
for (auto j = i + 1; j != loopSystem.end(); ++j) // 两两环合
{
unordered_set<int> temp;
cyclization(*i, *j, temp);
loopSpace.push_back(temp);
}
}
if (loopSystem.size() == 3) // 三个环合
{
auto t1 = loopSystem.begin();
auto t2 = t1 + 1;
unordered_set<int> temp1;
cyclization(*t1, *t2, temp1);
unordered_set<int> temp2;
t2++;
cyclization(temp1, *t2, temp2);
loopSpace.push_back(temp2);
}
else if (loopSpace.size() > 3)
{
int num = loopSystem.size();
for (int i = 3; i <= int(loopSpace.size()); ++i) // i代表参与环合的环路数量
{
// 先从num个环路中选出i个环路,然后对这i个环路进行环合运算
vector<vector<int>> sets;
vector<int> temp;
selectSequence(0, i, sets, temp, num);
for (int ti = 0; ti < int(sets.size()); ++ti) // sets[ti][tj]是loopSystem中环路的序号
{
auto t1 = loopSystem[sets[ti][0]];
unordered_set<int> t;
for (int tj = 1; tj < i; ++tj)
{
auto t2 = loopSystem[sets[ti][tj]];
cyclization(t1, t2, t);
t1 = t; // 当前的部分环合赋值给t1
t.clear();
}
loopSpace.push_back(t1); // 其中一个i环合结果,一共有sets.size()个
}
}
}
printf("该生成树对应的环路空间:\n");
printEdgeSet(loopSpace, 2);
// // 求断集空间
for (auto i = cutSystem.begin(); i != cutSystem.end(); ++i)
{
cutSpace.push_back(*i); // 一个断集
for (auto j = i + 1; j != cutSystem.end(); ++j) // 两两环合
{
unordered_set<int> temp;
cyclization(*i, *j, temp);
cutSpace.push_back(temp);
}
}
if (cutSystem.size() == 3) // 三个环合
{
auto t1 = cutSystem.begin();
auto t2 = t1 + 1;
unordered_set<int> temp1;
cyclization(*t1, *t2, temp1);
unordered_set<int> temp2;
t2++;
cyclization(temp1, *t2, temp2);
cutSpace.push_back(temp2);
}
else if (cutSpace.size() > 3)
{
int num = cutSystem.size();
for (int i = 3; i <= int(cutSpace.size()); ++i) // i代表参与环合的断集数量
{
// 先从num个断集中选出i个断集,然后对这i个断集进行环合运算
vector<vector<int>> sets;
vector<int> temp;
selectSequence(0, i, sets, temp, num);
for (int ti = 0; ti < int(sets.size()); ++ti) // sets[ti][tj]是cutSystem中环路的序号
{
auto t1 = cutSystem[sets[ti][0]];
unordered_set<int> t;
for (int tj = 1; tj < i; ++tj)
{
auto t2 = cutSystem[sets[ti][tj]];
cyclization(t1, t2, t);
t1 = t; // 当前的部分环合赋值给t1
t.clear();
}
cutSpace.push_back(t1); // 其中一个i环合结果,一共有sets.size()个
}
}
}
printf("该生成树对应的断集空间:\n");
printEdgeSet(cutSpace, 3);
return 0;
}
测试结果
测试用例一:
输入顶点数:
4
输入相邻矩阵:
0 1 1 1
1 0 0 1
1 0 0 1
1 1 1 0
输出关联矩阵:
1 1 1 0 0
1 0 0 1 0
0 1 0 0 1
0 0 1 1 1
sequences大小10
生成树的数量:
8
任意一个生成树的关联矩阵:
1 0 0
0 1 0
0 0 1
1 1 1
任意一个生成树的相邻矩阵:
0 0 0 1
0 0 0 1
0 0 0 1
1 1 1 0
branches:3
strings:2
基本割集系统:
{{e5,e2},{e4,e1},{e3,e2,e1}}
基本回路系统:
{e2e5e3,e1e4e3}
该生成树对应的环路空间:
{null set,e2e5e3,e4e1e5e2,e1e4e3}
该生成树对应的断集空间:
{null set,{e5,e2,e1},{e4,e5},{e3,e5},{e4,e2,e1},{e3,e4},{e3,e2,e1}}
测试用例二
输入顶点数:
4
输入相邻矩阵:
0 1 1 1
1 0 1 1
1 1 0 1
1 1 1 0
输出关联矩阵:
1 1 0 1 0 0
1 0 1 0 1 0
0 1 1 0 0 1
0 0 0 1 1 1
生成树的数量:
16
任意一个生成树的关联矩阵:
1 0 0
0 1 0
0 0 1
1 1 1
任意一个生成树的相邻矩阵:
0 0 0 1
0 0 0 1
0 0 0 1
1 1 1 0
branches:3
strings:3
基本割集系统:
{{e6,e2,e3},{e5,e1,e3},{e4,e1,e2}}
基本回路系统:
{e3e6e5,e2e6e4,e1e5e4}
该生成树对应的环路空间:
{null set,e3e6e5,e4e2e5e3,e4e1e6e3,e2e6e4,e5e1e6e2,e1e5e4,e1e3e2}
该生成树对应的断集空间:
{null set,{e6,e2,e3},{e5,e2,e1,e6},{e4,e3,e1,e6},{e5,e1,e3},{e2,e4,e3,e5},{e4,e1,e2},{e4,e6,e5}}
测试用例三
输入顶点数:
2
输入相邻矩阵:
0 1
1 0
输出关联矩阵:
1
1
生成树的数量:
1
任意一个生成树的关联矩阵:
1
1
任意一个生成树的相邻矩阵:
0 1
1 0
branches:1
strings:0
基本割集系统:
{{e1}}
基本回路系统:
null set
该生成树对应的环路空间:
{null set}
该生成树对应的断集空间:
{null set,{e1}}
测试用例四
输入顶点数:
5
输入相邻矩阵:
0 1 1 0 0
1 0 0 1 0
1 0 0 1 0
0 1 1 0 1
0 0 0 1 0
输出关联矩阵:
1 1 0 0 0
1 0 1 0 0
0 1 0 1 0
0 0 1 1 1
0 0 0 0 1
生成树的数量:
4
任意一个生成树的关联矩阵:
1 0 0 0
0 1 0 0
1 0 1 0
0 1 1 1
0 0 0 1
任意一个生成树的相邻矩阵:
0 0 1 0 0
0 0 0 1 0
1 0 0 1 0
0 1 1 0 1
0 0 0 1 0
branches:4
strings:1
基本割集系统:
{{e5},{e4,e1},{e3,e1},{e2,e1}}
基本回路系统:
{e1e3e4e2}
该生成树对应的环路空间:
{null set,e1e3e4e2}
该生成树对应的断集空间:
{null set,{e5},{e1,e4,e5},{e1,e3,e5},{e1,e2,e5},{e4,e1},{e3,e4},{e2,e4},{e3,e1},{e2,e3},{e2,e1},{e3,e5,e4},{e2,e5,e4},{e2,e5,e3},{e1,e2,e4,e3},{e1,e3,e5,e4,e2}}