图
一、概念
- 图右顶点的有穷集合V和边的集合E组成
- 无向图(,)/有向图<,>:有无方向
- 度/入度出度:顶点边数
- 简单图:不存在某个顶点到其自身的边且不重复的边
- 完全图:任意两个顶点有边
- 带权图/网:边有权值
- 子图
- 路径:顶点到另一个顶点的序列
- 路径长度:路径上边的个数
- 环/回路
- 简单路径:顶点不重复出现
- 简单环:除第一和最后不重复
- 连通:有路径
- 连通图:任意一对顶点连通
- 连通分量:无向图中的极大连通子图,多个
- 强连通图:有向图
注:
计算各顶点的度存在重复
无向连通图所有顶点的度之和为偶数(重复计算)
矩阵Am各元素值的含义:i 经过(m-1)个中间结点到 j 的路径条数为Ai,j条
二、存储结构
-
顺序存储结构
若顶点不为数:用数组标记Vertex
邻接矩阵:二维数组
typedef struct{ int no; //编号 Elemtype data; //信息 }VertexType; typedef struct{ int edges[MAXV][MAXV]; //邻接矩阵 int n; //顶点数 int e; //边数 VertexType vexs[MAXV]; //信息 }MatGraph;
-
链式存储结构
邻接表
typedef ANode{ int adjvex; //编号 struct ANode *nextarc; //下个结点 int weight; //权值 }ArcNode; //结点 typedef struct Vnode{ Elemtype data; //信息 ArcNode *firstarc; //第一个边结点 }VNode; //头结点 typedef struct{ VNode adjlist[MAXV]; //头结点数组 int n; //顶点数 int e; //边数 }AdjGraph; //邻接表
逆邻接表:头结点为边结点的终点
十字链表:
邻接多重表
三、遍历
-
深度优先遍历(DFS)
void DFS(int v, AGraph *G){ visit[v] = l; //标记已访问 Visit(v); ArcNode* q = G->adjlist[v].firstarc; //第一个边结点 while(q != NULL){ if(vist[q->adjvex] == 0){ //下个结点未访问 DFS(q->adjvex, G); } q = q->nextarc; } }
-
广度优先遍历(BFS)
void BFS(AGraph *G, int v, int visit[MaxSize]){ ArcNode *p; int que[MaxSize], front = 0, rear = 0; //队列 int j; Visit(v); visit[v] = 1; //标记已访问 rear = (rear+1)%MaxSize; //找到队尾 que[rear] = v; //入队 while(front != rear){ //队不空 front = (front+1)%MaxSize; //出队 j = que[front]; //获得出队元素 p = G->adjlist[j].first; //取第一个边结点 while(p != NULL){ if(visit[p->adjV] == 0){ //未访问 Visit(p->adjV); visit[p->adjV] = 1; //标记已访问 rear = (rear+1)%MaxSize; que[rear] = p->adjV; //入队 } p = p->next; //找到出队元素其他相邻结点 } } }
四、最小生成树——带权值最小
-
Prim算法——生成树的结点与未加入结点之间找最小且不会产生环
void Prim(int n, float MGraph[][n], int v0, float &sum){ float lowCost[n],vSet[n]; int v, k, min; for(int i = 0 ; i < n ; i++){ //初始化 lowCost[i] = MGraph[v0][i]; //以v0为根与其他顶点权值 vSet[i] = 0; } v = v0; vSet[v] = 1; //已并入树中 sum = 0; for(int i = 0; i < n-1 ; i++){ //访问其余顶点 min = INF; for(int j = 0 ; j < n ; ++j){ if(vSet[j] == 0 && lowCost[j] < min){ //从未访问中找最小 min = lowCost[j]; k = j; //对应顶点 } } vSet[k] = 1; //加入树 v = k; sum += min; for(int j = 0 ; j < n ; j++){ //更新与未接入之间的权值 if(vSet[j] == 0 && MGraph[v][j] < lowCost[j]){ lowCost[j] = MGraph[v][j]; } } } }
-
Kruskal算法——找权值最小的边,且不产生环,边两端顶点生成树
并查集
相关存储结构
typedef struct{ int a,b; //两端顶点 int w; //权值 }Road; Road road[MaxSize]; //找根结点,相同会产生环 int getRoot(int p){ while(p != v[p]) p = v[p]; return p; } void Kruskal(Road road[], int n, int e, int &sum){ int a,b; sum = 0; for(int i = 0 ; i < n ; ++i){ //初始化 v[i] = i; } sort(road,e); //从小到大排序权值 for(int i = 0 ; i < e ; ++i){ //遍历所有边 a = getRoot(road[i].a); //获得左右比较 b = getRoot(road[i].b); if(a != b){ //不等不会产生环 v[a] = b; sum += road[i].w; } } }
五、最短路径
-
Dijkstra算法——某一顶点到其余顶点的最短路径
如果加入顶点后到达其余点的路径比原来小,更新dist[]和path[]
查找路径从终点找上一个顶点
dist[]:存放到各点最短路径
path[]:存放到达该点的上个顶点
set[]:判断是否已加入路径
void Dijkstra(int n, float MGraph[][n], int v0, int dist[], in path[]){ int set[MaxSize]; int min,v; for(int i = 0; i < n ; i++){ //初始化 dist[i] = MGraph[v0][i]; set[i] = 0; if(MGraph[v0][i] < INF) //可达 path[i] = v0; else //不可达 path[i] = -1; } set[v0] = 1; //已访问 path[v0] = -1; for(int i = 0; i < n-1; ++i){ //查找其余结点 min = INF; for(int j = 0; j < n; ++j){ if(set[j] == 0 && dist[j] < min){//找到最短路径 v = j; min = dist[j]; } } set[v] = 1; //已访问 for(int j = 0; j < n; ++j){ //更新最短路径 if(set[j] == 0 && dist[v]+MGraph[v][j] < dist[j]){ dist[j] = dist[v]+MGraph[v][j]; path[j] = v; } } } }
-
Floyd算法——任意两个顶点之间最短路径
对于每个顶点v,和任一顶点对(i,j), i≠j,v≠j,i≠v,
如果A[i] [j] >A[i] [v]+A[v] [j],更新A[i] [j]的值,Path[i] [j] = v
A[i] [j]:存储任意两个顶点之间当前最短路径
Path[i] [j]:经过中间点
void printPath(int u, int v, int path[][MAX]){ if(path[u][v] == -1){ //两顶点直接相连 printf(); }else{ int mid = path[u][v]; //获取中间点 printPath(u, mid, path); //起点到中间点 printPath(mid, v, path); //中间点到终点 } } void Floyd(int n, float MGraph[][n], int Paht[]){ int i, j, v; int A[n][n]; for(i = 0; i < n; ++i){ //初始化 for(j = 0; j < n; ++j){ A[i][j] = MGraph[i][j]; Path[i][j] = -1; } } for(v = 0; v < n; ++v){ for(i = 0; i < n; ++i){ for(j = 0; j < n; ++j){ if(A[i][j] > A[i][v] + A[v][j]){ //加入中间结点后有更小 A[i][j] = A[i][v] + A[v][j]; Path[i][j] = v; } } } } }
六、关键路径——最后结束
- AOE网:源点:只有出度,汇点:只有入度
- 拓扑排序:后面结点必须要前面结点先发生
- 逆拓扑排序
- 事件最早发生时间ve:最长到达该顶点时间
- 事件最迟发生时间vl:后继事件-活动时间,多个时选取最小
- 活动最早发生时间e:之前已经过时间,选最大
- 活动最迟发生时间l:指向事件最迟-活动时间
- 关键活动:活动最早和最迟重合
缩短整个工期要缩短所有关键路径