目录
图的存储结构
邻接矩阵
代码如下:
#define MaxInt 32767
#define MVNum 10
//图的存储结构
typedef struct {
char vers[MVNum];
int arcs[MVNum][MVNum];
int vexnum, arcnum;
int visited[MVNum];
}AMGraph;
//图的邻接矩阵
int CreateUDN(AMGraph* G) {
char v1, v2;
int w,tmp1,tmp2;
printf("请输入结点数以及边数\n");
scanf_s("%d%d",&G->vexnum, &G->arcnum);
for (int i = 0; i < G->vexnum; i++) {
printf("请输入第%d个结点\n", i + 1);
scanf_s("%c\n", &G->vers[i], 1);
}
for (int i = 0; i < G->vexnum;i++) {
for (int j = 0; j < G->vexnum;j++) {
G->arcs[i][j] = MaxInt;
printf("邻接矩阵初始化完成\n");
}
}
for (int k = 0; k < G->arcnum;k++) {
printf("请输入第%d个边(v1 v2 权值)\n",k+1);
scanf_s("%c%c%d",&v1,1,&v2,1,&w);
tmp1 = LocateVex(*G, v1);
tmp2 = LocateVex(*G, v2);
G->arcs[tmp1][tmp2] = w;
G->arcs[tmp2][tmp1] = w;
}
return 1;
}
优缺点:
邻接表
代码:
//图的存储结构(邻接表)
//边结点
typedef struct ArcNode{
int adjvex; //该边所指向的顶点的位置
struct ArcNode* nextarc; //指向下一条边的指针
}ArcNode;
//头节点
typedef struct VNode {
char data;
ArcNode* fristarc;
}VNode,AdjList[MVNum];
//邻接表
typedef struct {
AdjList vertices;
int vexnum, arcnum; //图的当前顶点数和边数
}ALGraph;
十字链表
邻接多重表
图一共有四种存储方式,在考研和插本中只考前两种,后两种更好但是更复杂,故而不在此说明
图的遍历算法
图的广度优先算法(BFS)
①访问第一个结点v,然后将visited数组标记为已访问并且将其放入队列中
②然后判断队列是否空,不为空就取出第一个数据,然后将其相邻结点依次访问并放入队列中,且标记已访问。重复②这个步骤直至结束
③以防图出现非连通图,所以需要写一个BFSTraverse方法循环的查看visited数组是否还有未访问的结点,有的话将该节点继续调用BFS,直至全部结点都被访问
邻接矩阵实现
//图的广度优先遍历
void BFS(AMGraph G, SqQueue Q,int v) {
visit(v);
G.visited[v] = 1;
EnQueue(&Q, v);
while (!QueueEmpty(Q)) {
DeQueue(&Q, &v);
for (int w = FristNeighbor(G, v); w >= 0;w = NextNeighbor(G,v,w)) {
if (!G.visited[w]) {
visit(w);
G.visited[w] = 1;
EnQueue(&Q, w);
}
}
}
}
void BFSTraverse(AMGraph G,SqQueue Q) {
for (int i = 0; i < G.vexnum; i++) {
G.visited[i] = 0;
}
for (int i = 0; i < G.vexnum;i++) {
if (!G.visited[i]) {
BFS(G,Q,i);
}
}
}
图的深度优先算法(DFS)
①先访问第一个点
②然后for循环找他的邻接结点,并且将他的结点都调用DFS算法,这样递归调用就会使其一直访问下去,直到它最深的结点然后回溯回去。
③同时也可能出现非完全连通图,所以要循环检查visited数组是否有没有访问到的结点,再次执行一遍。
(1)邻接矩阵实现
//图的深度优先遍历
void DFS(AMGraph G,int v) {
visit(v);
G.visited[v] = 1;
for (int w = FristNeighbor(G, v); w > 0;w = NextNeighbor(G,v,w)) {
if (!G.visited[w]) {
DFS(G, w);
}
}
}
void DFSTraverse(AMGraph G) {
for (int i = 0; i < G.vexnum; i++) {
G.visited[i] = 0;
}
for (int i = 0; i < G.vexnum; i++) {
if (!G.visited[i]) {
DFS(G,i);
}
}
}
图的最小生成树的算法
普里姆算法(Prim)
清晰步骤:
代码:
void MiniSpanTree_Prim(AMGraph G,char u) {
int k = LocateVex(G, u);
char u0,v0;
//初始化closedge数组
for (int j = 0; j < G.vexnum;j++) {
if (j != k) {
closedge[j].adjvex = u;
closedge[j].lowcost = G.arcs[k][j];
}
}
//将u"加入"到U中(实际这个U不存在,只是数组中lowcost为0的都默认为加入到U中了)
closedge[k].lowcost = 0;
//循环遍历每个结点
for (int i = 1; i < G.vexnum;i++) {
//将数组中最小的权值的那个结点下标找到
k = Min(closedge);
//将u和k结点打印出去
u0 = closedge[k].adjvex;
v0 = G.vers[k];
printf("%c-%c",u0,v0);
//将k结点加入U中
closedge[k].lowcost = 0;
//循环将closedge数组更新
for (int j = 0; j < G.vexnum;j++) {
if (G.arcs[k][j]<closedge[j].lowcost) {
closedge[j].adjvex = G.vers[k];
closedge[j].lowcost = G.arcs[k][j];
}
}
}
}
克鲁斯卡尔算法(Kruskal)
准备工作:
步骤:
代码:
//Kruskal算法
struct {
char Head;
char Tail;
int lowcost;
}Edge[arcnum];
int Vexset[MVNum];
void MiniSpanTree_Kruskal(AMGraph G) {
int v1, v2,vs1,vs2;
//排序Edge
sort(Edge);
//初始化连通分量数组
for (int i = 0; i < G.vexnum;i++) {
Vexset[i] = i;
}
//循环遍历Edge
for (int i = 0; i < G.arcnum;i++) {
v1 = LocateVex(G,Edge[i].Head);
v2 = LocateVex(G,Edge[i].Tail);
vs1 = Vexset[v1];
vs2 = Vexset[v2];
if (vs1 != vs2) {
printf("%c-%c",v1,v2);
for (int i = 0; i < G.vexnum;i++) {
if (Vexset[i] == vs2) {
Vexset[i] = vs1;
}
}
}
}
}
二者对比:
图的最短路径算法
从某个源点到其余顶点的最短路径
广度优先算法(BFS)
迪杰斯特拉算法(Dijstra)
每一对顶点之间的最短路径
弗洛伊德算法(Floyed)
三种算法比对:
图的拓扑排序
拓扑排序定义:
排序思路:
从第一个入度为0的结点开始,将其取出,然后将他的结点和边去掉,然后再重复前面的操作就入度为0的结点如此操作,一直下去就可以得到拓扑排序了。
代码阶段:
这段代码是基于邻接表实现的,左边为邻接表的结构,右边就是代码本身
准备工作:
①indegree数组(当前结点的入度为多少)
②print数组(拓扑排序的结果)
③栈(保存入度为0的结点)
步骤:
①将图、栈、两个数组都初始化好
②遍历indegree数组,将入度为0的结点压入栈中
③当栈不为空的时候,将栈顶元素i出栈并且将其输出到print数组然后count++
④然后for循环邻接表中i指向的结点,将指向结点的入度都减一(indegree减一),并且将入度为0的顶点压入栈中,然后一直重复③。
⑤判断count是否小于顶点数,也就是说print里面是否有全部的顶点输出了,目的是判断成功与否。
逆拓扑排序:
代码实现:
使用DFS来实现逆拓扑排序
总结:
图的关键路径
概念:
AOE图:
关键路径和关键活动:
步骤:
总结: