持续创作,加速成长!这是我参与「掘金日新计划 · 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起始的边:
V1的 firstout(指向第一个以当前结点为起始点的边)指向边 0->1 ,(这里存储的是结点下标)
此边的 tlink (指向和当前边的起始结点相同的上一条边)指向 0->2
可一直通过边的 tlink 找到相同起始点的边
找以 V1 结束的边:
V1 的 firstin (指向第一个以当前结点为终止点的边)指向边 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