一、图的存储及基本操作
邻接矩阵法
#define MaxVertexNum 100 //顶点数目的最大值
typedef char VertexType;//顶点的数据类型
typedef int EdgeType;//带权图中边上权值的数据类型
typedef struct{
VertexType Vex[MaxVertexNum];//顶点表
EdgeType Edge[MaxVertexNum][MaxVertexNum];//邻接矩阵,边表
int vexnum,arcnum; //图的当前顶点数和弧数
}MGraph;
邻接表法
#define MaxVertexNum 100
typedef struct ArcNode{ //边表结点
int adjvex;//该弧指向的顶点的位置
struct ArcNode *next;//指向下一条弧的指针
//InfoType info;//网的边权值
}ArcNode;
typedef struct VNode{ //顶点表结点
VertexType data;
ArcNode *first;
}VNode,AdjList[MaxVertexNum];
typedef struct{
AdjList vertices;//邻接表
int vexnum,arcnum;
}ALGraph;//邻接表存储的图
二、图的遍历
广度优先搜索BFS
广度优先遍历
bool visited[MAX_VERTEX_NUM];
void BFSTraverse(Graph G)
{
for(int i = 0;i < G.vexnum;i++)
{
visited[i] = false;
}
InitQueue(Q);
//以上为初始化
for(int i = 0;i < G.vexnum;i++) //BFS遍历整个图
{
if(!visited[i])
{
BFS(G,i);
}
}
}
void BFS(Graph G,int v)
{
visit(v);
visited[v] = true;
EnQueue(Q,v);
while(!isEmpty(Q))
{
DeQueue(Q,v);
for(w = FirstNeighbor(G,v);w >= 0;w = NextNeighbor(G,v,w)) //出队后不再访问该结点,直接遍历其邻接点
//NextNeighbor(G,v,w)表示图G中v的除w外的下一个邻接点
{
if(!visited[w])//邻接点未访问过,则直接访问并入队
{
visit(w);
visited[w] = true;
EnQueue(Q,w);
}
}
}
}
广度优先搜索求非带权图的单源最短路
void BFS_MIN_Distance(Graph G,int u)//从u出发的最短路径
{
//d[i]表示从u到i的最短路径长度
for(int i = 0;i < G.vexnum;i++)
{
d[i] = INF;//初始化
visited[i] = false;
}
d[u] = 0;
visited[u] = true;
EnQueue(Q,u);
while(!isEmpty(Q))
{
DeQueue(Q,u);
for(w = FirstNeighbor(G,u);w >= 0;w = NextNeighbor(G,u,w))//w为u尚未访问的邻接点
if(!visited[w])
{
visited[w] = true;
d[w] = d[u] + 1;
EnQueue(Q,w);
}
}
}
深度优先搜索DFS
bool visited[MAX_VERTEX_NUM];
void DFSTraverse(Graph G)
{
for(int i = 0;i < G.vexnum;i++)
{
visited[i] = false;
}
for(int i = 0;i < G.vexnum;i++)
{
if(!visited[i])
{
DFS(G,i);
}
}
}
void DFS(Graph G,int u)
{
visit(u);
visited[u] = true;
for(w = FirstNeighbor(G,u);w >= 0;w = NextNeighbor(G,u,w))
{
if(!visited[w])
{
DFS(G,w);
}
}
}
三、图的应用
最小生成树
1.Prim算法 O(n^2) 适用于点少边多的图
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 505,INF = 0x3f3f3f3f;
int n,m;
int g[N][N];
int dist[N];//表示点到集合中某点的距离
bool st[N];
int prim()
{
int res = 0;
memset(dist,INF,sizeof dist);
for(int i=0;i<n;i++) //循环n次,把所有点都加入到集合中
{
int t = -1;
for(int j=1;j<=n;j++) //找到集合外距离集合最短的点
{
if(!st[j] && (t == -1 || dist[j] < dist[t])) //j在集合外,且未找到任何一个点或j距离较小
{
t = j;
}
}
if(i && dist[t] == INF) return INF; //不是第一个点且当前距离最近的点的距离都是无穷,
//说明图不连通
if(i) res += dist[t];//只要不是第一条边(无穷),就把t的距离加入到答案去
for(int j=1;j<=n;j++)//用当前新加入的点更新其它集合外的点与集合的距离
{
dist[j] = min(dist[j],g[t][j]);
}
st[t] = true; //标志着当前点加入到集合中
}
return res;
}
int main()
{
cin>>n>>m;
memset(g,INF,sizeof g);
for(int i=0;i<m;i++)
{
int a,b,c;
cin>>a>>b>>c;
g[a][b] = g[b][a] = min(c,g[a][b]);//无向图
}
int t = prim();
if(t == INF) puts("impossible");
else cout<<t<<endl;
return 0;
}
2.Kruskal算法 O(mlogm) 适用于边少点多的图
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int M = 2e5+20,INF = 0x3f3f3f3f;
int p[M];
int n,m,a,b,c;
struct Edge
{
int a;
int b;
int w;
}edges[M];
int find(int a) //查找a点的集合的祖宗
{
if(a!=p[a]) p[a] = find(p[a]);
return p[a];
}
bool cmp(struct Edge a,struct Edge b)
{
return a.w < b.w;
}
void kruskal()
{
int cnt = 0,res = 0;
sort(edges+1,edges+m+1,cmp);//对每个边进行升序排序
for(int i=1;i<=n;i++) //初始化所有集合
{
p[i] = i;
}
for(int i=1;i<=m;i++)
{
int a = edges[i].a;
int b = edges[i].b;
int w = edges[i].w;
a = find(a),b = find(b); //若不在一个集合,则合并
if(a!=b)
{
p[b] = a;
cnt++;
res += w;
}
}
if(cnt < n-1) puts("impossible");//合并次数小于n-1,说明图不连通
else cout<<res<<endl;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
edges[i] = {a,b,c};
}
kruskal();
return 0;
}
最短路
单源最短路 Dijkstra算法 O(n^2)
多源最短路 Floyd算法 O(n^3)
四、易错总结
- 只要连通图中没有权值相同的边,则其最小生成树唯一
- 当连通图中存在权值相同的边时,最小生成树不一定唯一
- 深度优先遍历、拓扑排序、求关键路径可以判断一个有向图是否有环
- 若一个有向图的顶点不能排成一个拓扑序列,则判定该有向图含有顶点数大于1的强连通分量,不一定是强连通图(如单顶点的图:强连通且存在拓扑序列)
- 带权图的最小生成树中,某条边的权值可能超过未选边的权值(若错误,则意味着最小生成树一定是权值最小的n-1条边)
- AOE网中,要缩短工期,必须将所有的关键路径都缩短,即每个关键路径的时间都缩短
- 上三角邻接矩阵对应的图的拓扑序列必存在但可能不唯一
- 单个顶点本身可以是强连通的,可以作为一个强连通分量
- 邻接矩阵主对角元素全为0,无通路的结点元素为无穷
- 关键路径用顶点序列表示,eg.(v1,v3,v5)
- 根据工程优先关系及其时间画出AOE图时,最后应把无后继的结点指向时间跨度最长的结点,表明工期的结束
- Dijkstra算法能给出一棵生成树,但不能给出最小生成树
- 求有向图的强连通分量的数目的方法类似于拓扑排序:当某个顶点只有出边而没有入边时,其它顶点无法到达这个顶点,不可能于其他顶点构成强连通分量(此时单独构成强连通分量)