实验目的:
- 掌握无向连通图生成树的求解方法;
- 掌握基本回路系统和环路空间的求解方法;
- 掌握基本割集系统和断集空间的求解方法;
- 了解生成树、环路空间和断集空间的实际应用。
实验要求
1.给定一无向简单连通图的相邻矩阵
2.输出此图的关联矩阵M。
3.求此图所有生成树的个数。
4.输出其中任意一颗生成树的相邻矩阵(默认第i行对应顶点vi)和关联矩阵(默认第i行对应顶点vi,第j列对应边ej)。
5.求此生成树对应的基本回路系统(输出形式如:{e1e4e3,e2e5e3})。
6.求此生成树对应的基本割集系统(输出形式如:{{e1,e4},{e2,e5},{e3,e4,e5}})。
加分题
求此生成树对应的环路空间
求此生成树对应的断集空间
思路分析
这里用一个样例给大家简单输出一下
0 1 1 1
1 0 0 1
1 0 0 1
1 1 1 0
第一问
输出关联矩阵
较为简单,不多讲解,这里也应该卡一个输入输出限制,不能检查有没有负数,判断是不是简单图
for (int l = 0; l < line; l++)
{
for (int h = 0; h < column; h++)
{
cin >> p[l][h];
if (p[l][h] < 0)
{
cout << "存在负数 请重新输入"<<endl;
return 0;
}
sumD += p[l][h];
}
}
int edgesNumber;
if (sumD % 2 == 1)
{
cout << "矩阵有误,请重新输入";
return 0;
}
这里还设置一个边类,这个边,内存放连接的两个点,和这个边的id(它是e几)
class borde
{
public:
int v1;
int v2;
int id;
borde()
{
}
};
准备工作做好以后就开始转换
先把相邻矩阵有1的地方转换为边(这个非0点的二维坐标就是连接的两个边)//注意在代码里是e0开始 所有输出的时候加个1.
borde* mborde = new borde[edgesNumber];
//检查是相邻矩阵是否为1
int n = 0;//计数
for (int l = 0; l < line; l++)
{
for (int h = l + 1; h < column; h++)
{
if (p[l][h] == 1)
{
mborde[n].v1 = l;
mborde[n].v2 = h;
mborde[n].id = n + 1;
n++;
}
}
}
然后对应着把这个边数组,存进二维数组里。
int** associated;
column = edgesNumber;
associated = new int* [line];
for (int i = 0; i < line; i++)
{
associated[i] = new int[column]();
}
for (int x = 0; x < column; x++)
{
associated[mborde[x].v1][x] = 1;
associated[mborde[x].v2][x] = 1;
}
cout << endl;
结果
这里从0-4是 e1-e5;
求生成树的数量
首先,这里用了离散数学书上的方法,也就是说,我们需要一个n-1阶的基本关联矩阵,那我们就只要这个矩阵的n-1行 这里需要n-1列,所以这里需要排列组合 ,大家可以上网搜搜排列组合,我是直接用到别人的,就不放出来了。
这里我们应该得到 C3/5 的所有组合数
这个数代表上面那个关联矩阵的列数,所有拿第一个举例 0 3 4 对应 e1 e4 e5
然后去3*3的一个矩阵 判断这个二维数组的行列式 非0则为生成数 遍历一下判断有几个数;
随便取组作为生成的树输出
相邻矩阵这里不多讲
求此生成树对应的基本回路系统
这里使用的一个不是很好理解的方法
先讲一个简单的方法,
首先要知道基本回路系统只有一个圈,我们这里用度数为2来判断,一定有弦的数量个回路,而且一定不一样.开始遍历,找一条弦和任意一条边,判断度数是否为2,不是就在换,这里组合求法和上一问一样,这里是从2条边开始判断,两条不行就三条,依次上加,然后路径要按一个圈的顺序来,我们知道这是一个回路 可以用上一提找欧拉回路的办法,这里因为已经知道圈的大小,所以直接从第一开始遍历就行。理解这种想法,下一问用了类似的
这里是我的方法(很抽象而且很没必要)
这里用了关联矩阵的递归,而且我目前写的这个方法还有点bug,不过懒的改了,在递归调用退出的时候有点小问题,
这是我们取到的任加一个弦的二维数组,此时想法从e2的一个顶点出发回到e2的另外一个顶点结束,因为这个圈里一定包含e2,所有一定能找到,我们读到e2的第一点 也就是v1,从v1出发 发现了e1 此时应该通过e1找到 连接的另外一个顶点,然后开始递归调用,
这里重点讲一下递归的结束条件,首先就是找到 也就是着一行的最后一个数是1,当然这不可能是起始的顶点,(这里不做多的判断 因为不可回到第一个顶点,不然就有圈了 它就不是生成树了)
第二种可能是这是个悬挂点 这一行只要1个1,显然回退出返回上一次调用
两种情况判断完以后,就开始找这一行非自己的1,在准备进入下一行;
int start(int ** tmpRoad,int line,int firstX,int lastY,int *road,int number)//要一个二维数组
{
for (int l = 0; l < line ; l++)
{
int quite = 0;
if (tmpRoad[l][lastY] == 1 && l != firstX)
{
//找到先判断是不是终点
if (tmpRoad[l][line-1]==1)
{
//就是找到了
road[0] = number;
road[number] = lastY;
return 1;
}
//在判断这个点是不是悬挂点
int Ok=0;
for (int i=0; i < line - 1; i++)
{
if (tmpRoad[l][i]==1&&i!=lastY)
{
Ok = 1;
break;
}
}
if (Ok!=1)
{
return 0;//直接返回
}
else
{
//找到下一个点并且开始递归
for (int i=0; i < line-1; i++)
{
if (tmpRoad[l][i] == 1 && i != lastY)
{
quite=start(tmpRoad, line, l, i, road, number + 1);
if (quite==1)
{
road[number] = lastY;
return 1;
}
}
}
}
}
}
}
这个就是递归的子函数,有bug,大家可以修改,bug就在第二个判断时,当这个点时悬挂点时,我们应该返回上一层继续判断下一个点,我这里写的时候应该直接出去了,直接全部return了,有问题,不过问题不大,
附加题的第一问做环合运算就行,不多讲
最后一问
//最后一问;
//选中生成树的一条边
//从弦中找一下 1/2/3/n删除 若删完不连通 则为一个断集
//一个二维数组存最后的结果 第一个数是这个数组的长度
这个和上一问的简单方法想法一样,就是选一个树枝,然后找任意一个弦,把这个两个都删了,判断矩阵连通不,不连通在换一条边,一条不行就放两条,这里连通的方法和上篇博客的方法一样,大家可以取看看,
附加的第二问也是做个环合运算就行