您的位置:首页 > 理论基础 > 数据结构算法

【数据结构笔记】3:无向图的邻接矩阵存储结构

2017-04-09 12:01 302 查看
这学期学了图,感觉图论在很多问题上都有非常精妙的应用。邻接矩阵作为无向图的存储结构,实现起来是最简易的。实际上很多时候,我们并会直接采用邻接矩阵去做什么,但是在后面学习其它存储结构的时候就会发现,因为邻接矩阵的简洁明快,更多地是用它去作为一个初始化的工具,用来为其它结构存储的图进行初始化。

在这种存储结构上,对数据节点本身单独打表,用一维数组vertexes[]存储节点信息,用二维数组arcs[][]存储边(有向中为弧)的信息。

*邻接矩阵的数据成员:

int vexNum, vexMaxNum, arcNum;			// 顶点数目、允许的顶点最大数目和边数
int **arcs;							    // 存放边信息邻接矩阵
ElemType *vertexes;						// 存放顶点信息的数组
mutable Status *tag;					// 标志数组


从数据成员中可以看出,除了边数组和节点数组之外,还有一些必要的成员。顶点数目用来确认我们的矩阵中哪些元素是真实在录的(如我们删除某个节点后,节点数组的大小总归没有改变,就要用节点数目来知道我们现在有几个节点是在录的);允许的顶点最大数目可以用作参数传入构造函数,这样我们就知道边数组和节点数组需要开多大的空间了;边数的存在纯粹是为了方便我们返回边数的操作,在图的应用中这样的操作应该是常有的,将他记录下来相当于“用空间去换取时间效率”;枚举型标志数组tag[]是用于图的访问过程中记录哪些点是我们访问过的了,注意mutable关键字记录的成员即使在const尾修饰的函数中也可以被修改,显然遍历时我们不希望修改邻接矩阵中的任何顶点和边,但是访问情况的修改却是必要的。

作为无向图,如果存在从A到B的边那么也就是存在从B到A的边,因此无向图的邻接矩阵中边数组总是对称的。

 

*构造函数:

template <class ElemType>
AdjMatrixUndirGraph<ElemType>::AdjMatrixUndirGraph(ElemType es[], int vertexNum, int vertexMaxNum)
// 操作结果:构造数据元素为es[],顶点个数为vertexNum,允许的顶点最大数目为vertexMaxNum,边数为0的无向图

{
if (vertexMaxNum < 0)
throw Error("允许的顶点最大数目不能为负!");        // 抛出异常

if (vertexMaxNum < vertexNum)
throw Error("顶点数目不能大于允许的顶点最大数目!");// 抛出异常

vexNum = vertexNum;
vexMaxNum = vertexMaxNum;
arcNum = 0;

vertexes = new ElemType[vexMaxNum];      // 生成生成顶点信息数组
tag = new Status[vexMaxNum];			       // 生成标志数组
arcs = (int **)new int *[vexMaxNum];     // 生成邻接矩阵
for (int v = 0; v < vexMaxNum; v++)
arcs[v] = new int[vexMaxNum];

for (int v = 0; v < vexNum; v++) {
vertexes[v] = es[v];
tag[v] = UN
bb01
VISITED;
for (int u = 0; u < vexNum; u++)
arcs[v][u] = 0;
}
}

注意这里arcs = (int **)new int *[vexMaxNum];是创建了一块存放整形指针的长为vexMaxNum的一维数组,并将这块空间的地址强制转换为二级指针给了arcs,而在后面对于这个存放整形指针的数组中的每一个元素v,都用arcs[v] = new int[vexMaxNum];分配了一块长度为vexMaxNum的整形数组用来存放边的情况,每个这一块数组都相当于边数组的一列。

*析构函数 :

template <class ElemType>
AdjMatrixUndirGraph<ElemType>::~AdjMatrixUndirGraph()
// 操作结果:释放对象所占用的空间
{
delete []vertexes;					// 释放顶点数据
delete []tag;						    // 释放标志

for (int v = 0; v < vexMaxNum; v++)	// 释放邻接矩阵的行
delete []arcs[v];
delete []arcs;					    // 释放邻接矩阵
}

注意对于这样创建的二维数组,需要先依次释放每一列的纵向的arcs[v]所指向的空间(整形数组),再释放横向的arcs所指向的空间(整形指针数组)。

 

*插入顶点/插入边/删除边:

插入顶点和插入边(依附于参数提供的两个点的索引)都是较为方便的。

注意插入顶点时要判断顶点数没有越界,然后插入后维护好当前顶点数,并将顶点所在行列全部置0(表示没有边和这个顶点联系)。

注意插入边的时候要判断这条边是否存在,这条边不存在的时候才能作维护(边数+1),然后将表示这条边的两个对称的矩阵区域置1。

注意删除边的时候要判断这条边是否存在,这条边存在的时候才能作维护(边数-1),然后将表示这条边的两个对称的矩阵区域置0。

 

*删除顶点

template <class ElemType>
void AdjMatrixUndirGraph<ElemType>::DeleteVex(const ElemType &d)
// 操作结果:删除元素为d的顶点
{
int v;
for (v = 0; v < vexNum; v++)
if	(vertexes[v] == d)
break;	//运行到此找到要删除的元素的下标
if (v == vexNum)
throw Error("图中不存在要删除的顶点!");	// 抛出异常

for (int u = 0; u < vexNum; u++)             // 删除与顶点d相关联的边
if (arcs[v][u] == 1) {
arcNum--;	//维护边的数量信息
arcs[v][u] = 0;
arcs[u][v] = 0;
}

vexNum--;	//维护点的数量信息
if (v < vexNum)	//如果要删除的点不是数组中最后的那个点
{
vertexes[v] = vertexes[vexNum];	//就将数组最后一个点提上来直接覆盖它
tag[v] = tag[vexNum];	//对应的访问情况也直接覆盖它
for (int u = 0; u <= vexNum; u++)	//把边数组的最后一行覆盖删掉的节点的对应行
arcs[v][u] = arcs[vexNum][u];
for (int u = 0; u <= vexNum; u++)	//把边数组的最后一列覆盖删掉的节点的对应列
arcs[u][v] = arcs[u][vexNum];
}
}

删除顶点实际上就要删除掉和顶点有关的一切,这里先将那些分布杂乱(无规律可循)的与顶点有关的边都删除了(实际上只要遍历节点的一行就能找到全部有关边,删除时却要置0行寻和列寻的两个矩阵元),然后因为这种顺序结构信息移动的复杂性,干脆将最后一列的所有信息提上来补充到删掉的这一列就方便多了。

 

在主程序中,可以用遍历一个普通的二维数组和插入边的方式去建立一个想要的无向图。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: