6.6 图的应用
拓扑排序 的对象是 :有向无环图 (DAG图)中的 AOV-网,带不带权均可;
关键路径 的对象 :(带权)有向无环网,属于有向无环图 (DAG图)中的 AOE-网,一定带权。
6.6.3 拓扑排序
#include <stdio.h>
#include <stdlib.h>
#define MVNum 100
#define OK 1
#define ERROR -1
typedef char VerTexType;
typedef int OtherInfo;
typedef int Status;
typedef struct ArcNode
{
int adjvex;
OtherInfo info;
struct ArcNode* nextarc;
}ArcNode; //边结点
typedef struct VNode
{
VerTexType data;
ArcNode* firstarc;
}VNode,AdjList[MVNum]; //表头结点
typedef struct
{
AdjList vertices; //变ex为ices
int vexnum;
int arcnum;
}ALGraph;
typedef struct stackNode
{
int data; //存入度为0的顶点的下标
struct stackNode* next;
}stackNode,*LinkStack;
int indegree[MVNum]; //存放各顶点的入度数量
void CreateDG(ALGraph& G);
int LocateVex(ALGraph G, VerTexType e);
void printALGraph(ALGraph G);
void InitStack(LinkStack &S);
void Push(LinkStack &S, int e);
void Pop(LinkStack &S, int& e);
int EmptyStack(LinkStack S);
void FindinDegree(ALGraph G, int indegree[]);
Status TopologicalSort(ALGraph G, int topo[]);
int main()
{
ALGraph G = { {'\0',NULL},0,0 };
int topo[MVNum] = { 0 };
CreateDG(G);
printALGraph(G);
TopologicalSort(G,topo);
return 0;
}
//创建有向图的邻接表
void CreateDG(ALGraph& G)
{
int i = 0;
int j = 0;
int k = 0;
VerTexType v1 = '\0';
VerTexType v2 = '\0';
ArcNode* p = NULL;
printf("请输入总顶点数:");
scanf_s(" %d", &G.vexnum);
printf("请输入总边数:");
scanf_s(" %d", &G.arcnum);
for (i = 0; i < G.vexnum; i++)
{
printf("请输入第%d顶点的值:", i + 1);
scanf_s(" %c", &G.vertices[i].data);
G.vertices[i].firstarc = NULL;
}
for (k = 0; k < G.arcnum; k++)
{
printf("请输入第%d条边的两个顶点:",k+1);
scanf_s(" %c %c", &v1, sizeof(VerTexType), &v2, sizeof(VerTexType));
i = LocateVex(G, v1);
j = LocateVex(G, v2);
p = (ArcNode*)malloc(sizeof(ArcNode));
p->adjvex = j;
p->nextarc = G.vertices[i].firstarc;
G.vertices[i].firstarc = p;
}
}
int LocateVex(ALGraph G, VerTexType e)
{
int i = 0;
for (i = 0; i < G.vexnum && G.vertices[i].data != e; i++)
{
;
}
return i;
}
void printALGraph(ALGraph G)
{
int i = 0;
ArcNode* p = NULL;
for (i = 0; i < G.vexnum; i++)
{
printf("\n第%d个顶点为:%c", i + 1,G.vertices[i].data);
p = G.vertices[i].firstarc;
if (!p)
{
printf("\n没有以该顶点为出度的弧。");
continue;
}
printf("\n以该顶点为出度,其所有邻接点为:");
while (p)
{
printf("%c ", G.vertices[p->adjvex].data);;
p = p->nextarc;
}
}
}
//存入各顶点的入度弧数量
void FindinDegree(ALGraph G, int indegree[])
{
int i = 0;
ArcNode* p = NULL;
int num = 0;
int m = 0; //indegree数组的下标
for (m = 0; m < G.vexnum; m++)
{
for (i = 0; i < G.vexnum; i++)
{
p = G.vertices[i].firstarc;
while (p)
{
if (p->adjvex == m)
{
num++;
}
p = p->nextarc;
}
}
indegree[m] = num;
num = 0;
}
printf("\n\nindegree数组初始元素为:");
for (i = 0; i < G.vexnum; i++)
{
printf("%d ", indegree[i]);
}
}
//初始化栈
void InitStack(LinkStack &S)
{
S = NULL;
}
//入栈
void Push(LinkStack &S, int e)
{
struct stackNode* p = (struct stackNode*)malloc(sizeof(struct stackNode));
if (!p)
{
printf("元素入栈时,内存分配失败。\n");
return;
}
p->data = e;
p->next = S;
S = p;
}
//出栈
void Pop(LinkStack &S, int& e)
{
if (!S)
{
printf("元素出栈时,栈为空。\n");
return;
}
struct stackNode* p = S;
e = p->data;
S = p->next;
free(p);
p = NULL;
}
//判断栈空
int EmptyStack(LinkStack S)
{
if (!S)
{
return 1;
}
else
{
return 0;
}
}
//若 G 无回路,则生成 G 的一个拓扑序列 topo[] 并返回 OK, 否则返回 ERROR
Status TopologicalSort(ALGraph G, int topo[])
{
int i = 0;
int j = 0;
int m = 0; //对输出顶点计数
int k = 0;
ArcNode* p = NULL;
FindinDegree(G, indegree); //indegree数组中存入各顶点的入度弧数量
LinkStack S = NULL;
InitStack(S);
for (i = 0; i < G.vexnum; ++i)
{
if (!indegree[i])
{
Push(S, i);
}
}
while (!EmptyStack(S))
{
Pop(S, i);
topo[m] = i;
++m;
p = G.vertices[i].firstarc;
while (p != NULL)
{
k = p->adjvex;
--indegree[k];
if (!indegree[k])
{
Push(S, k);
}
p = p->nextarc;
}
}
if (m < G.vexnum)
{
printf("\n\n邻接表中存在回路,生成拓扑序列失败。");
return ERROR;
}
else
{
printf("\n\n该邻接表的拓扑序列为:");
for (i = 0; i < G.vexnum; i++)
{
printf("%c ", G.vertices[topo[i]].data);
}
return OK;
}
}
6.6.4 关键路径
#include <stdio.h>
#include <stdlib.h>
#define MVNum 100
#define OK 1
#define ERROR -1
typedef char VerTexType;
typedef int ArcType;
typedef int Status;
typedef struct ArcNode
{
int adjvex;
ArcType weight;
struct ArcNode* nextarc;
}ArcNode; //边结点
typedef struct VNode
{
VerTexType data;
ArcNode* firstarc;
}VNode, AdjList[MVNum]; //表头结点
typedef struct
{
AdjList vertices; //变ex为ices
int vexnum;
int arcnum;
}ALGraph;
typedef struct stackNode
{
int data; //存入度为0的顶点的下标
struct stackNode* next;
}stackNode, * LinkStack;
int indegree[MVNum]; //拓扑排序,存放各顶点的入度数量
int ve[MVNum]; //事件Vi的最早发生时间
int vl[MVNum]; //事件Vi的最迟发生时间
int topo[MVNum]; //记录拓扑序列的顶点序号
void CreateDG(ALGraph& G);
int LocateVex(ALGraph G, VerTexType e);
void printALGraph(ALGraph G);
void InitStack(LinkStack& S);
void Push(LinkStack& S, int e);
void Pop(LinkStack& S, int& e);
int EmptyStack(LinkStack S);
void FindinDegree(ALGraph G, int indegree[]);
Status TopologicalSort(ALGraph G);
Status CriticalPath(ALGraph G);
int main()
{
ALGraph G = { {'\0',NULL},0,0 };
int topo[MVNum] = { 0 };
CreateDG(G);
printALGraph(G);
CriticalPath(G);
return 0;
}
//创建有向图的邻接表
void CreateDG(ALGraph& G)
{
int i = 0;
int j = 0;
int k = 0;
VerTexType v1 = '\0';
VerTexType v2 = '\0';
ArcNode* p = NULL;
ArcType w = 0;
printf("请输入总顶点数:");
scanf_s(" %d", &G.vexnum);
printf("请输入总边数:");
scanf_s(" %d", &G.arcnum);
for (i = 0; i < G.vexnum; i++)
{
printf("请输入第%d顶点的值:", i + 1);
scanf_s(" %c", &G.vertices[i].data);
G.vertices[i].firstarc = NULL;
}
for (k = 0; k < G.arcnum; k++)
{
printf("请输入第%d条边的两个顶点:", k + 1);
scanf_s(" %c %c", &v1, sizeof(VerTexType), &v2, sizeof(VerTexType));
printf("请输入第%d条边的权值:", k + 1);
scanf_s(" %d", &w);
i = LocateVex(G, v1);
j = LocateVex(G, v2);
p = (ArcNode*)malloc(sizeof(ArcNode));
p->adjvex = j;
p->weight = w;
p->nextarc = G.vertices[i].firstarc;
G.vertices[i].firstarc = p;
}
}
int LocateVex(ALGraph G, VerTexType e)
{
int i = 0;
for (i = 0; i < G.vexnum && G.vertices[i].data != e; i++)
{
;
}
return i;
}
void printALGraph(ALGraph G)
{
int i = 0;
ArcNode* p = NULL;
for (i = 0; i < G.vexnum; i++)
{
printf("\n第%d个顶点为:%c", i + 1, G.vertices[i].data);
p = G.vertices[i].firstarc;
if (!p)
{
printf("\n没有以该顶点为出度的弧。");
continue;
}
printf("\n以该顶点为出度,其所有邻接点及权值为:");
while (p)
{
printf("%c【%d】 ", G.vertices[p->adjvex].data,p->weight);
p = p->nextarc;
}
}
}
//存入各顶点的入度弧数量
void FindinDegree(ALGraph G, int indegree[])
{
int i = 0;
ArcNode* p = NULL;
int num = 0;
int m = 0; //indegree数组的下标
for (m = 0; m < G.vexnum; m++)
{
for (i = 0; i < G.vexnum; i++)
{
p = G.vertices[i].firstarc;
while (p)
{
if (p->adjvex == m)
{
num++;
}
p = p->nextarc;
}
}
indegree[m] = num;
num = 0;
}
printf("\n\nindegree数组初始元素为:");
for (i = 0; i < G.vexnum; i++)
{
printf("%d ", indegree[i]);
}
}
//初始化栈
void InitStack(LinkStack& S)
{
S = NULL;
}
//入栈
void Push(LinkStack& S, int e)
{
struct stackNode* p = (struct stackNode*)malloc(sizeof(struct stackNode));
if (!p)
{
printf("元素入栈时,内存分配失败。\n");
return;
}
p->data = e;
p->next = S;
S = p;
}
//出栈
void Pop(LinkStack& S, int& e)
{
if (!S)
{
printf("元素出栈时,栈为空。\n");
return;
}
struct stackNode* p = S;
e = p->data;
S = p->next;
free(p);
p = NULL;
}
//判断栈空
int EmptyStack(LinkStack S)
{
if (!S)
{
return 1;
}
else
{
return 0;
}
}
//若 G 无回路,则生成 G 的一个拓扑序列 topo[] 并返回 OK, 否则返回 ERROR
Status TopologicalSort(ALGraph G)
{
int i = 0;
int j = 0;
int m = 0; //对输出顶点计数
int k = 0;
ArcNode* p = NULL;
FindinDegree(G, indegree); //indegree数组中存入各顶点的入度弧数量
LinkStack S = NULL;
InitStack(S);
for (i = 0; i < G.vexnum; ++i)
{
if (!indegree[i])
{
Push(S, i);
}
}
while (!EmptyStack(S))
{
Pop(S, i);
topo[m] = i;
++m;
p = G.vertices[i].firstarc;
while (p != NULL)
{
k = p->adjvex;
--indegree[k];
if (!indegree[k])
{
Push(S, k);
}
p = p->nextarc;
}
}
if (m < G.vexnum)
{
printf("\n\n邻接表中存在回路,生成拓扑序列失败。");
return ERROR;
}
else
{
printf("\n\n该邻接表的拓扑序列为:");
for (i = 0; i < G.vexnum; i++)
{
printf("%c ", G.vertices[topo[i]].data);
}
return OK;
}
}
//G为邻接表存储的有向网,输出G的各项关键活动
Status CriticalPath(ALGraph G)
{
if (!TopologicalSort(G))
{
printf("拓扑排序算法调用失败。\n");
return ERROR;
}
int n = G.vexnum;
int i = 0;
int j = 0;
int k = 0;
ArcNode* p = NULL;
int e = 0;
ArcType l = 0;
//给每个事件的最早发生时间置初值0
for (i = 0; i < n; i++)
{
ve[i] = 0;
}
//按拓扑次序求每个事件的最早发生时间ve
for (i = 0; i < n; i++)
{
k = topo[i];
p = G.vertices[k].firstarc;
while (p)
{
j = p->adjvex;
if (ve[j] < ve[k] + p->weight)
{
ve[j] = ve[k] + p->weight;
}
p = p->nextarc;
}
}
//给每个事件的最迟发生时间置初值ve[n-1]
for (i = 0; i < n; i++)
{
vl[i] = ve[n - 1];
}
//按逆拓扑次序求每个事件的最迟发生时间vl
for (i = n - 1; i >= 0; i--)
{
k = topo[i];
p = G.vertices[k].firstarc;
while (p)
{
j = p->adjvex;
if (vl[k] > vl[j] - p->weight)
{
vl[k] = vl[j] - p->weight;
}
p = p->nextarc;
}
}
printf("\n\n该AOE-网的关键活动是:");
//判断每一活动是否为关键活动
for (i = 0; i < n; i++)
{
p = G.vertices[i].firstarc;
while (p)
{
j = p->adjvex;
e = ve[i];
l = vl[j] - p->weight;
if (e == l)
{
printf(" < %c , %c > ", G.vertices[i].data, G.vertices[j].data);
}
p = p->nextarc;
}
}
return OK;
}
6.7 案例分析与实现
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTEX_NUM 20
bool visited[MAX_VERTEX_NUM];
//标记某个顶点是否被访问过。数组中初值均为 "false",如果被访问过,则置其相应的分量为 "true"
typedef enum { Eunvisited, Evisited } EVisitIf; //标记某条边edge是否被搜索过
typedef int InfoType; //假设在边结点中的数据域存储边的权值,且边的权值类型为整型
typedef char VerTexType; //假设顶点的数据类型为字符型
typedef struct EBox
{
EVisitIf mark; //mark为标志域,可用以标记该条边是否被搜索过
int ivex;
int jvex;
struct EBox* ilink;
struct EBox* jlink;
InfoType* info;
}EBox;
typedef struct VexBox
{
VerTexType data;
EBox* firstedge;
}VexBox;
typedef struct
{
VexBox adjmulist[MAX_VERTEX_NUM]; //表头向量
int vexnum; //顶点总数
int edgenum; //总边数
}AMLGraph; //图的信息
typedef struct QNode
{
int data; //因为每个顶点后面还跟着边的链表,因此队列中只保存顶点在G.adjmulist中的位置数
struct QNode* next;
}QNode, * QNodeptr;
typedef struct
{
QNodeptr front;
QNodeptr rear;
}LinkQueue; //链队
void CreateAMLGraph(AMLGraph& G);
int LocateVex(AMLGraph& G, VerTexType v);
void printAMLGraph(AMLGraph& G);
void InitQueue(LinkQueue& Q);
void EnQueue(LinkQueue& Q, int e);
int DeQueue(LinkQueue& Q);
int EmptyQueue(LinkQueue& Q);
void SixDegree_BFS(AMLGraph G, int Start);
int FirstAdjVex(AMLGraph G, int i);
int NextAdjVex(AMLGraph G, int i, int w);
int main()
{
AMLGraph G = { {'\0',NULL,NULL}, 0,0 };
int i = 0;
int j = 0;
CreateAMLGraph(G);
printAMLGraph(G);
for (i = 0; i < G.vexnum; i++)
{
SixDegree_BFS(G, i+1);
//将visited数组初始化(重置visited数组)
for (j = 0; j < G.vexnum; j++)
{
visited[j] = false;
}
}
return 0;
}
//采用邻接多重表表示法创建无向图
void CreateAMLGraph(AMLGraph& G)
{
int i = 0;
int j = 0;
int k = 0;
VerTexType v1 = 0;
VerTexType v2 = 0;
EBox* p1 = NULL;
printf("请输入人数(无向图的总顶点数):");
scanf_s(" %d", &G.vexnum);
printf("请输入两个人“认识”的关系数(无向图的总边数):");
scanf_s(" %d", &G.edgenum);
for (i = 0; i < G.vexnum; ++i)
{
printf("请输入第%d个人的标识(第%d个顶点的值):", i + 1,i+1);
scanf_s(" %c", &(G.adjmulist[i].data));
G.adjmulist[i].firstedge = NULL;
}
for (k = 0; k < G.edgenum; k++)
{
printf("请输入第%d对认识的两个人(第%d条边依附的两个顶点:) ", k + 1, k + 1);
scanf_s(" %c %c", &v1, sizeof(VerTexType), &v2, sizeof(VerTexType));
i = LocateVex(G, v1);
j = LocateVex(G, v2);
p1 = (EBox*)malloc(sizeof(EBox));
p1->ivex = i;
p1->jvex = j;
p1->ilink = G.adjmulist[i].firstedge;
G.adjmulist[i].firstedge = p1;
p1->jlink = G.adjmulist[j].firstedge;
G.adjmulist[j].firstedge = p1;
}
}
//在G的顶点表adjmulist中获取字符v的下标(数组G.xlist的下标从0开始)
int LocateVex(AMLGraph& G, VerTexType v)
{
int i = 0;
for (i = 0; i < G.vexnum && (G.adjmulist[i].data != v); ++i)
{
;
}
return i;
}
//打印邻接多重表
void printAMLGraph(AMLGraph& G)
{
int i = 0;
EBox* pMove = NULL;
for (i = 0; i < G.vexnum; i++)
{
printf("\n第%d个人(第%d个顶点)为:%c", i + 1, i + 1, G.adjmulist[i].data);
pMove = G.adjmulist[i].firstedge;
if (pMove)
{
printf("\n与第%d个人认识的人(与第%d个顶点相邻接的顶点)有:", i + 1, i + 1);
while (pMove)
{
if (pMove->ivex == i)
{
printf("%c ", G.adjmulist[pMove->jvex].data);
pMove = pMove->ilink;
}
else if (pMove->jvex == i)
{
printf("%c ", G.adjmulist[pMove->ivex].data);
pMove = pMove->jlink;
}
}
}
else
{
printf("\n没有与第%d个人认识的人(没有与第%d个顶点相邻接的顶点)。", i + 1, i + 1);
}
}
}
//初始化链队
void InitQueue(LinkQueue& Q)
{
Q.front = (QNodeptr)malloc(sizeof(QNode));
if (!Q.front)
{
printf("初始化链队时,内存分配失败。\n");
return;
}
Q.rear = Q.front;
Q.front->next = NULL;
}
//入队
void EnQueue(LinkQueue& Q, int e)
{
QNodeptr p = (QNodeptr)malloc(sizeof(QNode));
if (!p)
{
printf("元素入队时,新结点内存分配失败。\n");
return;
}
p->data = e;
p->next = NULL;
Q.rear->next = p;
Q.rear = p;
}
//出队
int DeQueue(LinkQueue& Q)
{
if (EmptyQueue(Q))
{
printf("元素出队时,链队为空。\n");
return 0;
}
QNodeptr p = Q.front->next;
int e = p->data;
Q.front->next = p->next;
if (p == Q.rear)
{
Q.rear = Q.front;
}
free(p);
p = NULL;
return e;
}
//判空
int EmptyQueue(LinkQueue& Q)
{
if (Q.front == Q.rear)
{
return 1;
}
else
{
return 0;
}
}
//从第Start个顶点出发,通过广度优先搜索方法遍历G来验证六度空间理论
void SixDegree_BFS(AMLGraph G, int Start)
{
int Visit_Num = 0;
LinkQueue Q = { NULL,NULL };
int len = 1;
int u = 0;
int w = 0;
visited[Start-1] = true;
InitQueue(Q);
EnQueue(Q, Start);
for (len = 1;len<=7 && !EmptyQueue(Q);len++)
{
u = DeQueue(Q); //第一次while循环中:u是队头,即e
for (w = FirstAdjVex(G, u); w >= 0; w = NextAdjVex(G, u, w + 1))
{
if (!visited[w])
{
visited[w] = true;
Visit_Num++;
EnQueue(Q, w + 1);
}
}
}
//printf("\n从第%d个顶点%c出发,与其之间路径长度不超过7的顶点数量为%d,占其余所有顶点数量%d的百分比为:%.2f%% ", Start, G.adjmulist[Start-1].data, Visit_Num, G.vexnum-1, (double)Visit_Num * 100 / (double)(G.vexnum-1));
printf("\n经历不超过六个人,第%d个人%c可认识的总人数为%d,占该人际关系网络中其余总人数%d 的 %.2f%% ", Start, G.adjmulist[Start - 1].data, Visit_Num, G.vexnum-1, (double)Visit_Num * 100 / (double)(G.vexnum-1));
}
//查找第i个顶点的第一个邻接点下标
int FirstAdjVex(AMLGraph G, int i)
{
EBox* p = G.adjmulist[i - 1].firstedge;
if (p)
{
if (i - 1 == p->ivex)
{
return p->jvex;
}
else if (i - 1 == p->jvex)
{
return p->ivex;
}
else
{
return -1;
}
}
else
{
return -1;
}
}
//查找第i个顶点相对于 第w个顶点 邻接点的下一个邻接点的下标
int NextAdjVex(AMLGraph G, int i, int w)
{
EBox* p = G.adjmulist[i - 1].firstedge;
// 寻找当前边
while (p)
{
if ((i - 1 == p->ivex && w - 1 == p->jvex) || (i - 1 == p->jvex && w - 1 == p->ivex))
break;
p = (i - 1 == p->ivex) ? p->ilink : p->jlink;
//注意 if 及 p向后移动 的命令顺序
}
if (!p)
return -1; // 如果没有找到当前边则返回 -1
// 移动到下一个边
p = (i - 1 == p->ivex) ? p->ilink : p->jlink;
// 查找下一个邻接顶点
if (p)
{
if (i - 1 == p->ivex)
return p->jvex;
else if (i - 1 == p->jvex)
return p->ivex;
}
return -1; // 如果没有找到下一个邻接顶点则返回 -1
}