邻接表的存储结构(ADJacency List)
//邻接表(Adjacency List)
//链表中结点的类型
typedef struct ArcNode{
int adjvex; //该弧指向的顶点的位置
struct ArcNode *nextarc; //指向下一个弧结点的指针
InfoType info; //该弧的相关信息的指针
}ArcNode;
//头结点的类型
typedef struct VNode{
ElemType data; //顶点的信息
ArcNode *firstarc; //指向第一条依附该顶点的弧的指针
int degree; //存放顶点的入度
}VNode, AdjList[N+1]; //AdjList[0]可以不存放顶点信息
typedef struct {
AdjList adjlist; //邻接表
int vexnum, arcnum; //图的顶点数和弧数
}ALGraph;
邻接表的方式构造图(无向图)
//邻接表的方式构造无向图
int LocateVex_AL(ALGraph G, ElemType v)
{ //找到顶点v在邻接表中的位置
int i;
for(i = 1; i<=G.vexnum; i++)
{
if(v == G.adjlist[i].data)
return i;
}
return -1;
}
void Create_ALGraph(ALGraph &G)
{
int i, j;
ElemType v1, v2;
int a1, a2;
scanf("%d %d", &G.vexnum, &G.arcnum);//输入顶点数和边数
//读入顶点信息,同时将顶点下一个指针初始化为空,入度初始化为零
for(i = 1;i<=G.vexnum; i++)
{
scanf("%d", &G.adjlist[i]);
G.adjlist[i].firstarc = NULL;
G.adjlist[i].degree = 0;
}
//读入边的信息
for(i = 1; i<=G.arcnum; i++)
{
scanf("%d %d", &v1, &v2);
a1 = LocateVex_AL(G, v1);
a2 = LocateVex_AL(G, v2);
ArcNode * p;
p = (ArcNode*)malloc(sizeof(ArcNode));
p->adjvex = a2;
p->nextarc = G.adjlist[a1].firstarc;
G.adjlist[a1].firstarc = p;
G.adjlist[a2].degree++;
//图为无向图,所以弧要做两次插入
ArcNode * q;
q = (ArcNode*)malloc(sizeof(ArcNode));
q->adjvex = a1;
q->nextarc = G.adjlist[a2].firstarc;
G.adjlist[a2].firstarc = q;
G.adjlist[a1].degree++;
}
}
图的遍历
一、深度优先遍历(Depth_First Search)
算法思想:
1.从图中某一个顶点v出发,访问此顶点,然后依次从v的未被访问过的邻接顶点出发深度遍历图,直到图中所有与v连通的顶点都被访问过;
2.若此时图中还有顶点未被访问(即非连通图),则另选一个未被访问过的顶点起始点,重复此过程,直到所有顶点都被访问过。
//深度优先搜索
void DFS(ALGraph G, int visited[], int v)
{
int i;
int t;
ArcNode *p;
p = G.adjlist[v].firstArc;
printf("%c ", G.adjlist[v].data);
visited[v] = 1;
while(p)
{
if(visited[p->adjvex] == 0)
DFS(G, visited, p->adjvex);
p = p->nextArc;
}
}
void DFSTravel(ALGraph G)
{
int i;
int visited[N]= {0}; //值为1表示该店访问过
for(i = 0; i<G.vexnum; i++)
{
if(visited[i] == 0)
DFS(G, visited, i);
}
}
二、广度优先遍历(Broadth_First Search)
//广度优先搜索,类似于树的层次遍历
void BFSTravel(ALGraph G)
{
//利用队列
int i;
int visited[N] = {0};
int Q[N];
int rear = 0; //队尾指针
int front = 0; //队头指针
int t; //用来接收出队元素
for(i = 0; i<G.vexnum; i++)
{
if(visited[i] == 0)
{
printf("%c ", G.adjlist[i].data);
visited[i] = 1;
Q[rear++] = i;//进队
while(rear != front)//队不空
{
t = Q[front++];
ArcNode *p;
p = (ArcNode*)malloc(sizeof(ArcNode));
p = G.adjlist[t].firstArc;
while(p)
{
if(visited[p->adjvex]==0)
{
printf("%c ", G.adjlist[p->adjvex].data);
visited[p->adjvex] = 1;
Q[rear++] = p->adjvex;
}
p = p->nextArc;
}
}
}
}
}
图的常见算法
最小生成树
- 构造连通网的一棵生成树,使得总的代价最少——最小生成树的问题。
一、普利姆算法(Prim)
算法思想:
-
定义一个辅助结构数组 closege[n],记录从U 到 V-U 具有最小代价的边。 对每个还没有加入到生成树的顶点vi ∈V-U , 计算:
closege[i-1].lowcost = Min{ cost(vi , u) | u∈U } closege[i-1].vexs = { u | 若 cost(vi , u ) 最小 }
-
求 closege[k].lowcost 最小的顶点vk+1,并加入到U中,同时把 边( vk+1 , closege[k].vexs )加入到生成树中;
-
重复1,2过程。直到 U = V。
//最小生成树
//普利姆算法(Prim)
struct {
int lowcost; //closedge[i].lowcost表示当前与第i个顶点相连的的权值最小的边
int vex; //closedeg[i].vex表示与第i个顶点相连的顶点的位置
}closedge[N];
void MiniTree_Prim(ALGraph G, int v) //从第v个顶点开始
{
int i;
int j;
int visited[N]={0}; //0表示未访问,1表示已访问
printf("%c\n", G.adjlist[v].data);
visited[v] = 1;
//初始化closedge
for(i = 0; i<=G.vexnum; i++)
{
closedge[i].lowcost = MAX;
closedge[i].vex = v;
}
ArcNode *p;
p = G.adjlist[v].firstArc;
while(p)
{
closedge[p->adjvex].lowcost = p->info;
p = p->nextArc;
while(p)
{
closedge[p->adjvex].lowcost = p->info;
p = p->nextArc;
}
}
//找出closedge中最小的边,输出并进行更新
for(j = 1; j<G.vexnum; j++)
{
int min = MAX;
int t;
for(i = 0; i<G.vexnum; i++)
{
if(closedge[i].lowcost < min && visited[i] == 0)
{
min = closedge[i].lowcost;
t = i;
}
}
printf("%c,%d,%c\n", G.adjlist[t].data,closedge[t].lowcost, G.adjlist[closedge[t].vex].data);
visited[t] = 1;
//更新
p = G.adjlist[t].firstArc;
while(p)
{
if(closedge[p->adjvex].lowcost>p->info)
{
closedge[p->adjvex].lowcost = p->info;
closedge[p->adjvex].vex = t;
}
p = p->nextArc;
}
}
}
二、克鲁斯卡尔算法(Kruskal)
算法思想:
- 设T=(V,{E})是一个连通图,则令最小生成树的初始状态为只有n个顶点而无边的非连通图 ,即:T=( V,{ } ) 图中每个顶点自成一个连通分量。
- 在E中选择代价最小的边。约束条件:若此边依附的顶点落在T的不同连通分量上,将此边加入T中。否则舍去此边另选下一条代价最小的边。
- 依次类推,直到T中所有顶点都在同一个连通分量中。
拓扑排序(Topological Sort),AOV-网
算法思想:
- 在有向图中选一个没有前驱的顶点且输出之。
- 从图中删除此顶点及所有以它为尾的弧。
- 重复1.2过程,直到所有顶点均已输出,或当前图中不再有无前驱的顶点为止,此种情况表示图中有环。
//拓扑排序 (Topological Sort)
void TopologicalSort(ALGraph G)
{
//统计每个顶点的入度
//建立栈,存入每个入度为零的顶点
//当栈不空,删除入度为零以及以它为尾的弧
int i,t;
int indegree[N]={0}; //存放入度的数组
int S[N];
int top = 0;
ArcNode *p;
int count = 0; //记录输出顶点的个数
for(i = 0; i<G.vexnum; i++) //统计入度
{
p = G.adjlist[i].firstArc;
while(p)
{
indegree[p->adjvex]++;
p = p->nextArc;
}
}
for(i = 0; i<G.vexnum; i++) //对入度为零的顶点进栈并访问
{
if(indegree[i] == 0)
{
S[top++] = i;
count++;
printf("%c,", G.adjlist[i].data);
}
}
while(top != 0) //当栈不空
{
t = S[--top];
//进行更新
p = G.adjlist[t].firstArc;
while(p)
{
indegree[p->adjvex]--;
if(indegree[p->adjvex] == 0)
{
S[top++] = p->adjvex; //进栈的同时标记已访问
count++;
printf("%c,", G.adjlist[p->adjvex].data);
}
p = p->nextArc;
}
}
if(count == G.vexnum)
printf("\n该图中无回路");
}
关键路径问题,AOE-网
- 从开始点到完成点的最长路径的长度.即路径上各活动持续时间之和
算法思想:
- 输入e条弧,建立AOE-网的存储结构;
- 从源点V0出发,令ve[0]=0;按拓扑有序求其余各顶点的ve[i];若得到的序列中顶点的个数小于网中的顶点总数,则不能求关键路径,算法终止;否则执行步骤(3);
- 从汇点Vn出发,令vL[n-1]=ve[n-1],按逆拓扑有序求其余各顶点的vL[i];
- 根据各顶点的ve和vL,求每条弧s的e[s]和L[s],若满足e[s]=L[s],则为关键活动。
int ve[N] = {0}; //ve[i]表示最早开始时间
int vl[N] = {0}; //vl[i]表示最晚开始时间
int T[N],S[N];
int topS = 0;
int topT = 0;
void TopologicalOrder(ALGraph G)
{
int i, t;
int indegree[N] = {0};
ArcNode *p;
for(i = 0; i<G.vexnum; i++) //统计入度
{
p = G.adjlist[i].firstArc;
while(p)
{
indegree[p->adjvex]++;
p = p->nextArc;
}
}
for(i = 0; i<G.vexnum; i++) //对入度为零的顶点进栈
{
if(indegree[i] == 0)
{
S[topS++] = i;
T[topT++] = i;
}
}
while(topS != 0) //当栈不空
{
t = S[--topS];
p = G.adjlist[t].firstArc;
while(p) //求出当前最早开始时间,并对入度进行更新
{
if(ve[t] + p->info > ve[p->adjvex])
ve[p->adjvex] = ve[t]+p->info;
indegree[p->adjvex]--;
if(indegree[p->adjvex] == 0)
{
S[topS++] = p->adjvex;
T[topT++] = p->adjvex;
}
p = p->nextArc;
}
}
}
void CriticalPath(ALGraph G)
{
int i,t;
ArcNode *p;
TopologicalOrder(G);
t = T[--topT];
for(i = 0; i<G.vexnum ;i++)
{
vl[i] = ve[t];
}
while(topT != 0)
{
t = T[--topT];
p = G.adjlist[t].firstArc;
while(p)
{
if(vl[p->adjvex]-p->info < vl[t])
vl[t] = vl[p->adjvex]-p->info;
p = p->nextArc;
}
}
for(i = 0; i<G.vexnum; i++)
{
if(ve[i] == vl[i])
printf("%c,", G.adjlist[i].data);
}
}
最短路径问题
一、迪杰斯特拉算法(Dijkstra)
- 目的:求源点到其余各顶点的最短路径
算法思想:
- 1.定义D[i]:表示当前所找到的从源点V0到Vi的最短路径的长度。初态:D[i] = G.arcs[v0][vi] 即弧<v , vi>的权值。
- D[j] = Min( D[i] ) (i=1,…., n-1)。显然:Vj 就是从V0出发最早到达的长度最短的顶点。
- 下一条最短路径为 D[k] = min(D[k] , D[j]+G.arcs[j][k]);
- 同理,求出接下来的每个最短路径。
//迪杰斯特拉算法(Dijkstra),求v0到各顶点的最短距离
void ShortestPath_DIJ(ALGraph G, int v0)
{
//从v0求直接到其他顶点的距离,其中最小的为最短距离
//再从该点找直接相连的
int i, j, t;
int visited[N] = {0};
int D[N]; //D[i]表示当前v0到第i个顶点的最短距离
int P[N] = {0}; //P[i]表示v0到第i个顶点最短路径所经过的上一个顶点
ArcNode *p;
int min;
for(i = 0; i<G.vexnum; i++)
D[i] = MAX;
p = G.adjlist[v0].firstArc;
while(p)
{
D[p->adjvex] = p->info;
P[p->adjvex] = v0;
p = p->nextArc;
}
D[v0] = 0; //v0到v0点的距离为0
for(i = 1; i<G.vexnum; i++)
{
//找出D[]中的最小值D[t]
min = MAX;
for(j = 0; j<G.vexnum; j++)
{
if(D[j] < min && visited[j] == 0)
{
min = D[j];
t = j;
}
}
visited[t] = 1;
p = G.adjlist[t].firstArc;
while(p)
{
if(D[t] + p->info < D[p->adjvex] && visited[p->adjvex] == 0)
{
D[p->adjvex] = D[t] + p->info;
P[p->adjvex] = t;
}
p = p->nextArc;
}
}
for(i = 0; i<G.vexnum; i++)
{
printf("%d, %d\n",D[i], P[i]);
}
}
一、弗洛伊德算法(Floyd)
- 目的:求每一对顶点之间的最短路径
//弗洛伊德算法(Floyd),求每一对顶点之间的最短距离
void ShortestPath_FLOYD(ALGraph G)
{
int i, j, k;
int D[N][N]; //D[i][j]表示当前第i个顶点到第j个顶点的最短距离
int P[N][N] = {0}; //P[i][j]表示第i个顶点到第j个顶点最短路径所经过的上一个顶点
ArcNode *p;
//初始化D[][]
for(i = 0;i<G.vexnum; i++)
{
for(j = 0; j<G.vexnum; j++)
D[i][j] = MAX;
}
for(i = 0; i<G.vexnum; i++)
{
p = G.adjlist[i].firstArc;
while(p)
{
D[i][p->adjvex] = p->info;
P[i][p->adjvex] = i;
p = p->nextArc;
}
D[i][i] = 0;
}
for(i = 0;i<G.vexnum; i++)
{
for(j = 0; j<G.vexnum; j++)
{
for(k = 0; k<G.vexnum; k++)
{
if(D[j][i] + D[i][k] < D[j][k])
{
D[j][k] = D[j][i] + D[i][k];
P[j][k] = i;
}
}
}
}
for(i = 0;i<G.vexnum; i++)
{
for(j = 0; j<G.vexnum; j++)
{
printf("%d, %d\n", D[i][j],P[i][j]);
}
}
}