22计算机408考研—数据结构—十字链表

403 阅读10分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第34天,点击查看活动详情

手把手教学考研大纲范围内图的存储和遍历 22考研大纲数据结构要求的是C/C++,笔者以前使用的都是Java,对于C++还很欠缺, 如有什么建议或者不足欢迎大佬评论区或者私信指出 初心是用最简单的语言描述数据结构

Talk is cheap. Show me the code. 理论到处都有,代码加例题自己练习才能真的学会

2.3、十字链表

十字链表特点:

​ 十字链表是有向图的另一种链式结构,可以看作邻接表和逆邻接表结合起来得到的一种链表。

​ 十字链表可以快速得到与某点相关联的边,可以高效存取。

​ 大致理解为:每一行第一个为顶点结点,顶点结点的指针根据规律指向对应的边结点。

十字链表结构:

十字链表订单结构 的图像结果

顶点表结点:

data:表示顶点的值

firstin:指向第一个以当前结点为终止点的边(first 第一个 in 进入 第一个进入该结点的边

firstout:指向第一个以当前结点为起始点的边(first 第一个 out 出去 第一个从该结点出去的边

边结点:

tailvex:存储边的起始点。(tailvex代表的是弧尾,也就是起始点 A -> B 弧尾是A,这条边从A开始)

headvex:存储边的终止点(headvex 代表的是弧头)

hlink:指向上一条以 headvex 为终止点的边。

tlink:指向上一条以 tailvex 为起始点的边。

十字链表例图:

在这里插入图片描述

V1 点为例子,

找从V1起始的边:

V1firstout(指向第一个以当前结点为起始点的边)指向边 0->1 ,(这里存储的是结点下标)

​ 此边的 tlink (指向和当前边的起始结点相同的上一条边)指向 0->2

​ 可一直通过边的 tlink 找到相同起始点的边

找以 V1 结束的边:

V1firstin (指向第一个以当前结点为终止点的边)指向边 3->0

​ 此边的 hlink (指向和当前边终止结点相同的上一条边) ,不存在终止结点相同上一条边,此时为NULL

​ 可一直通过边的 hlink 找相同终止点的边

十字链表的最大优点:

把邻接表和逆邻接表整合在一起了,很容易找到以 V1 为起始点的边,也容易找到以 V1 为终止点的边。

十字链表示例代码

代码:
#include "iostream"

using namespace std;

#define MAXSize 100  //顶点数最大为100

typedef struct ArcNode {    //定义边结点结构体:(边采用头插法)
    int info;   //边的权重,
    int tailvex;    //边的尾结点的下标(一条边的起始点下标)
    int headvex;    //边的头结点的下标(一条边的终止点下标)
    struct ArcNode *hlink;  //指向上一条 与边的头结点相同的边 (指向上一条有相同起始点的边)
    struct ArcNode *tlink;  //指向上一条 与边的尾结点相同的边 (指向上一条有相同终止点的边)
} ArcNode;

typedef struct VNode {  //定义表头结点结构体:
    int data;   //结点的数据
    ArcNode *firstin;   //指向最后一条以当前顶点为结尾的边(最后一条以当前点的下标为终止点的边)
    ArcNode *firstout;  //指向最后一条以当前顶点为起始的边(最后一条以当前点的下标为起始点的边)
} VexNode;

typedef struct {    //定义十字链表结构体:表头结点数组,结点数量和边数量
    VexNode vexlist[MAXSize];
    int vexnum, arcnum;
} ALGraph;
//找到顶点在表头结点的下标
int getGraphIndex(ALGraph G, int targetnode) {  //通过结点的值,找到在图中表头结点的下标(如果不存在,返回-1)
    for (int i = 0; i < G.vexnum; i++) {
        if (G.vexlist[i].data == targetnode) {
            return i;
        }
    }
    return -1;
}

    //创建边
bool createArc(ALGraph &G) {    //(边采用头插法)
    int vex1, vex2, index1, index2; //起始点,终止点,起始点下标,终止点下标
    cout << "输入边的起始点和终止点" << endl;
    cin >> vex1 >> vex2;
    index1 = getGraphIndex(G, vex1);  //找到两个顶点的下标
    index2 = getGraphIndex(G, vex2);
    if (index1 == -1 && index2 == -1) {
        cout << "起始顶点和终止顶点不存在" << endl;
        return false;
    } else if (index1 == -1) {
        cout << "起始顶点不存在" << endl;
        return false;
    } else if (index2 == -1) {
        cout << "终止顶点不存在" << endl;
        return false;
    }
    ArcNode *newArc = new ArcNode;  //创建新边结点,新边的起始点指向index1,终止点指向index2
    newArc->tailvex = index1;
    newArc->headvex = index2;

    ArcNode *oldtail = G.vexlist[index1].firstout;  //找到当前index1为起始点的第一条边
    ArcNode *oldhead = G.vexlist[index2].firstin;   //找到当前index2为终止点的第一条边

    newArc->hlink = oldhead;    //新边的index2为终止点的边指向oldhead(oldhead就成了上一个以index2为终止点的边)
    newArc->tlink = oldtail;    //新边的index2为起始点的边指向oldtail(oldtail就成了上一个以index2为起始点的边)

    G.vexlist[index1].firstout = newArc;    //以index1为起始点的第一条边指向新边(头插法)
    G.vexlist[index2].firstin = newArc;     //以index2为终止点的第一条边指向新边


    cout << vex1 << "->" << vex2 << "  创建成功" << endl;
    return true;
}

    //删除边
bool deleteArc(ALGraph &G) {
    int vex1, vex2, index1, index2;
    cout << "输入边的起始点和终止点" << endl;
    cin >> vex1 >> vex2;
    index1 = getGraphIndex(G, vex1);
    index2 = getGraphIndex(G, vex2);
    if (index1 == -1 && index2 == -1) {
        cout << "起始顶点和终止顶点不存在" << endl;
        return false;
    } else if (index1 == -1) {
        cout << "起始顶点不存在" << endl;
        return false;
    } else if (index2 == -1) {
        cout << "终止顶点不存在" << endl;
        return false;
    }
    ArcNode *targetArc;
    ArcNode *firstOutCur = G.vexlist[index1].firstout;  //找到index1为起始点的第一条边
    ArcNode *firstOutPre;
    int count = 0;
    while (firstOutCur) {   //看边是否存在
        count++;    //每循环一次就是上一条边,count每次加1,记录这是第几条边
        //找到起始点和终止点对应的那条边(找到就终止)
        if (firstOutCur->tailvex == index1 && firstOutCur->headvex == index2) {
            break;
        }
        firstOutPre = firstOutCur;  //把当前边给pre,cur指向下一个以index1为起始点的边
        firstOutCur = firstOutCur->tlink;
    }
    if (count == 0 || !firstOutCur) {   //count等于0 说明没进循环 firstOutCur为空
        cout << "不存在该边" << endl;    //或者 找了所有以index1为起始点的边没有找到这条边
        return false;
    } else if (count == 1) {    //如果count=1 说明循环了一次就找到了这条边,这是第一条边
        //就把以index1为起始点的第一条边指向这条边的下一个,把这条边以index1为起始点的关系解除
        G.vexlist[index1].firstout = firstOutCur->tlink;
    } else {
        //把这条边的上一条边的相同起始点的边 指向 这条边下一条相同起始点的边,把这条边以index1为起始点的关系解除
        firstOutPre->tlink = firstOutCur->tlink;
    }
    targetArc = firstOutCur;    //只要没有返回false,证明当前存在这条边

    //下面的是找以index2为终止点的边  与上面大概类似!!!
    count = 0;  //count重置为0
    ArcNode *firstInCur = G.vexlist[index2].firstin;
    ArcNode *firstInPre;
    while (firstInCur) {    //如果以index2为终止点的边不为空,一直循环
        count++;    //每次循环,都是一条新边, count+1
        if (firstInCur==targetArc) {    //如果当前边是上面找出来的那条边,终止循环
            break;
        }
        firstInPre = firstInCur;    //当前边保存到pre,当前边继续找index2为终止点的边
        firstInCur = firstInCur->hlink;
    }
    if (count == 0 || !firstOutCur) {
        cout << "不存在该边" << endl;
        return false;
    } else if (count == 1) {
        G.vexlist[index2].firstin = firstInCur->hlink;
    } else {
        firstInPre->hlink = firstInCur->hlink;
    }

    G.arcnum--; //删除后图的边数-1
    cout << vex1 << "->" << vex2 << "  删除成功" << endl;
    return true;
}
    //根据下标删除边(删除结点时使用)                          //删除结点的时候使用此方法,因为要频繁的删除与结点相关的边
bool deleteArc(ALGraph &G, int index1, int index2) {    //方法和上面删除边的方法基本类似,这里的下标是不需要输入的
    ArcNode *firstOutCur = G.vexlist[index1].firstout;
    ArcNode *firstOutPre;
    int count = 0;
    while (firstOutCur) {
        count++;
        if (firstOutCur->tailvex == index1 && firstOutCur->headvex == index2) {
            break;
        }
        firstOutPre = firstOutCur;
        firstOutCur = firstOutCur->tlink;
    }
    if (count == 0 || !firstOutCur) {
        return false;
    } else if (count == 1) {
        G.vexlist[index1].firstout = firstOutCur->tlink;
    } else {
        firstOutPre->tlink = firstOutCur->tlink;
    }

    count = 0;
    ArcNode *firstInCur = G.vexlist[index2].firstin;
    ArcNode *firstInPre;
    while (firstInCur) {
        count++;    //这里可以用下标匹配删除,如果存在多条起点和终点相同的边(平行边),删除的一定是相同一条边!!!
                    //插入边的时候采用的是头插法,最后插入的边一定在最后,
                    // 搜起始点相同的也是先搜到的最后一个边,终止点相同的也是最后一个边
        if (firstInCur->tailvex == index1 && firstInCur->headvex == index2) {
            break;
        }
        firstInPre = firstInCur;
        firstInCur = firstInCur->hlink;
    }
    if (count == 0 || !firstOutCur) {
        return false;
    } else if (count == 1) {
        G.vexlist[index2].firstin = firstInCur->hlink;
    } else {
        firstInPre->hlink = firstInCur->hlink;
    }
    G.arcnum--;
    return true;
}
    //删除结点
bool deleteVex(ALGraph &G) {
    cout << "请输入删除的顶点" << endl;
    int vex, index;
    cin >> vex;
    index = getGraphIndex(G, vex);
    for (int i = 0; i < G.vexnum; ++i) {    //循环每一个顶点,删除与他相关的边
        while (deleteArc(G, index, i));//删除下标 index->i 的边,
                                        // 如果删除成功返回true,继续循环,防止有平行边,当删除失败说明不存在边,返回false
        while(deleteArc(G, i, index));  //同上 删除 i->index 的边
    }
    G.vexlist[index].data = -1; //删除完结点,设置此结点的值为-1,表示结点已删除
    cout << "结点已删除" << endl;
}

    //添加结点
bool createVex(ALGraph &G) {
    cout << "请输入添加结点的值" << endl;
    int data;
    cin >> data;
    G.vexlist[G.vexnum].data = data;    //设置结点的数值,以该结点为终止点,以该结点为初始点的边赋值空
    G.vexlist[G.vexnum].firstin = NULL;
    G.vexlist[G.vexnum].firstout = NULL;
    G.vexnum++;
    cout << "添加结点成功" << endl;
}

    //创建十字链表
bool createGraph(ALGraph &G) {
    G.vexnum = 0;   //图的初始结点数量为0
    cout << "请输入顶点数量" << endl;
    int count;
    cin >> count;    //输出顶点数量和边数量
    for (int i = 0; i < count; i++) {    //循环创建count个结点
        createVex(G);
    }
    cout << "请输入边的数量" << endl;
    cin >> G.arcnum;
    int v1, v2;
    for (int i = 0; i < G.arcnum; i++) {    //循环创建边
        createArc(G);
    }
    cout << "初始化十字链表成功" << endl;
    return true;
}



    //打印十字链表
void printGraph(ALGraph G) {
    cout <<endl;
    for (int i = 0; i < G.vexnum; i++) {
        if (G.vexlist[i].data == -1) {  //如果该结点的值为 -1 说明该结点被删除了,跳过本次循环
            continue;
        }
        ArcNode *tail = G.vexlist[i].firstout;  //找到第一条以该结点为初始点的边
        ArcNode *head = G.vexlist[i].firstin;   //找到第一条以该结点为终止点的边

        cout << G.vexlist[i].data << "  起始的边有: ";
        while (tail) {  //输出完当前边的信息,接着找上一条以当前点为初始点的边
            cout << G.vexlist[tail->tailvex].data << "->" << G.vexlist[tail->headvex].data << "   ";
            tail = tail->tlink;
        }

        cout << endl << G.vexlist[i].data << "  结束的边有: ";
        while (head) {  //输出完当前边的信息,接着找上一条以当前点为终止点的边
            cout << G.vexlist[head->tailvex].data << "->" << G.vexlist[head->headvex].data << "   ";
            head = head->hlink;
        }
        cout << endl;
    }
    cout << endl;
}

int main() {
    ALGraph G;
    //输入样例:4 3 1 2 3 4 1 2 1 3 2 3
    createGraph(G);
    printGraph(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;
        }
        printGraph(G);
    }
}

输入输出样例:
/home/a1439775520/CLionProjects/Graph/cmake-build-debug/Graph
请输入顶点数量
4
请输入添加结点的值
0
添加结点成功
请输入添加结点的值
1
添加结点成功
请输入添加结点的值
2
添加结点成功
请输入添加结点的值
3
添加结点成功
请输入边的数量
7
输入边的起始点和终止点
0 1
0->1  创建成功
输入边的起始点和终止点
0 2
0->2  创建成功
输入边的起始点和终止点
2 0
2->0  创建成功
输入边的起始点和终止点
2 3
2->3  创建成功
输入边的起始点和终止点
3 0
3->0  创建成功
输入边的起始点和终止点
3 1
3->1  创建成功
输入边的起始点和终止点
3 2
3->2  创建成功
初始化十字链表成功

0  起始的边有: 0->2   0->1   
0  结束的边有: 3->0   2->0   
1  起始的边有: 
1  结束的边有: 3->1   0->1   
2  起始的边有: 2->3   2->0   
2  结束的边有: 3->2   0->2   
3  起始的边有: 3->2   3->1   3->0   
3  结束的边有: 2->3   

输入 1 添加边, 输入 2 删除边, 输入 3 添加结点, 输入 4 删除结点, 输入其他退出
1
输入边的起始点和终止点
1 2
1->2  创建成功

0  起始的边有: 0->2   0->1   
0  结束的边有: 3->0   2->0   
1  起始的边有: 1->2   
1  结束的边有: 3->1   0->1   
2  起始的边有: 2->3   2->0   
2  结束的边有: 1->2   3->2   0->2   
3  起始的边有: 3->2   3->1   3->0   
3  结束的边有: 2->3   

输入 1 添加边, 输入 2 删除边, 输入 3 添加结点, 输入 4 删除结点, 输入其他退出
2
输入边的起始点和终止点
0 2
0->2  删除成功

0  起始的边有: 0->1   
0  结束的边有: 3->0   2->0   
1  起始的边有: 1->2   
1  结束的边有: 3->1   0->1   
2  起始的边有: 2->3   2->0   
2  结束的边有: 1->2   3->2   
3  起始的边有: 3->2   3->1   3->0   
3  结束的边有: 2->3   

输入 1 添加边, 输入 2 删除边, 输入 3 添加结点, 输入 4 删除结点, 输入其他退出
3
请输入添加结点的值
4
添加结点成功

0  起始的边有: 0->1   
0  结束的边有: 3->0   2->0   
1  起始的边有: 1->2   
1  结束的边有: 3->1   0->1   
2  起始的边有: 2->3   2->0   
2  结束的边有: 1->2   3->2   
3  起始的边有: 3->2   3->1   3->0   
3  结束的边有: 2->3   
4  起始的边有: 
4  结束的边有: 

输入 1 添加边, 输入 2 删除边, 输入 3 添加结点, 输入 4 删除结点, 输入其他退出
1
输入边的起始点和终止点
1 4
1->4  创建成功

0  起始的边有: 0->1   
0  结束的边有: 3->0   2->0   
1  起始的边有: 1->4   1->2   
1  结束的边有: 3->1   0->1   
2  起始的边有: 2->3   2->0   
2  结束的边有: 1->2   3->2   
3  起始的边有: 3->2   3->1   3->0   
3  结束的边有: 2->3   
4  起始的边有: 
4  结束的边有: 1->4   

输入 1 添加边, 输入 2 删除边, 输入 3 添加结点, 输入 4 删除结点, 输入其他退出
1
输入边的起始点和终止点
4 3
4->3  创建成功

0  起始的边有: 0->1   
0  结束的边有: 3->0   2->0   
1  起始的边有: 1->4   1->2   
1  结束的边有: 3->1   0->1   
2  起始的边有: 2->3   2->0   
2  结束的边有: 1->2   3->2   
3  起始的边有: 3->2   3->1   3->0   
3  结束的边有: 4->3   2->3   
4  起始的边有: 4->3   
4  结束的边有: 1->4   

输入 1 添加边, 输入 2 删除边, 输入 3 添加结点, 输入 4 删除结点, 输入其他退出
4
请输入删除的顶点
4
结点已删除

0  起始的边有: 0->1   
0  结束的边有: 3->0   2->0   
1  起始的边有: 1->2   
1  结束的边有: 3->1   0->1   
2  起始的边有: 2->3   2->0   
2  结束的边有: 1->2   3->2   
3  起始的边有: 3->2   3->1   3->0   
3  结束的边有: 2->3   

输入 1 添加边, 输入 2 删除边, 输入 3 添加结点, 输入 4 删除结点, 输入其他退出
q

Process finished with exit code 0