数据结构例题代码及其讲解-图

01 图的邻接矩阵存储结构定义。
  1. 顶点表、边(二维数组)、顶点数量和边的数量
typedef struct MGraph {char Vex[MaxSize];//顶点(vertex)中数据int Edge[MaxSize][MaxSize];//边int vexnum, arcnum;//顶点数量和边的数量
}MGraph;

图中涉及到.和->的区别。

弧与边:弧:有向图中连接两个节点的媒介通常叫做“弧”。 边:无向图中连接两个节点的媒介通常叫做“边”。而arc表示弧的意思,希望可以对大家记录这几个单词有所帮助。

02 图的邻接表存储结构定义。
//边表结点
typedef struct ArcNode {int adjvex;//边指向顶点的存储位置struct ArcNode* nextarc;//指向下一个边表结点的指针域
}ArcNode;
//顶点结点
typedef struct VNode {char data;//顶点中数据ArcNode* firstarc;//指向第一个边表结点的指针域
}VNode;
//图
typedef struct AGraph {VNode adjlist[MaxSize];//邻接表int vexnum, arcnum;//顶点数量和边的数量
}AGraph;

用邻接表存储图,需要两部分,顶点和边,这里定义了两种结点,

顶点结点:顶点结点用来存放顶点,包括数据域和指针域,其指针域用来指向这个顶点的第一条边

边表结点:边表结点用来表示边之间的关系,也包括数据域和指针域,指针域指向该顶点结点指向边。

顶点结点的指针域是ArcNode类型,也就是边表结点类型,其指向第一个边表结点。

图中定义的是顶点结点类型的数组(数组中的每个元素是顶点结点类型)。

参数定义说明:定义函数传参时,MGraph G是把邻接矩阵表示的图直接传进去了,而AGraph G是直接把邻接表表示的图传进去,但是这种情况比较少见,且主流的参考资料中,在邻接表存储的图中,使用的是AGraph* G,即传进去一个指针,这个指针指向这个图。用指针的方式,则取结构体中的内容时候,需要用->

void Func(AGraph* G) {int a = G->vexnum;//a获取图的顶点数量int b = G->arcnum;//b获取图的边的数量printf("%c", G->adjlist[0].data);//打印第一个顶点结点的数据ArcNode* p;//定义一个ArcNode类型的p指针;p = G->adjlist[0].firstarc;//p初始时指向图的第一个顶点结点的指针域,也就是边表结点printf("%c", G->adjlist[p->adjvex].data);//通过p指针指向的边表结点的数据域,打印顶点结点
}

上述代码是图的部分操作代码,这里尤其要注意用**.还是->**来表示结构体中内容。
在这里插入图片描述

03 写出从图的邻接表表示转换成邻接矩阵表示的算法。
  1. 邻接表表示的图有邻接表,顶点数量变量和边数量变量,邻接矩阵表示的图有顶点表、边(二维数组)、顶点数量变量和边的数量变量,要将邻接表转换成邻接矩阵,对于顶点数量和边数量,直接复制过来即可,对于如何填充邻接矩阵的顶点表和边(二维数组),需要遍历;
  2. 这里按行遍历,将邻接表中的顶点信息和边的信息复制过来,在此之前需要对==邻接矩阵初始化==一下,二维数组初始化可以使用两个for循环;
  3. 遍历需要遍历指针,是边表结点(ArcNode)类型的遍历指针,按行遍历,遍历四行(也就是顶点结点的个数)。
void Func(MGraph& G1, AGraph* G2) {//MGraph表示邻接矩阵,AGraph表示邻接表G1.vexnum = G2->vexnum;//给图的顶点数量赋值G1.arcnum = G2->arcnum;//给图的边数量赋值//初始化邻接矩阵for (int i = 0; i < G2->vexnum; i++)for (int j = 0; j < G2->vexnum; j++)G1.Edge[i][j] = 0;ArcNode* p;//定义遍历指针for (int i = 0; i < G2->vexnum; i++) {//先复制到邻接矩阵的顶点表中G1.Vex[i] = G2->adjlist[i].data;//将顶点数据值存储在 Vex 数组中//然后处理边,也就是填充二维数组p = G2->adjlist[i].firstarc;//指针 p 指向顶点 i 的第一个边表结点while (p != NULL) {//遍历顶点 i 的所有边表结点G1.Edge[i][p->adjvex] = 1;//将邻接矩阵中边对应位置改为 1p = p->nextarc;//指针 p 继续向后遍历}}
}
04 写出从图的邻接矩阵表示转换成邻接表表示的算法。
  1. 邻接矩阵表示的图有顶点表、边(二维数组)、顶点数量变量和边的数量变量,邻接表表示的图有邻接表,顶点数量变量和边数量变量,要将邻接矩阵转换成邻接表,对于顶点数量和边数量,直接复制过来即可,对于如何填充邻接表,需要遍历邻接矩阵;
  2. 遍历邻接矩阵,如果值为1,说明有边,需要创建边表结点,插入到邻接表,可以采用头插法插入。
  3. 代码步骤如下:①给邻接表的顶点数量和边数量变量赋值;②填充邻接表的顶点结点;③遍历邻接矩阵(2个for循环遍历二维数组),存在边创建边表结点头插。
void Func(MGraph G1, AGraph * G2) {//MGraph表示邻接矩阵,AGraph表示邻接表G2->vexnum = G1.vexnum;//给图的顶点数量赋值G2->arcnum = G1.arcnum;//给图的边数量赋值//邻接表的顶点结点的填充for (int i = 0; i < G1.vexnum; i++) {//遍历存储顶点数据的 Vex 数组G2->adjlist[i].data = G1.Vex[i];//将各顶点数据赋值给邻接表的顶点结点中G2->adjlist[i].firstarc = NULL;//初始化顶点结点的指针域}ArcNode* p;//定义遍历指针for (int i = 0; i < G1.vexnum; i++) {//遍历邻接矩阵for (int j = 0; j < G1.vexnum; j++) {if (G1.Edge[i][j] != 0) {//若两顶点存在边则需建立边表结点p = (ArcNode*)malloc(sizeof(ArcNode));//创建边表结点p->adjvex = j;//为边表结点数据域赋值,注意是j不是i//将边表结点头插到对应位置p->nextarc = G2->adjlist[i].firstarc;G2->adjlist[i].firstarc = p;}}}
}

对于邻接表转邻接矩阵:

①复制边和顶点数量,②初始化邻接矩阵,③填充顶点表和填充邻接矩阵在一个for循环里操作;

对于邻接矩阵转邻接表:

①复制边和顶点数量,②填充邻接表的顶点结点,③两个for循环遍历邻接矩阵找边创建结点头插。

05 请写出以邻接矩阵方式存储图的广度优先遍历算法。
  1. 广度优先遍历又称BFS(Breath First Search),先遍历最近的一层,没了在转到下一个结点,继续;
  2. 在邻接矩阵上看,就是先遍历指定结点这一行,有的话就依次打印,遍历完后,在去找下一个结点,这里需要考虑是否已经被遍历打印过,图这一章中经常会定义一个辅助数组visited[],初始化时全部设为0,每次遍历一个结点,将其在visited[]中对应内容置为1,表示已经被遍历过了;
  3. BFS函数需要传入邻接矩阵G,v表示需要从哪个结点开始进行广度优先遍历,以及遍历数组。
  4. 有些图是非连通图,需要检查是否有结点没有被遍历过,因此需要代码中第22行开始的部分。

BFS 为什么需要队列?

​ 对于 BFS 算法,正如上面所说的,我们需要一层一层遍历所有的相邻结点。那么相邻结点之间的先后顺序如何确定?因此我们需要一个数据结构来进行存储和操作,需要使得先遍历的结点先被存储,直到当前层都被存储后,按照先后顺序,先被存储的结点也会被先取出来,继续遍历它的相邻结点。因此我们可以发现,这个需求不就是我们的队列吗,First In First Out (FIFO) 。因此对于 BFS 我们需要使用 Queue 这样的一个数据结构,来存储每一层的结点,同时**维护『先进先出 FIFO』**的顺序。

void BFS(MGraph G, int v, int visited[]) {//visited[]已经全部初始化为0了Queue Q;//定义辅助队列 QInitQueue(Q);//初始化队列 Qprintf("%c", G.Vex[v]);//打印该顶点数据visited[v] = 1;//更新遍历数组EnQueue(Q, v);//将遍历顶点地址入队while (!IsEmpty(Q)) {//队列不为空则继续循环DeQueue(Q, v);//出队并让 v 接收出队顶点地址for (int j = 0; j < G.vexnum; j++){//遍历邻接矩阵的第 v 行if (G.Edge[v][j] == 1 && visited[j] == 0) {//寻找有边且未被遍历过的顶点printf("%c", G.Vex[j]);//打印该顶点的数据visited[j] = 1;//更新遍历数组EnQueue(Q, j);//将该顶点地址入队}}}
}
void Func(MGraph G, int v) {int visited[G.vexnum];//定义遍历数组for (int i = 0; i < G.vexnum; i++)//初始化遍历数组visited[i] = 0;BFS(G, v, visited);//从顶点 v 开始进行广度优先遍历for (int i = 0; i < G.vexnum; i++)//检查图 G 是否有未被遍历到的顶点if (visited[i] == 0)//若有未被遍历到的顶点则需再次执行 BFS 算法BFS(G, i, visited);
}
06 请写出以邻接表方式存储图的广度优先遍历算法。
  1. 邻接矩阵中打印结点值是在顶点表中打印的,而邻接表中打印结点值需要在顶点结点中;
  2. 邻接矩阵中是遍历二维数组进行广度优先遍历,邻接表遍历边表结点进行广度优先遍历;
  3. 邻接表中需要遍历指针p才能遍历,邻接矩阵中直接for循环遍历;
  4. 遍历时,遍历指针p不为空,说明有结点相连,需要进一步判断结点是否已经被遍历过,如果没有被遍历过,打印,更新遍历数组,入队,被遍历过就不去管它,之后需要继续向后遍历。
  5. 有些图是非连通图,需要检查是否有结点没有被遍历过,因此需要代码中第25行开始的部分。
  6. 邻接表方式和邻接矩阵方式存储的图的广度优先遍历,主要区别在于邻接表要用遍历指针去遍历,而邻接矩阵可以for循环遍历。
void BFS(AGraph * G, int v, int visited[]) {Queue Q;//定义辅助队列 QInitQueue(Q);//初始化辅助队列 Qprintf("%c", G->adjlist[v].data);//打印地址为 v 的顶点值visited[v] = 1;//更新遍历数组EnQueue(Q, v);//将遍历顶点地址入队ArcNode* p;//定义遍历指针 pwhile (!IsEmpty(Q)) {//队列不为空则需继续循环DeQueue(Q, v);//出队并让 v 接收出队顶点地址p = G->adjlist[v].firstarc;//p 指针指向顶点 v 的第一个边表结点while (p != NULL) {//遍历顶点 v 的所有边表结点if (visited[p->adjvex] == 0) {//判断遍历边表结点对应顶点是否被遍历过printf("%c", G->adjlist[p->adjvex].data);//遍历其顶点visited[p->adjvex] = 1;//更新遍历数组EnQueue(Q, p->adjvex);//将此遍历顶点地址入队}p = p->nextarc;//遍历指针继续向后遍历}}
}
void Func(AGraph* G, int v) {int visited[G->vexnum];//定义遍历数组for (int i = 0; i < G->vexnum; i++)//初始化遍历数组visited[i] = 0;BFS(G, v, visited);//从顶点 v 开始进行广度优先遍历for (int i = 0; i < G->vexnum; i++)//检查图 G 是否有未被遍历到的顶点if (visited[i] == 0)//若有未被遍历到的顶点则需再次执行 BFS 算法BFS(G, i, visited);
}
07 请设计一个算法,找出邻接表方式存储的无向连通图 G 中距离顶点 v 最远的一个顶点。(所谓最远就是到达顶点 v 的路径长度最长)
  1. 考察广度优先遍历(一层一层的遍历),最后进行遍历的结点就是离最开始遍历的结点最远的结点;
  2. 连通图,不需要考虑一次广度优先遍历没有遍历完结点的情况;
  3. 在队列中,最后一个出队的结点就是最远的顶点。
int Func(AGraph * G, int v, int visited[]) {//改写 BFS,最后一个出队顶点即为所求for (int i = 0; i < G->vexnum; i++)//初始化遍历数组visited[i] = 0;Queue Q;//定义辅助队列 QInitQueue(Q);//初始化队列 Qvisited[v] = 1;//更新遍历数组EnQueue(Q, v);//令遍历顶点地址入队ArcNode* p;//定义遍历指针 pwhile (!IsEmpty(Q)) {//队列不为空则继续循环DeQueue(Q, v);//出队并让 v 接收出队顶点地址p = G->adjlist[v].firstarc;//遍历指针 p 指向顶点 v 的第一个边表结点while (p != NULL) {//遍历顶点 v 的所有边表结点if (visited[p->adjvex] == 0) {//若遍历边表结点对应顶点还未被遍历过visited[p->adjvex] = 1;//更新遍历数组EnQueue(Q, p->adjvex);//将该遍历顶点地址入队}p = p->nextarc;//指针 p 继续向后遍历}}return v;//返回最后一个出队顶点地址
}
08 请写出利用 BFS 算法求解邻接表存储的无权图中单源最短路径的算法。
  1. 单源最短路径是说,找一个顶点,其到其他顶点的最短路径,一般来说定义一个距离数组,通过广度优先遍历的一层一层的遍历,更新数组中的元素。由于可能不是连通图,因此初始化时,需要将距离设为最大。
  2. 例如:求解0号位置,也就是A结点的单源最短路径,通过广度优先遍历将B和C在距离数组中的值变成1,A结点遍历完后,遍历1号位置,也就是遍历B结点开始,剩下只有D结点没有被遍历到,因此D的距离数组中的内容应该是B在距离数组中的元素值加1,这里需要注意。
  3. 单源最短路径的累加(本题的重点是距离数组的更新融入到广度优先遍历算法中)是通过本次开始遍历的顶点在距离数组中的值+1。
void BFS_MIN_Distance(AGraph * G, int v, int visited[], int d[]) {for (int i = 0; i < G->vexnum; i++) {//初始化遍历数组和距离数组visited[i] = 0;d[i] = INT_MAX;}Queue Q;//定义辅助队列 QInitQueue(Q);//初始化辅助队列 QArcNode* p;//定义遍历指针visited[v] = 1;//更新遍历数组d[v] = 0;//单源顶点地址为 v,该顶点到自己的距离为 0EnQueue(Q, v);//将该顶点地址入队while (!IsEmpty(Q)) {//队列不为空则继续遍历DeQueue(Q, v);//出队并让 v 接收出队顶点地址p = G->adjlist[v].firstarc;//遍历指针 p 指向顶点 v 的第一个边表结点while (p != NULL) {//遍历顶点 v 的所有边表结点if (visited[p->adjvex] == 0) {//若遍历边表结点对应的顶点还未被遍历过visited[p->adjvex] = 1;//更新遍历数组d[p->adjvex] = d[v] + 1;//更新距离数组EnQueue(Q, p->adjvex);//将遍历顶点地址入队}p = p->nextarc;//遍历指针继续向后遍历}}
}
09 请写出以邻接矩阵方式存储图的深度优先遍历算法。
  1. 深度优先遍历又称DFS(Depth First Search),一直往前走,向前走不了了,往回走,看是否还有其他路可以走。
  2. 在邻接矩阵中,顶点在其所在行进行查找没有被遍历的顶点,然后跳转到该顶点所在行,依次进行下去;如果找不到没有被遍历的顶点,则返回到上一个顶点所在行,继续往后寻找没有被遍历的顶点。
  3. 这里涉及到往下递和往回归的过程,可以通过递归编写代码。
  4. 递归的案例可以看一下

在这里插入图片描述

void DFS(MGraph G, int v, int visited[]) {printf("%c", G.Vex[v]);//打印顶点 v 的数据visited[v] = 1;//更新遍历数组for (int j = 0; j < G.vexnum; j++) {//遍历邻接矩阵的第 v 行if (G.Edge[v][j] == 1 && visited[j] == 0)//寻找第一个有边且未被遍历过的顶点DFS(G, j, visited);//递归打印顶点 j 的数据值}
}
void Func(MGraph G, int v) {int visited[G.vexnum];//定义遍历数组for (int i = 0; i < G.vexnum; i++)//初始化遍历数组visited[i] = 0;DFS(G, v, visited);//从顶点 v 开始进行深度优先遍历//非连通图的时候需要检查没有被遍历的顶点for (int i = 0; i < G.vexnum; i++)//检查图 G 是否有未被遍历到的顶点if (visited[i] == 0)//若有未被遍历到的顶点则需再次执行 DFS 算法DFS(G, i, visited);
}
10 请写出以邻接表方式存储图的深度优先遍历算法。
  1. while (p != NULL)和p = p->nextarc;相当于邻接矩阵里面的for循环遍历。
void DFS(AGraph * G, int v, int visited[]) {printf("%c", G->adjlist[v].data);//打印顶点 v 的数据visited[v] = 1;//更新遍历数组ArcNode* p = G->adjlist[v].firstarc;//遍历指针 p 指向顶点 v 的第一个边表结点while (p != NULL) {//遍历顶点 v 的所有边表结点if (visited[p->adjvex] == 0)//寻找其未被遍历过的边表结点DFS(G, p->adjvex, visited);//递归打印该顶点的数据值p = p->nextarc;//遍历指针继续向后遍历}
}
void Func(AGraph* G, int v) {int visited[G->vexnum];//定义遍历数组for (int i = 0; i < G->vexnum; i++)//初始化遍历数组visited[i] = 0;DFS(G, v, visited);//从顶点 v 开始进行深度优先遍历for (int i = 0; i < G->vexnum; i++)//检查图 G 是否有未被遍历到的顶点if (visited[i] == 0)//若有未被遍历到的顶点则需再次执行 DFS 算法DFS(G, i, visited);
}
11 有向图 G 以邻接表方式存储的,请设计一个算法判断图 G 中顶点 i 到顶点 j 是否存在路径。(i 和 j 不相等)
  1. 考的是图的遍历,假设从顶点i处遍历,经过一次广度优先遍历/深度优先遍历后,去看visited[]数组中顶点j是否被遍历过。
int Path_i_j(AGraph* G, int i, int j) {//此算法省略了 DFS 算法书写int visited[G->vexnum];//定义遍历数组for (int k = 0; k < G->vexnum; k++)//初始化遍历数组visited[k] = 0;DFS(G, i, visited);//从顶点 i 开始进行深度优先遍历,也可改为 BFS(G,i,visited)if (visited[j] == 1)//若深度优先遍历执行结束后遍历数组中顶点 j 被遍历了return 1;//则说明顶点 i 到顶点 j 有路径else//若顶点 j 未被遍历到,则说明无路径return 0;
}
12 请设计一个算法判断一个邻接表存储的无向图中有几个连通分量。
  1. 一开始写的BFS/DFS算法都是争对连通图的算法,如果不是连通图,需要加一个新的函数,判断是否有没有被遍历到的结点,然后重复调用BFS/DFS算法;
  2. 这里记录连通分量数量也就是改写之前我们写的,也就是看要执行几次遍历,定义一个count变量累加即可。
int Func(AGraph* G) {//此算法省略了 DFS 算法的书写int visited[G->vexnum];//定义遍历数组for (int i = 0; i < G->vexnum; i++)//初始化遍历数组visited[i] = 0;int count = 0;//定义变量 count 负责记录连通分量的个数for (int i = 0; i < G->vexnum; i++)//遍历辅助数组if (visited[i] == 0) {//若存在未被遍历的顶点则以此顶点开始执行 DFS 算法DFS(G, i, visited);//也可改为 BFS(G,i,visited)count++;//每执行一次 DFS 算法连通分量个数就加一}return count;//最后返回 count 值即为连通分量个数
}
13 试设计一个算法,判断一个邻接表存储的无向图 G 是否为一棵树。
  1. 算法思想:树的要求:①连通图;②边的数量=顶点数量-1。
  2. BFS/DFS遍历都可以;
  3. 一次遍历后,visited[]数组若全为1,说明是连通图,G->arcnum == G->vexnum - 1
  4. 无向图中,边会计算两次从A->B和从B->A,但是这里默认边的数量是实际边的数量,没有乘以2的情况。
int IsTree(AGraph * G) {//此算法省略了 DFS 算法的书写int visited[G->vexnum];//定义遍历数组for (int i = 0; i < G->vexnum; i++)//初始化遍历数组visited[i] = 0;DFS(G, 0, visited);//遍历图 G,也可改为 BFS(G,0,visited)for (int i = 0; i < G->vexnum; i++)//检查图 G 是否有未被遍历到的顶点if (visited[i] == 0)//若有未遍历顶点,则图 G 不连通,不是树,返回 0return 0;if (G->arcnum == G->vexnum - 1)//若图 G 是连通图且满足边数等于顶点数减一return 1;//则图 G 是树,返回 1else//若图 G 是连通图但不满足边数等于顶点数减一,则不是树,返回 0return 0;
}
14 有一个邻接矩阵形式存储的连通图 G,试写出图 G 深度优先遍历的非递归算法。
  1. 写非递归算法,需要自己定义栈,因为递归也是调用了递归工作栈;
  2. j == G.vexnum表示没有从上面的for循环中break,也就是说没有边或者有边但是已经被遍历过了,说明这一行没有满足条件的,因此出栈,方便后续回到上一层。
void DFS(MGraph G, int v, int visited[]) {for (int i = 0; i < G.vexnum; i++)//初始化遍历数组visited[i] = 0;Stack S;//定义辅助栈 SInitStack(S);//初始化栈 Sprintf("%c", G.Vex[v]);//打印顶点 v 的数据visited[v] = 1;//更新遍历数组Push(S, v);//将该顶点地址压入栈中int j;while (!IsEmpty(S)) {//栈不为空则需继续循环GetTop(S, v);//让变量 v 接收栈顶顶点地址但不出栈for (j = 0; j < G.vexnum; j++) {//遍历邻接矩阵的第 v 行if (G.Edge[v][j] == 1 && visited[j] == 0)//寻找有边且还未被遍历的顶点break;//找到第 v 行满足条件的顶点后,则直接跳出循环}if (j == G.vexnum)//若第 v 行没有满足条件的顶点,则出栈Pop(S, v);else {//若找到了第 v 行满足条件的顶点printf("%c", G.Vex[j]);//打印其数据值visited[j] = 1;//更新遍历数组Push(S, j);//将该顶点地址压入栈中}}
}
15 有一个邻接表形式存储的连通图 G,试写出图 G 深度优先遍历的非递归算法。
  1. 连通图,只需要写一个函数就行;
  2. 首先要初始化visited数组,还要自己定义且初始化栈,然后就是打印v顶点的数据且将visited数组更新,然后入栈,由于是邻接表,因此需要遍历指针而且是边表结点的遍历指针,因此这里是ArcNode* p;
  3. 深度优先遍历由于需要回到上一层,因此一般是不出栈的,而是用变量去接收栈顶元素进行判断,而在邻接表中表示的都是边,因此这里着重判断是否被遍历过p==NULL说明邻接表中一排已经遍历完了,和邻接矩阵的非递归表示【if (j == G.vexnum),Pop(S, v);】即一行遍历完没有符合条件的,需要出栈,方便后续回到上一层。
  4. p!=NULL,说明找到可以往下遍历的边表结点,打印,更新遍历数组,压栈下一次while循环,读栈顶,p就会从这一行的第一个边表节点开始遍历。
void DFS(AGraph * G, int v, int visited[]) {for (int i = 0; i < G->vexnum; i++)//初始化遍历数组visited[i] = 0;Stack S;//定义辅助栈 SInitStack(S);//初始化栈 Sprintf("%c", G->adjlist[v].data);//打印顶点 v 的数据visited[v] = 1;//更新遍历数组Push(S, v);//将该顶点地址值压入栈中ArcNode* p;//定义遍历指针 pwhile (!IsEmpty(S)) {//栈不为空则需继续循环GetTop(S, v);//让变量 v 接收栈顶顶点地址但不出栈p = G->adjlist[v].firstarc;//遍历指针 p 指向顶点 v 的第一个边表结点while (p != NULL && visited[p->adjvex] == 1)//寻找边表结点中未遍历的顶点p = p->nextarc;if (p == NULL)//若 p 为空顶点 v 的所有的边表结点都已处理完毕Pop(S, v);//顶点 v 地址出栈else {//若指针 p 所指边表结点对应顶点还未遍历过printf("%c", G->adjlist[p->adjvex].data);//打印其数据值visited[p->adjvex] = 1;//更新遍历数组Push(S, p->adjvex);//将该顶点地址压入栈中}}
}

邻接矩阵和邻接表的DFS非递归算法的第二个while循环中都需要判断是否整行都不符合往下遍历的条件。

16 假设图用邻接表表示,设计一个算法,输出从顶点 u 到顶点 v 的所有简单路径。
  1. 简单路径:没有重复结点也找到最终路径,比如A-B-E从顶点A到顶点B,简单路径A-B,非简单路径A-B-E-B;
  2. 改写**深度优先遍历+回溯**,如果找到了一条简单路径,下一步就要回退一步,试着往其他节点处走,是否能到终点;
  3. 如果一条路走带头,无路可走,往回退一步;
  4. path[]代表路径数组,d表示路径数组的下标;
  5. 首先要将路径数组填充一下,更新遍历数组,如果找到一条路径(寻找顶点即为目标顶点),就打印路径,然后我们需要回溯,去到上一个结点,
void PrintPath(AGraph * G, int u, int v, int visited[], char path[], int d) {//d初始时为-1d++;//变量 d 为路径数组索引path[d] = G->adjlist[u].data;//从 u 开始寻找路径visited[u] = 1;//更新遍历数组if (u == v) {//若此时开始寻找顶点即为目标顶点则打印路径for (int i = 0; i <= d; i++)//打印路径数组中存储的路径printf("%c", path[i]);visited[u] = 0;//回溯return;}ArcNode* p = G->adjlist[u].firstarc;//深度优先遍历改写while (p != NULL) {if (visited[p->adjvex] == 0)PrintPath(G, p->adjvex, v, visited, path, d);//当前顶点开始继续寻找路径//上面的if判断如果成立,从顶点结点指向的边表结点开始继续递归,直到最上面的p = p->nextarc;}visited[u] = 0;//更新遍历数组,回溯寻找是否有其它路径
}
//主函数
void Func(AGraph* G, int u, int v) {int visited[G->vexnum];//定义遍历数组char path[G->vexnum];//定义路径数组,这里可以不初始化,因为后面是先更新path数组,然后打印。for (int i = 0; i < G->vexnum; i++)//初始化遍历数组visited[i] = 0;PrintPath(G, u, v, visited, path, -1);//调用函数输出两顶点的所有简单路径
}

大家可以参考下回溯法的模板

//一定要分成横纵两个方面思考回溯
void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {//注意i=0,i=start的区别处理节点;backtracking(路径,选择列表); // 递归  注意(i)和(i++)的区别  后面会懂回溯,撤销处理结果}
}

在这里插入图片描述

for/while循环横向遍历,递归纵向遍历,回溯不断调整结果集

17 请写出邻接表存储有向图的拓扑排序算法。
  1. 拓扑排序(不唯一):对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前
  2. 拓扑排序的步骤:①按照一定的顺序进行构造有向图,记录后个节点的入度;②从图中选择一个入度为0的顶点,输出该顶点; ③从图中删除该顶点及所有与该顶点相连的边;④重复上述两步,直至所有顶点输出。 ⑤或者当前图中不存在入度为0的顶点为止。此时可说明图中有环。
  3. 由于需要考虑到入度,可以在顶点结点处结构体定义一下,加一个入度的变量;
  4. 有环的情况,可以定义一个count计数变量
  5. 初始化好栈后,第一步找是否有入度为0的结点,有则将其入栈(栈中存放的是入度为0的结点),如果栈不为空,说明有入度为0的结点,出栈(用i去接收),打印,这里处理了,count++,然后就需要将出栈的顶点指向的所有顶点的入度减一,减到0了就要将其压栈;
  6. 最后只需要比较count和顶点数量是否相等,不相等说明有环。

结构体定义

//边表结点
typedef struct ArcNode {int adjvex;struct ArcNode* nextarc;
}ArcNode;
//顶点结点
typedef struct VNode {char data;int indegree;//顶点结点结构体增加了一个记录入度的成员ArcNode* firstarc;
}VNode;
typedef struct AGraph {VNode adjlist[MaxSize];int vexnum, arcnum;
}AGraph;
int Top(AGraph* G) {int i = 0, count = 0;//i在入栈出栈时使用,定义变量 count 记录拓扑排序顶点的个数ArcNode* p;//定义遍历指针 pStack S;//定义栈 SInitStack(S);//初始化栈 Sfor (; i < G->vexnum; i++)//遍历邻接表的顶点结点if (G->adjlist[i].indegree == 0)//将入度为 0 的顶点压入栈中Push(S, i);while (!IsEmpty(S)) {//栈不为空则证明还有入度为 0 的顶点需要处理Pop(S, i);//出栈,让变量 i 接收出栈顶点地址printf("%c", G->adjlist[i].data);//打印出栈顶点数据值count++;//记录打印过的顶点个数//⭐将出栈的顶点指向的所有顶点的入度减一p = G->adjlist[i].firstarc;//遍历指针 p 指向出栈顶点的第一个边表结点while (p != NULL) {//遍历出栈顶点的所有边表结点G->adjlist[p->adjvex].indegree--;//该顶点指向的顶点入度减一if (G->adjlist[p->adjvex].indegree == 0)//判断入度减一后是否等于 0Push(S, p->adjvex);//若等于 0 则压入栈中,等待处理p = p->nextarc;//遍历指针继续遍历}}if (count == G->vexnum)//若循环结束所有顶点都已打印,则拓扑排序成功return 1;else//若循环结束打印顶点数小于顶点个数,则该图有环,拓扑排序失败return 0;
}
18 请设计一个算法判断一个邻接表存储的无向图中是否有环。
  1. 方法不唯一,其中一个简单易懂的方法:和之前判断无向图是否构成一棵树的思想类似①是否为连通图②边=顶点-1(也是图的一个临界条件),若边再少一条,就不是连通图了,如果再加一条边,就构成了环。如果不是连通图,边=顶点数-连通分量数,如果边>顶点数-连通分量数,说明有环,这里不存在边<顶点数-连通分量数,因为一旦边少了一条,连通分量+1,左右都减去1,又平衡了。
  2. Func()返回的是连通分量个数
int Func(AGraph * G) {//函数功能:计算图 G 中连通分量个数int visited[G->vexnum];//定义遍历数组for (int i = 0; i < G->vexnum; i++)//初始化遍历数组visited[i] = 0;int count = 0;//定义变量 count 负责记录连通分量的个数for (int i = 0; i < G->vexnum; i++)//遍历辅助数组if (visited[i] == 0) {//若存在未被遍历的顶点则以此顶点开始执行 DFS 算法DFS(G, i, visited);//此算法省略了 DFS 算法的书写count++;//每执行一次 DFS 算法连通分量个数就加一}return count;//最后返回 count 值即为连通分量个数
}
int IsLoop(AGraph* G) {int n = Func(G);//定义变量 n 记录图中连通分量个数if (G->arcnum == G->vexnum - n)//若满足临界条件,则该无向图不存在环return 0;else//若边的数量大于临界条件,则该无向图存在环return 1;
}

如何判断有向图中是否有环呢--拓扑排序,拓扑排序失败,说明有环。

19 请写出构造最小生成树的 Prim 算法。
  1. 生成树,包含连通图中所有的顶点,且任意两顶点之间有且仅有一条通路(边=顶-1
  2. 最小生成树,找出来的所有边,权值之和最小,最小生成树不是唯一的,但是最小生成树的权值之和是唯一的
  3. 使用邻接矩阵构造图,因为邻接矩阵中存放权值比较方便;
  4. 首先要告诉程序从哪个顶点开始找边,lowcost[]数组用来存储当前最小生成树到其他各顶点的最短距离,每次最小生成树更新,lowcost[]也要更新。对于visited[]数组,先通过for循环进行初始化,对于lowcost[]数组,初始化时候,就是对应v顶点所在邻接矩阵的那一行(此时,这行就是该顶点到其他顶点的最短距离),然后更新visited[]数组,将顶点v所在的元素变成1,说明已经加入最小生成树了。然后就是重点了,就是需要找最小生成树的顶点到其他顶点最短距离的顶点,也就是找那条链接最小生成树的最短的边,因此这个循环查找的次数就是**顶点-1**次,每次查找将变量min设置为整型变量的最大值,方便后续更新其值,由于lowcost[]数组中存放的就是当前最小生成树到其他顶点的最短距离,我们遍历它,找到里面的最小值和顶点元素,如果遍历过程中找到之前没有被遍历过的顶点(没有加到最小生成树中的顶点)且其在lowcost[]数组中的距离比min还要小,因此需要更新min和顶点k的值,遍历完成后,则找到了最短距离及顶点,visited[]数组中其对应的元素置为1,最后需要更新lowcost[]数组,因为最小生成树加入了一个顶点,lowcost[]可能会发生变化。每次加入顶点后,用邻接矩阵中新加入顶点所在行与原来lowcost[]数组进行比较,如果变小了,需要更新lowcost[]数组。
void Prim(MGraph G, int v) {int visited[G.vexnum];//记录当前最小生成树中包含的顶点int lowcost[G.vexnum];//记录当前最小生成树到其它各顶点的最短距离for (int i = 0; i < G.vexnum; i++) {//初始化辅助数组visited[i] = 0;lowcost[i] = G.Edge[v][i];}printf("%d", G.Vex[v]);//打印当前加入最小生成树的顶点visited[v] = 1;//更新辅助数组int min, k;//定义变量 min 记录当前最小生成树到其它顶点的最小距离for (int i = 0; i < G.vexnum - 1; i++) {//共需找顶点个数减一条边min = INT_MAX;//每一次寻找前都需要初始化 minfor (int j = 0; j < G.vexnum; j++)//遍历记录距离的数组if (visited[j] == 0 && lowcost[j] < min) {//寻找此时距离最小的边min = lowcost[j];//min 记录最小生成树到其它顶点的最小距离k = j;//k 记录最小权值边对应的顶点}printf("%d", G.Vex[k]);//打印当前加入最小生成树的顶点visited[k] = 1;//更新辅助数组for (int j = 0; j < G.vexnum; j++)//更新记录最小生成树到其它顶点距离的数组if (visited[j] == 0 && G.Edge[k][j] < lowcost[j])lowcost[j] = G.Edge[k][j];}
}
20 请写出构造最小生成树的 Kruskal 算法。
  1. 边按照权值递增的次序进行找,看是否合适(成环就不合适了);
  2. ①构造新的结构体存储边相关的信息,包括起始顶点,结束顶点,权值;②然后进行递增排序;③使用并查集进行
typedef struct {//存储边的结构体int s;//记录边的起点startint e;//记录边的终点endint weight;//记录边的权值
}edge;
int Find(int S[], int x) {//在并查集 S 中查找 x 所在集合的根while (S[x] >= 0)x = S[x];return x;
}
  1. 邻接矩阵是对称的,只需要遍历上三角或者下三角就行。首先将邻接矩阵中的各边权值放到存放边信息的结构体中,然后对其排序
void Kruskal(MGraph G) {edge e[G.arcnum];//定义保存图中所有边的辅助数组int k = 0;//记录数组下标for (int i = 0; i < G.vexnum; i++)//①遍历邻接矩阵,将所有的边存储到辅助数组中for (int j = i + 1; j < G.vexnum; j++) {e[k].s = i;//边的起点e[k].e = j;//边的终点e[k].weight = G.Edge[i][j];//边的权值k++;//继续处理下一个数组元素}//②递增排序edge temp;//定义辅助变量 temp 保存待排序元素for (int i = 1; i < G.arcnum; i++)//直接插入排序if (e[i].weight < e[i - 1].weight) {temp = e[i];for (int j = i - 1; temp.weight < e[j].weight; j--)e[j + 1] = e[j];e[j + 1] = temp;}//③并查集操作int S[G.vexnum];//定义数组存储并查集int count = 0;//定义变量 count 记录符合要求边的数量int start, end;//定义两变量分别保存边起点和终点所在集合的根for (int i = 0; i < G.vexnum; i++)//初始化并查集数组S[i] = -1;for (int i = 0; i < G.arcnum; i++) {//遍历保存边的辅助数组start = Find(S, e[i].s);//查找边的起点所在集合的根end = Find(S, e[i].e);//查找边的终点所在集合的根if (start != end) {//若边的起点和终点在不同集合,则此条边符合要求S[start] = end;//将边的起点和终点合并为一个集合里printf("%d - %d", e[i].s, e[i].e);//打印此边count++;//更新计数变量if (count == G.vexnum - 1)//保留边的数量等于顶点数减一则任务完成break;}}
}
21 请写出求单源最短路径的 Dijkstra 算法。
  1. 之前求过单源最短路径,是对于无权图来说的,可以修改广度优先遍历实现,这次是对于有权图来说的。
  2. visited[]先初始化为0,dist[]和path[]也要初始化,dist[]中先把顶点v在邻接矩阵中的那权值复制过来,并且将path[]中初始化为v;
void Dijkstra(MGraph G, int v) {int visited[G.vexnum];//记录各顶点是否找到最短路径,找到置为1,没找到置为0int dist[G.vexnum];//记录目前源点到各顶点的最短距离int path[G.vexnum];//记录目前源点到各顶点最短路径的前驱顶点for (int i = 0; i < G.vexnum; i++)//初始化辅助数组visited[i] = 0;for (int i = 0; i < G.vexnum; i++) {//初始化辅助数组dist[i] = G.Edge[v][i];path[i] = v;}visited[v] = 1;//已确定源点 v 的最短路径int min, k;//定义变量 min 记录目前未确定最短路径顶点中的最小距离,k对应最短路径的顶点//已经确定了一个顶点,只需要在确定顶点数-1个顶点就好。for (int i = 0; i < G.vexnum - 1; i++) {//每次循环确定一个顶点的最短路径min = INT_MAX;//初始化记录最小值的变量 minfor (int j = 0; j < G.vexnum; j++) {//遍历记录距离的数组if (visited[j] == 0 && dist[j] < min) {min = dist[j];//变量 min 记录未确定最短路径顶点中的最小距离k = j;//变量 k 记录最小距离对应的顶点}}visited[k] = 1;//已确定顶点 k 的最短路径for (int j = 0; j < G.vexnum; j++) {//更新距离数组if (visited[j] == 0 && dist[k] + G.Edge[k][j] < dist[j]) {dist[j] = dist[k] + G.Edge[k][j];//发现更短路径则更新距离数组path[j] = k;//更新路径数组}}}
}
22 请写出求各顶点之间最短路径的 Floyd 算法。
  1. 用到了动态规划的算法思想:
void Floyd(MGraph G) {int i, j, k;int A[MaxSize][MaxSize];//定义二维数组记录两顶点间最短距离int path[MaxSize][MaxSize];//定义二维数组记录最短路径中转点for (i = 0; i < G.vexnum; i++) {//初始化两个二维数组for (j = 0; j < G.vexnum; j++) {A[i][j] = G.Edge[i][j];//根据顶点间的直连边计算初始顶点间最短距离path[i][j] = -1;//初始时顶点间最短路径没有中转点}}for (k = 0; k < G.vexnum; k++) {//变量 k 为此次循环的中转点for (i = 0; i < G.vexnum; i++) {//遍历两个二维数组for (j = 0; j < G.vexnum; j++) {if (A[i][k] + A[k][j] < A[i][j]) {//中转点加入是否需要更新最短距离A[i][j] = A[i][k] + A[k][j];//更新顶点 i 到 j 的最短距离path[i][j] = k;//更新顶点 i 到 j 的最短路径中转点}}}}
}
void PrintPath(int u, int v, int A[][MaxSize], int path[][MaxSize]) {//打印最短路径if (A[u][v] == INT_MAX)//顶点 u 到 v 没有路径printf("顶点%d到顶点%d没有路径", u, v);else if (path[u][v] == -1)//顶点 u 到 v 有直达路径printf(" %d - %d", u, v);else {//顶点 u 到 v 有中转路径int mid = path[u][v];//定义变量 mid 记录顶点 u 到 v 的最短路径中转点PrintPath(u, mid, A, path);//递归打印顶点 u 到中转点 mid 的最短路径PrintPath(mid, v, A, path);//递归打印中转点 mid 到顶点 v 的最短路径}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/124046.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

golang中的Interface接口 类型断言、接口赋值、空接口的使用、接口嵌套

Interface整理 文章目录 Interface整理接口嵌套接口类型断言类型判断 type-switch使用方法集与接口空接口实例 接口赋值给接口 接口是一种契约&#xff0c;实现类型必须满足它&#xff0c;它描述了类型的行为&#xff0c;规定类型可以做什么。接口彻底将类型能做什么&#xff0…

openGauss学习笔记-109 openGauss 数据库管理-管理用户及权限-角色

文章目录 openGauss学习笔记-109 openGauss 数据库管理-管理用户及权限-角色109.1 创建、修改和删除角色109.2 内置角色 openGauss学习笔记-109 openGauss 数据库管理-管理用户及权限-角色 角色是一组用户的集合。通过GRANT把角色授予用户后&#xff0c;用户即具有了角色的所有…

4.1 网络基础之网络IO

一、编写基本服务程序流程 下面介绍一个最最简单的服务程序的编写流程&#xff0c;先按照顺序介绍各个函数的参数和使用。然后在第三节用一对简单的程序对客户端与服务端通信过程进行演示。下面所有代码均在linux平台实现&#xff0c;所以可能与windows上的编程有所区别&#…

红队专题-从零开始VC++C/S远程控制软件RAT-MFC-远程桌面屏幕监控

红队专题 招募六边形战士队员[24]屏幕监控-(1)屏幕查看与控制技术的讲解图像压缩算法图像数据转换其他 [25]---屏幕监控(2)查看屏幕的实现7.1 屏幕抓图显示7.7 完善主控端 招募六边形战士队员 一起学习 代码审计、安全开发、web攻防、逆向等。。。 私信联系 [24]屏幕监控-(1…

vue源码分析(五)——vue render 函数的使用

文章目录 前言一、render函数1、render函数是什么&#xff1f; 二、render 源码分析1.执行initRender方法2.vm._c 和 vm.$createElement 调用 createElement 方法详解&#xff08;1&#xff09;区别&#xff08;2&#xff09;代码 3、原型上的_render方法&#xff08;1&#xf…

37基于MATLAB平台的图像去噪,锐化,边缘检测,程序已调试通过,可直接运行。

基于MATLAB平台的图像去噪&#xff0c;锐化&#xff0c;边缘检测&#xff0c;程序已调试通过&#xff0c;可直接运行。 37matlab边缘检测图像处理 (xiaohongshu.com)

大语言模型在天猫AI导购助理项目的实践!

本文主要介绍了Prompt设计、大语言模型SFT和LLM在手机天猫AI导购助理项目应用。 ChatGPT基本原理 “会说话的AI”&#xff0c;“智能体” 简单概括成以下几个步骤&#xff1a; 预处理文本&#xff1a;ChatGPT的输入文本需要进行预处理。 输入编码&#xff1a;ChatGPT将经过预…

Java毕业设计 SpringBoot 新能源充电桩管理系统

Java毕业设计 SpringBoot 新能源充电桩管理系统 SpringBoot 新能源充电桩管理系统 功能介绍 管理员 登录 验证码 注册 系统用户管理 普通用户管理 通知公告管理 留言管理 充电站管理 充电桩管理 充电桩预约 充电管理 订单管理 修改密码 普通用户 登录 修改个人资料 通知公告…

虹科分享|确保冻干工艺开发中精确测量和数据完整性的5步指南

文章来源&#xff1a;虹科环境监测技术 阅读原文&#xff1a;虹科分享 | 确保冻干工艺开发中精确测量和数据完整性的5步指南 一、介绍 冻干周期的工艺开发在冻干中起着至关重要的作用&#xff0c;因为它可以优化关键工艺参数&#xff0c;以实现理想的产品质量和工艺一致性。优…

MappingMongoConverter原生mongo 枚举类ENUM映射使用的是name

j.l.IllegalArgumentException: No enum constant com.xxx.valobj.TypeEnum.stringat java.lang.Enum.valueOf

*Django中的Ajax 纯js的书写样式1

搭建项目 建立一个Djano项目&#xff0c;建立一个app&#xff0c;建立路径&#xff0c;视图函数大多为render, Ajax的创建 urls.py path(index/,views.index), path(index2/,views.index2), views.py def index(request):return render(request,01.html) def index2(requ…

Python 读取 Word 详解(python-docx)

文章目录 1 概述1.1 第三方库&#xff1a;python-docx 2 新建文档2.1 空白文档2.2 标题2.3 段落2.4 文本2.5 字体2.6 图片2.7 表格 3 扩展3.1 修改文档3.2 读取文档 1 概述 1.1 第三方库&#xff1a;python-docx > pip install python-docx2 新建文档 2.1 空白文档 impo…

Python运维学习Day02-subprocess/threading/psutil

文章目录 1. 检测网段在线主机2. 获取系统变量的模块 psutil 1. 检测网段在线主机 import subprocessdef checkIP(ip):cmd fping -n 1 -w 1 {ip}null open(nlll,modewb)status subprocess.call(cmd,shellTrue,stdoutnull,stderrnull)if status 0:print(f"主机[{ip}]在…

ue5 右击.uproject generator vs project file 错误

出现如下错误 Unable to find valid 14.31.31103 C toolchain for VisualStudio2022 x64 就算你升级了你的 vs installer 也不好使 那是因为 在C:\Users\{YourUserName}\AppData\Roaming\Unreal Engine\UnrealBuildTool\BuildConfiguration.xml 这个缓存配置文件中写死了 14…

leetcode-链表

链表是一个用指针串联起来的线性结构&#xff0c;每个结点由数据域和指针域构成&#xff0c;指针域存放的是指向下一个节点的指针&#xff0c;最后一个节点指向NULL&#xff0c;第一个结点称为头节点head。 常见的链表有单链表、双向链表、循环链表。双向链表就是多了一个pre指…

​轻量应用服务器有什么优势?如何评价亚马逊云科技轻量应用服务器?

什么是轻量应用服务器&#xff1f; 随着如今各行各业对云计算的需求越来越多&#xff0c;云服务器也被越来越多的企业所广泛采用。其中&#xff0c;轻量应用服务器是一种简单、高效、可靠的云计算服务&#xff0c;能够为开发人员、企业和个人提供轻量级的虚拟专用服务器&#…

AWTK 液体流动效果控件发布

液体流动效果控件。 主要特色&#xff1a; 支持水平和垂直方向。支持正向和反向流动。支持设置头尾的图片。支持设置流动的图片。支持设置速度的快慢。支持启停操作。 准备 获取 awtk 并编译 git clone https://github.com/zlgopen/awtk.git cd awtk; scons; cd -运行 生成…

JMeter简单使用

JMeter是一个功能强大的开源性能测试工具&#xff0c;用于对各种应用程序、协议和服务器进行性能和负载测试。它被广泛用于测试Web应用程序的性能&#xff0c;并可以模拟多种负载条件和行为。 JMeter使用 添加线程组 设置线程组的配置 设置请求 配置请求 添加监听器 查看压…

JVM虚拟机:Java对象的头信息有什么?

本文重点 在前面的课程中,我们学习了对象头,其中对象头包含Mark Word和class pointer,当然数组还会有一个数组长度。本文主要分析Mark Work中包含的信息。 Mark Word 以下两张图是一个意思: 32位 32位 64位 以上就是Mark Word会存储的信息,这个意思是说Java对象在不同…

计网小题题库整理第一轮(面向期末基础)(2)

该系列第二期&#xff0c;第一期链接在这~ 计网小题题库整理第一轮&#xff08;面向期末基础&#xff09;&#xff08;1&#xff09;https://blog.csdn.net/jsl123x/article/details/134030486?spm1001.2014.3001.5501 一.选择题 1、Internet的前身是 &#xff08;C &#x…