持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第35天,点击查看活动详情
手把手教学考研大纲范围内图的存储和遍历 22考研大纲数据结构要求的是C/C++,笔者以前使用的都是Java,对于C++还很欠缺, 如有什么建议或者不足欢迎大佬评论区或者私信指出 初心是用最简单的语言描述数据结构
Talk is cheap. Show me the code. 理论到处都有,代码加例题自己练习才能真的学会
2.4、邻接多重表
邻接多重表特点:
虽然邻接表已经能很好的表示无向图了,但是如果需要删除一条边<i, j>,则需要去 i,j 中分别删除相邻的结点
邻接多重表,每条边有个边结构体,删除时只需要删除一个边即可,但是要解除这个边的所有关系
邻接多重表中,边与边之间的联系相对密切,很容易找到与某个结点相关的边
邻接多重表结构:
顶点表结点:
mark:哨兵,用来标记当前边是否被访问过。
ivex:当前边相关联的一个结点。
ilink:指向上一条存在 ivex 结点的边。
jvex:当前边相关联的另一个结点。
jlink:指向上一条存在 jvex 结点的边。
info:表示顶点的值
在本篇博客中,做了ivex < jvex 的处理,
不做处理也可以实现,删除边时,相对于本篇博客的代码可能要做一些逻辑修改
邻接多重表例图:
以 <2, 3> 这条边为例子:
可以通过 <2, 1> 这条边的 ilink 找到 <2, 3>
V4 (下标为3) 头结点的第一条相关边为 <2, 3>
可以通过 <2, 3> 这条边的 ilink 找到 <2, 4>
可以通过 <2, 3> 这条边的 jlink 找到 <0, 3>
边与边之间的联系相对密切,很容易找到与某个结点相关的边
邻接多重表示例代码
代码:
#include "iostream"
#define MAXSize 100
using namespace std;
typedef struct ArcNode { //边结构体:
bool visited; //记录此边是否被访问
int ivex, jvex; //记录此边的两个结点 i j(这里存放的是两个结点的下标)
// (笔者边结点保证了,下标小的为i,下标大的为j,如果不做处理,后面删除边也要修改逻辑)
struct ArcNode *ilink, *jlink; //ilink指向上一条和当前边 ivex 相同的边(上一条边的ivex 或者 jvex 和当前边的ivex相等)
// ,jlink也是指向上一条和 jvex 相关的边
int info; //用来记录这条边的权重
} ArcNode;
typedef struct VexNode { //结点结构体: 结点的值,firstedge指向与结点相关的第一条边
int data;
ArcNode *firstedge;
} VexNode;
typedef struct { //图结构体:结点的数组(表头为结点,结点后面跟指针),vexnum 结点的数量,arcnum 边结点的数量
VexNode adjmulist[MAXSize];
int vexnum, arcnum;
} AMLGraph;
//找到顶点在表头结点的下标
int getGraphIndex(AMLGraph G, int targetnode) { //通过结点的值,找到在图中表头结点的下标(如果不存在,返回-1)
for (int i = 0; i < G.vexnum; i++) {
if (G.adjmulist[i].data == targetnode) {
return i;
}
}
return -1;
}
//创建边
bool createArc(AMLGraph &G) {
int vex1, vex2, index1, index2;
cout << "请输入新建边的两个结点" << endl;
cin >> vex1 >> vex2;
index1 = getGraphIndex(G, vex1); //找到两个结点在结点数组中的下标
index2 = getGraphIndex(G, vex2);
ArcNode *newedge = new ArcNode; //构建一个新边
ArcNode *oldfirstegde; //!!!
if (index1 > index2) { //保证index1 <= index2 这么做是为了里面边的 i j 相对有序(如果不保证有序,但是后面删除边的逻辑要记得修改)
index1 ^= index2;
index2 ^= index1;
index1 ^= index2;
}
newedge->ivex = index1; //新边为i j 之间的边,边的两个结点要赋值i j
newedge->jvex = index2;
//把新边连接到下标index1 和 index2 两个头结点的第一条边
//相当于链表插入,头插法
oldfirstegde = G.adjmulist[index1].firstedge; //找到当前index1下标的结点的第一条边
G.adjmulist[index1].firstedge = newedge; //把新边绑定到index1的第一条边
newedge->ilink = oldfirstegde; //新边的ivex 和index1相等的,把老边绑定到新边的ilink
//与上面类似,是插入到index2头结点的第一条边
oldfirstegde = G.adjmulist[index2].firstedge;
G.adjmulist[index2].firstedge = newedge;
newedge->jlink = oldfirstegde;
G.arcnum++;
return true;
}
//通过下标找到边,在表头结点target相关的边 中 解除这条边的关系 //(target是index1 或者index2的其中一个)
bool deleteArc(AMLGraph &G, int index1, int index2, int target) { //找到一条下标index1 和 index2相关的边,删除下标 target 中此边的关系
ArcNode *curArc = G.adjmulist[target].firstedge; //找到target下标结点的第一条边
ArcNode *preArc; //记录当前边的上一条边,如果cur是要删除的边,直接pre下一条的关系连接到cur下一条的关系
int count = 0;
while (curArc) { //此边不为空,一直循环
count++; //每次循环一条边,count+1,,记录目标边是第几条
if (curArc->ivex == index1 && curArc->jvex == index2) { //如果找到此边,退出循环
break;
}
preArc = curArc; //当前边给pre,cur继续去找下一条边
//这里通过ivex 或者 jvex 找到下一条有关系的点
//如果 ivex 是 index1 或者 index2 ,就找ivex相关的下一条边,指向->ilink
if (curArc->ivex == index1 || curArc->ivex == index2) { //如果ivex结点为index1或者index2其中的一个
curArc = curArc->ilink;
//其他情况下就是 jvex 是 index1 或者 index2,就找jlink
} else {
curArc = curArc->jlink;
}
}
if (count == 0 || !curArc) { //如果没有循环,或者cur指向空结点了,说明没有此边
// cout << "当前边不存在" << endl;
return false;
} else if (count == 1) { //如果只循环了一次,说明是表头结点的第一条相关的边
if (curArc->ivex == target) { //如果是 ivex 结点与target相关 就把当前边的 ilink 赋值给表头结点的第一条边
G.adjmulist[target].firstedge = curArc->ilink;
} else { //否则就是 jvex 结点与target相关,把当前边的jlink给表头结点的第一条边
G.adjmulist[target].firstedge = curArc->jlink;
}
} else {
//如果pre的ivex 和 cur的ivex 相等,说明两个边都是通过 ilink 连接的,cur->ilink和pre->ilink是同一个ivex相关联的点
// 删除当前边的关系,把cur->ilink 赋值给pre->ilink
//这里举个例子:头结点下标为 0 的边 <0,1>,<0,2>,<0,3> cur指向<0,2> pre指向<0,1>
//两个边是通过 ivex 相连的,直接把pre->ilink 指向 cur->ilink (下面的大家可以自己举个例子理解理解)
if (preArc->ivex == curArc->ivex) {
preArc->ilink = curArc->ilink;
//pre->ivex和cur->jvex相关联 例子:头结点下标为 5 的边 <5,6>,<3,5>,<5,7>
} else if (preArc->ivex == curArc->jvex) {
preArc->ilink = curArc->jlink;
//例子:头结点下标为 5 的边 <0,5>,<5,7>,<3,5>
} else if (preArc->jvex == curArc->ivex) {
preArc->jlink = curArc->ilink;
//例子:头结点下标为 5 的边 <0,5>,<3,5>,<5,7>
} else if (preArc->jvex == curArc->jvex) {
preArc->jlink = curArc->jlink;
}
//一共就是这四种关系,大家可以多对应图,理解理解
}
return true;
}
//删除边
bool deleteArc(AMLGraph &G) {
int vex1, vex2, index1, index2;
cout << "请输入删除边的两个结点" << endl;
cin >> vex1 >> vex2;
index1 = getGraphIndex(G, vex1);
index2 = getGraphIndex(G, vex2);
if (index1 > index2) { //保证index1 <= index2 这么做是为了里面边的 i j 相对有序(如果没有这一步后面可能删除边会相对麻烦一些)
index1 ^= index2;
index2 ^= index1;
index1 ^= index2;
}
//删除index1 和 index2 相对于 index1 和 index2 头结点的关系
if (deleteArc(G, index1, index2, index1) && deleteArc(G, index1, index2, index2)) {
cout << vex1 << "->" << vex2 << "边删除成功" << endl;
return true;
} else {
cout << vex1 << "->" << vex2 << "边删除失败" << endl;
return false;
}
}
//创建结点
bool createVex(AMLGraph &G) {
cout << "请输入要创建的结点的值" << endl;
int data;
cin >> data;
G.adjmulist[G.vexnum].data = data; //给结点赋值,结点的第一条有关的边设为空,图中结点数量+1
G.adjmulist[G.vexnum].firstedge = NULL;
G.vexnum++;
return true;
}
//删除结点
bool deleteVex(AMLGraph &G) {
cout << "请输入要删除的结点" << endl;
int vex;
cin >> vex;
int index = getGraphIndex(G, vex); //找到要删除结点的下标
for (int i = 0; i < G.vexnum; i++) { //循环每个结点,删除所有结点与目标结点的关系
while (deleteArc(G, i, index, i)); //这里用while,是防止存在多条边的情况(当要删除的边不存在的时候会返回false)
while (deleteArc(G, i, index, index)); // 解除表头结点 i 中这条边的关系
}
G.adjmulist[index].data = -1; //结点删除后,把当前结点的值标记为 -1 ,表明该点已被删除
return true;
}
//初始化创建图
bool createAMLGraph(AMLGraph &G) {
cout << "请输入结点数" << endl;
int vexCount;
cin >> vexCount;
G.vexnum = 0; //初始化时,G的顶点数量为0
for (int i = 0; i < vexCount; ++i) {
createVex(G);
}
cout << "请输入边数" << endl;
int arcCount;
cin >> arcCount;
for (int i = 0; i < arcCount; ++i) {
createArc(G);
}
cout << "邻接多重表初始化完成" << endl;
return true;
}
//输出邻接多重表
bool printAMLGraph(AMLGraph G) {
for (int i = 0; i < G.vexnum; i++) { //循环图中的每一个点
if (G.adjmulist[i].data == -1) { //如果当前点的值为 -1 ,说明当前点已被删除
continue;
}
cout << G.adjmulist[i].data;
ArcNode *demo = G.adjmulist[i].firstedge;
while (demo) { //找到当前表头结点的第一个边,
cout << " " << demo->ivex << "<->" << demo->jvex; //输出当前边的两个结点
demo = demo->ivex == i ? demo->ilink : demo->jlink; //如果当前点的ivex与表头结点相关,就指向ilink,否则就是jvex与表头结点相关
}
cout << endl;
}
return true;
}
int main() {
AMLGraph G;
createAMLGraph(G); //初始化图
printAMLGraph(G);
int selected;
while (true) {
cout << "输入 1 添加边, 输入 2 删除边, 输入 3 添加结点, 输入 4 删除结点, 输入其他退出" << endl;
cin >> selected;
if (selected == 1) {
createArc(G); //创建边
} else if (selected == 2) {
deleteArc(G); //删除边
} else if (selected == 3) {
createVex(G); //创建结点
} else if (selected == 4) {
deleteVex(G); //删除结点
} else {
break;
}
printAMLGraph(G); //每次都输出图
}
return 0;
}
输入输出样例:
/home/a1439775520/CLionProjects/Graph/cmake-build-debug/Graph
请输入结点数
4
请输入要创建的结点的值
0
请输入要创建的结点的值
1
请输入要创建的结点的值
2
请输入要创建的结点的值
3
请输入边数
5
请输入新建边的两个结点
0 1
请输入新建边的两个结点
0 2
请输入新建边的两个结点
0 3
请输入新建边的两个结点
1 3
请输入新建边的两个结点
2 3
邻接多重表初始化完成
0 0<->3 0<->2 0<->1
1 1<->3 0<->1
2 2<->3 0<->2
3 2<->3 1<->3 0<->3
输入 1 添加边, 输入 2 删除边, 输入 3 添加结点, 输入 4 删除结点, 输入其他退出
1
请输入新建边的两个结点
1 2
0 0<->3 0<->2 0<->1
1 1<->2 1<->3 0<->1
2 1<->2 2<->3 0<->2
3 2<->3 1<->3 0<->3
输入 1 添加边, 输入 2 删除边, 输入 3 添加结点, 输入 4 删除结点, 输入其他退出
2
请输入删除边的两个结点
0 3
0->3边删除成功
0 0<->2 0<->1
1 1<->2 1<->3 0<->1
2 1<->2 2<->3 0<->2
3 2<->3 1<->3
输入 1 添加边, 输入 2 删除边, 输入 3 添加结点, 输入 4 删除结点, 输入其他退出
3
请输入要创建的结点的值
4
0 0<->2 0<->1
1 1<->2 1<->3 0<->1
2 1<->2 2<->3 0<->2
3 2<->3 1<->3
4
输入 1 添加边, 输入 2 删除边, 输入 3 添加结点, 输入 4 删除结点, 输入其他退出
1
请输入新建边的两个结点
1 4
0 0<->2 0<->1
1 1<->4 1<->2 1<->3 0<->1
2 1<->2 2<->3 0<->2
3 2<->3 1<->3
4 1<->4
输入 1 添加边, 输入 2 删除边, 输入 3 添加结点, 输入 4 删除结点, 输入其他退出
4
请输入要删除的结点
4
0 0<->2 0<->1
1 1<->2 1<->3 0<->1
2 1<->2 2<->3 0<->2
3 2<->3 1<->3
输入 1 添加边, 输入 2 删除边, 输入 3 添加结点, 输入 4 删除结点, 输入其他退出
q
Process finished with exit code 0