22计算机408考研—数据结构—邻接多重表

402 阅读8分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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

三、图的遍历