一、图的存储模式(这里讲述两种常用的)
(1)邻接矩阵
//邻接矩阵
#define inf 0x7fffffff
vector<bool> visit;
typedef char VertexType;//图的顶点类型
typedef int ArcType;//边的权值类型
typedef struct {
vector<VertexType> vex;//顶点表
vector<vector<ArcType>> arc;//邻接矩阵 边表
int vexNum, arcNum;//当前顶点数 边数
}MGraph;
(2)邻接表
//邻接表
#define MVNum 100//最大顶点数
vector<bool> visit;
typedef char VerTexType;
typedef int OtherInfo;
typedef struct ArcNode {
int adjvex;//所指向的顶点的位置
struct ArcNode* nextarc;//指向下一条边的指针
OtherInfo info;//和边相关的其他信息
ArcNode(int i) :adjvex(i), nextarc(NULL), info(NULL) {};
}ArcNode;
typedef struct VNode {
VerTexType data;//顶点信息
ArcNode* firstarc;//指向第一条依附该顶点的边的指针
}VNode, AdjList[MVNum];//AdjList表示邻接表类型
typedef struct {
AdjList vertices;//顶点
int vexnum, arcnum;//图的顶点数和弧数
}ALGraph;
二、图的两种构造方式
(1)邻接矩阵构造
#include<iostream>
#include<vector>
#include <algorithm>
#include<iterator>
using namespace std;
#define inf 0x7fffffff
typedef char VertexType;//图的顶点类型
typedef int ArcType;//边的权值类型
typedef struct {
vector<VertexType> vex;//顶点表
vector<vector<ArcType>> arc;//邻接矩阵 边表
int vexNum,arcNum;//当前顶点数 边数
}MGraph;
//构造无向网:无向图权值为0或1即可
// 有向网只赋值G[i][j]
void create_Graph(MGraph& G) {
cin >> G.vexNum >> G.arcNum;//图的顶点数和边数
//顶点信息
G.vex.resize(G.vexNum);
for (int i = 0; i < G.vexNum; i++) cin >> G.vex[i];
//初始化边信息
vector<vector<ArcType>> arc(G.vexNum,vector<ArcType>(G.vexNum,inf));//赋值为无穷大
G.arc = arc;
//边信息
VertexType vex1, vex2;
int weight;
for (int k = 0; k < G.arcNum; k++) {
cin >> vex1 >> vex2 >> weight;//两顶点 权值
int i = find(G.vex.begin(), G.vex.end(), vex1)-G.vex.begin();//vex1 vex2在图中的位置
int j = find(G.vex.begin(), G.vex.end(), vex2) - G.vex.begin();
G.arc[i][j] = G.arc[j][i] = weight;//权值
}
}
int main() {
MGraph G;
create_Graph(G);
return 0;
}
(2)邻接表构造
#include<iostream>
using namespace std;
#define MVNum 100//最大顶点数
typedef char VerTexType;
typedef int OtherInfo;
typedef struct ArcNode {
int adjvex;//所指向的顶点的位置
struct ArcNode* nextarc;//指向下一条边的指针
OtherInfo info;//和边相关的其他信息
ArcNode(int i) :adjvex(i), nextarc(NULL), info(NULL) {};
}ArcNode;
typedef struct VNode {
VerTexType data;//顶点信息
ArcNode* firstarc;//指向第一条依附该顶点的边的指针
}VNode,AdjList[MVNum];//AdjList表示邻接表类型
typedef struct {
AdjList vertices;//顶点
int vexnum, arcnum;//图的顶点数和弧数
}ALGraph;
//创建无向图
void CreateUDG(ALGraph& G){
cin >> G.vexnum >> G.arcnum;//输入图的顶点数 边数
for (int i = 0; i < G.vexnum; i++) {//建立顶点表
cin >> G.vertices[i].data;//输入顶点值
G.vertices[i].firstarc = NULL;//初始化表头节点的指针域
}
VerTexType vex1, vex2;
for (int k = 0; k < G.arcnum; k++) {//输入各边 构造邻接表
cin >> vex1 >> vex2;//输入一条边依附的两个顶点
int i = Locate(G, vex1);
int j = Locate(G, vex2);
ArcNode* p1 = new ArcNode(j);//生成新节点头插入边表
p1->nextarc = G.vertices[i].firstarc;
G.vertices[i].firstarc = p1;
ArcNode* p2 = new ArcNode(i);
p2->nextarc = G.vertices[j].firstarc;
G.vertices[j].firstarc = p2;
}
}
//找下标
int Locate(ALGraph G, VerTexType vex) {
for (int i = 0; i < G.vexnum; i++) {
if (G.vertices[i].data = vex) return i;
}
return -1;
}
int main() {
ALGraph G;
CreateUDG(G);
return 0;
}
三、图的遍历(BFS、DFS)
这里给出一个无向图:
(1)邻接矩阵深度、广度优先遍历
#include<iostream>
#include<vector>
#include <algorithm>
#include<iterator>
#include<queue>
using namespace std;
//邻接矩阵
#define inf 0x7fffffff
vector<bool> visit;
typedef char VertexType;//图的顶点类型
typedef int ArcType;//边的权值类型
typedef struct {
vector<VertexType> vex;//顶点表
vector<vector<ArcType>> arc;//邻接矩阵 边表
int vexNum, arcNum;//当前顶点数 边数
}MGraph;
//构造无向网:无向图权值为0或1即可
// 有向网只赋值G[i][j]
void CreateUDG(MGraph& G) {
cin >> G.vexNum >> G.arcNum;//图的顶点数和边数
//顶点信息
G.vex.resize(G.vexNum);
for (int i = 0; i < G.vexNum; i++) cin >> G.vex[i];
//初始化边信息
vector<vector<ArcType>> arc(G.vexNum, vector<ArcType>(G.vexNum, inf));//赋值为无穷大
G.arc = arc;
//边信息
VertexType vex1, vex2;
int weight;
for (int k = 0; k < G.arcNum; k++) {
cin >> vex1 >> vex2 >> weight;//两顶点 权值
int i = find(G.vex.begin(), G.vex.end(), vex1) - G.vex.begin();//vex1 vex2在图中的位置
int j = find(G.vex.begin(), G.vex.end(), vex2) - G.vex.begin();
G.arc[i][j] = G.arc[j][i] = weight;//权值
}
}
//从顶点出发 深度优先遍历图
void DFS(MGraph G,int v) {
cout << G.vex[v]<<" ";
visit[v] = true;
for (int i = 0; i < G.vexNum; i++) {
if (G.arc[v][i] != inf && !visit[i]) {//查找尚未访问的邻节点
DFS(G,i);
}
}
}
//深度优先遍历
void DFSTraverse(MGraph G) {
visit.resize(0);//这里两次resize是为了保证初始化为false
visit.resize(G.vexNum);//初始化访问节点数组
for (int i = 0; i < G.vexNum; i++) {//这里for循环的是防止该图不是连通图
if (!visit[i]) DFS(G, i);
}
}
//广度优先遍历
void BFS(MGraph G) {
visit.resize(0);//这里两次resize是为了保证初始化为false
visit.resize(G.vexNum);//初始化访问节点数组
queue<VertexType>Q;
for (int i = 0; i < G.vexNum; i++) {//这里for循环的意义同样是防止是非连通图
if (!visit[i]) {//如果节点未被访问
Q.push(G.vex[i]);//添加节点
visit[i] = true;
while (!Q.empty()) {
cout << Q.front()<< " ";
int k = find(G.vex.begin(), G.vex.end(), Q.front()) - G.vex.begin();
Q.pop();
for (int j = 0; j < G.vexNum; j++) {
if (!visit[j]&&G.arc[k][j]!=inf) {
Q.push(G.vex[j]);
visit[j] = true;
}
}
}
}
}
}
int main() {
MGraph G;
CreateUDG(G);
cout << "DFS:";
DFSTraverse(G);
puts("");
cout << "BFS:";
BFS(G);
return 0;
}
这里带权值:
5 5
A B C D E
A B 1
A C 1
B D 1
B E 1
D E 1
DFS:A B D E C
BFS:A B C D E
(2)邻接表深度、广度优先遍历
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
//邻接表
#define MVNum 100//最大顶点数
vector<bool> visit;
typedef char VerTexType;
typedef int OtherInfo;
typedef struct ArcNode {
int adjvex;//所指向的顶点的位置
struct ArcNode* nextarc;//指向下一条边的指针
OtherInfo info;//和边相关的其他信息
ArcNode(int i) :adjvex(i), nextarc(NULL), info(NULL) {};
}ArcNode;
typedef struct VNode {
VerTexType data;//顶点信息
ArcNode* firstarc;//指向第一条依附该顶点的边的指针
}VNode, AdjList[MVNum];//AdjList表示邻接表类型
typedef struct {
AdjList vertices;//顶点
int vexnum, arcnum;//图的顶点数和弧数
}ALGraph;
//找下标
int Locate(ALGraph G, VerTexType vex) {
for (int i = 0; i < G.vexnum; i++) {
if (G.vertices[i].data == vex) return i;
}
return -1;
}
//创建无向图
void CreateUDG(ALGraph& G) {
cin >> G.vexnum >> G.arcnum;//输入图的顶点数 边数
for (int i = 0; i < G.vexnum; i++) {//建立顶点表
cin >> G.vertices[i].data;//输入顶点值
G.vertices[i].firstarc = NULL;//初始化表头节点的指针域
}
VerTexType vex1, vex2;
for (int k = 0; k < G.arcnum; k++) {//输入各边 构造邻接表
cin >> vex1 >> vex2;//输入一条边依附的两个顶点
int i = Locate(G, vex1);
int j = Locate(G, vex2);
ArcNode* p1 = new ArcNode(j);//生成新节点头插入边表
p1->nextarc = G.vertices[i].firstarc;
G.vertices[i].firstarc = p1;
ArcNode* p2 = new ArcNode(i);
p2->nextarc = G.vertices[j].firstarc;
G.vertices[j].firstarc = p2;
}
}
//从顶点出发 深度优先遍历图
void DFS(ALGraph G,int v) {
cout << G.vertices[v].data << " ";
visit[v] = true;
ArcNode* p = G.vertices[v].firstarc;
while (p->nextarc) {//查找尚未访问的邻节点
if (!visit[p->adjvex]) DFS(G, p->adjvex);
p = p->nextarc;
}
}
//深度优先遍历图
void DFSTraverse(ALGraph G) {
visit.resize(0);//这里两次resize是为了保证初始化为false
visit.resize(G.vexnum);//初始化访问节点数组
for (int i = 0; i < G.vexnum; i++) {//这里for循环的意义也是防止图为非连通图
if (!visit[i]) DFS(G, i);
}
}
//广度优先遍历
void BFS(ALGraph G) {
visit.resize(0);//这里两次resize是为了保证初始化为false
visit.resize(G.vexnum);//初始化访问节点数组
queue<VerTexType> Q;//队列存储节点
for (int i = 0; i < G.vexnum; i++) {//这里for循环的意义同样是防止是非连通图
if (!visit[i]){
Q.push(G.vertices[i].data);//添加节点
visit[i] = true;
while (!Q.empty()) {
cout << Q.front() << " ";
int k = Locate(G, Q.front());
Q.pop();
ArcNode* p = G.vertices[k].firstarc;
while (p->nextarc) {//查找尚未访问的邻节点入队列
if (!visit[p->adjvex]) {
Q.push(G.vertices[p->adjvex].data);
visit[p->adjvex] = true;
}
p = p->nextarc;
}
}
}
}
}
int main() {
ALGraph G;
CreateUDG(G);
cout << "DFS:";
DFSTraverse(G);
puts("");
cout << "BFS:";
BFS(G);
return 0;
}
这里没带权值:
5 5
A B C D E
A B
A C
B D
B E
D E
DFS:A C B E D
BFS:A C B E D
四、图的应用(最小生成树、最短路径、拓扑排序、关键路径)
1.最小生成树
一个连通图的生成树是指一个极小连通子图,它含有图中的全部顶点,但只有足以构成一棵树的 n-1 条边。在一个连通网的所有生成树中,各边代价之和最小的那棵生成树称为该连通网 最小代价生成树(MST),简称为 最小生成树。利用普里姆(Prim)算法和克鲁斯卡尔(Kruscal)算法可以生成一个连通网的最小代价生成树,它们都是基于贪心算法策略。
1.1 普里姆(Prim)算法
(1)基本步骤
假设N=(V,{E})是连通网,TE为最小代价生成树中边的集合。
① 初始U={u0}(u0∈V),TE=空集;
② 在所有u∈U,v∈V-U的边中选一条代价最小的边(u0,v0)并入集合TE,同时将v0并入U;
③ 重复②,直到U=V为止,此时必然存在n-1条边。
通俗点说就是:从一个顶点出发,在保证不形成回路的前提下,每找到并添加一条最短的边,就把当前形成的连通分量当做一个整体或者一个点看待,然后重复“找最短的边并添加”的操作。
这里有兄弟问过我为什么不需要判断环,哥们,咱动动脑子想想,形成环要什么条件?这里是两个集合,要形成环,两个顶点必须在一个集合里面,Prim算法显然不是的。
示例:
(2)代码实现+注释
#include<iostream>
#include<vector>
using namespace std;
#define inf 0x7fffffff
typedef char VertexType;//图的顶点类型
typedef int ArcType;//边的权值类型
typedef struct {
vector<VertexType> vex;//顶点表
vector<vector<ArcType>> arc;//邻接矩阵 边表
int vexNum, arcNum;//当前顶点数 边数
}MGraph;
//构造无向网:无向图权值为0或1即可
// 有向网只赋值G[i][j]
void CreateUDG(MGraph& G) {
cin >> G.vexNum >> G.arcNum;//图的顶点数和边数
//顶点信息
G.vex.resize(G.vexNum);
for (int i = 0; i < G.vexNum; i++) cin >> G.vex[i];
//初始化边信息
vector<vector<ArcType>> arc(G.vexNum, vector<ArcType>(G.vexNum, inf));//赋值为无穷大
G.arc = arc;
//边信息
VertexType vex1, vex2;
int weight;
for (int k = 0; k < G.arcNum; k++) {
cin >> vex1 >> vex2 >> weight;//两顶点 权值
int i = find(G.vex.begin(), G.vex.end(), vex1) - G.vex.begin();//vex1 vex2在图中的位置
int j = find(G.vex.begin(), G.vex.end(), vex2) - G.vex.begin();
G.arc[i][j] = G.arc[j][i] = weight;//建立对称弧
}
}
//Prim算法生成最小生成树
//时间复杂度:O(n*n)即O(v*v)
void MiniSpanTree_Prim(MGraph G){
int j, n = G.vexNum-1, mincost;//j用来记录下标 n用来记录循环遍历 mincost记录最小权值
//path保存相关顶点下标 这里从v0开始 即下标零开始 解释:path[i] = j——> 第i个顶点与第j个顶点的弧 这里首先初始化所有点与顶点0的弧
vector<int> path(G.vexNum, 0);
//lowcost保存顶点间边的权值 这里从v0开始 即下标零开始 初始化arc[0](将v0顶点与之组成的边的权值存入数组)lowcost[i] = w, path[i] = j
//即顶点i和顶点j之间弧权值为w
//就是保存已加入树的点 距离此点的最小权值
vector<int> lowcost(G.arc[0]);
//初始化第一个权值为0,即v0加入生成树
//lowcost的值为0,在这里就是此下标的顶点已经加入生成树,也相当于一个标记
lowcost[0] = 0;
//这里也可以写一个标记数组标记节点是否已经加入生成树中
//如vector<int> isJoin(G.vexNum, false);
while (n--) {
mincost = inf;//初始化最小权值为inf(无穷大)
for (int i = 1; i < G.vexNum; i++) {
if (lowcost[i]!=0&&lowcost[i] < mincost) {//如果权值不为0且权值小于mincost 找除了已经存入树的顶点的最小权值
mincost = lowcost[i];
j = i;//记录当前下标
}
}
cout<<j<<" ";
cout << G.vex[path[j]] << " " << G.vex[j] << endl;//打印当前顶点边中权值最小边的两顶点,即新加入生成树的一条边
lowcost[j] = 0;//将当前顶点权值设置为0 表示此顶点已经加入生成树
//再次循环遍历,找到未加入的节点以及与下标为k的结点相连的结点的权值与前面的比较,更小则进行覆盖
for (int i = 1; i < G.vexNum; i++) {//若下标为k顶点各边权值小于此前这些顶点未被加入生成树的边权值则覆盖原本lowcost并且建立弧关系
//lowcost[i] = w, path[i] = j
//即顶点i和顶点k之间弧权值为j
//就是保存已加入树的点 距离此点的最小权值
if (lowcost[i] != 0&&G.arc[j][i] < lowcost[i]) {//这里lowcost[i]!=0没有实际意义 我这里加上就是提醒大家这里第i个顶点已经被加入 不会形成环 它是往外面找的
lowcost[i] = G.arc[j][i];//将较小权值覆盖上一次权值 存入lowcost
path[i] = j;//弧建立关系 即下标为k的顶点与顶点i联系
}
}
}
}
int main() {
MGraph G;
CreateUDG(G);
MiniSpanTree_Prim(G);
return 0;
}
6 10
A B C D E F
A B 6
C D 6
B C 3
D E 2
A E 5
A F 1
B F 5
C F 6
D F 4
E F 5
A F
F D
D E
F B
B C
1.2 克鲁斯卡尔(Kruscal)算法
(1)基本步骤
假设N=(V,{E})是连通网,将N中的边按权值从小到大的顺序排列。
① 将n个顶点看成n个集合。
② 按权值从小到大的顺序选择边,所选边应满足两个顶点不在同一个顶点集合内,将该边放到生成树边的集合中,同时将该边的两个顶点所在的顶点集合合并。
③ 重复②直到所有的顶点都在同一顶点集合内。
示例:
(2)代码实现+注释
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef char VexType;
//边存储结构
typedef struct Edge {
int begin;//起点序号
int end;//尾点序号
int weight;//权值
}Edge;
//图存储结构
typedef struct EdgeGraph {
vector<VexType> vex;//顶点信息
vector<Edge> arc;//边信息
int vexnum, arcnum;//顶点数 边数
}EdgrGraph;
//权值升序
bool cmp(Edge e1,Edge e2) {
return e1.weight < e2.weight;
}
//创建网
void Create_EdgeGraph(EdgeGraph& G) {
cin >> G.vexnum >> G.arcnum;//输入顶点数和边数
G.vex.resize(G.vexnum);//初始化顶点数组大小
G.arc.resize(G.arcnum);//初始化边数组大小
for (int k = 0; k < G.vexnum; k++) {
cin >> G.vex[k];//输入顶点信息
}
VexType vex1, vex2;//两顶点
int weight;//权值
for (int k = 0; k < G.arcnum; k++) {//保存边信息
cin >> vex1 >> vex2 >> weight;//输入边信息:两顶点 + 权值
G.arc[k].begin = find(G.vex.begin(), G.vex.end(), vex1) - G.vex.begin();
G.arc[k].end = find(G.vex.begin(), G.vex.end(), vex2) - G.vex.begin();
G.arc[k].weight = weight;
}
}
//Kruskar算法生成最小生成树
void MiniSpanTree_Kruskal(EdgeGraph G) {
sort(G.arc.begin(), G.arc.end(), cmp);//按照权值升序
vector<bool> vexset(G.vexnum, false);//节点是否已经存入树中
for (int k = 0; k < G.arcnum; k++) {
int i = G.arc[k].begin;
int j = G.arc[k].end;
if (!(vexset[i]&&vexset[j])) {//两顶点不在同一集合 不会形成环
cout << G.vex[i] << " " << G.vex[j] << endl;
vexset[i] = vexset[j] = true;
}
}
}
int main() {
EdgeGraph G;
Create_EdgeGraph(G);
MiniSpanTree_Kruskal(G);
return 0;
}
6 10
A B C D E F
A B 16
B C 5
B D 6
C D 6
D E 18
A E 19
A F 21
D F 14
E F 33
B F 11
B C
B D
B F
A B
D E
2.最短路径
2.1 BFS算法求解单源最短路径(无权图)
(1)邻接矩阵
#include<bits/stdc++.h>
using namespace std;
#define inf 0x7fffffff
typedef char VertexType;//图的顶点类型
typedef int ArcType;//边的权值类型
typedef struct {
vector<VertexType> vex;//顶点表
vector<vector<ArcType>> arc;//邻接矩阵 边表
int vexNum, arcNum;//当前顶点数 边数
}MGraph;
vector<int> path;
vector<int> d;
//BFS算法求解单源最短路径(无权连通图/所有边权值都相同的连通图)
//求顶点u到其他顶点的最短路径
void BFS_MIN_Distance(MGraph G, int u) {
//标记访问
vector<bool> visit(G.vexNum, false);
//初始化path(先驱)和d(距离)
path.resize(G.vexNum);
d.resize(G.vexNum);
for (int i = 0; i < G.vexNum; ++i) {
d[i] = inf; //初始化路径长度
path[i] = -1; //节点先驱
}
d[u] = 0;
visit[u] = true; //标记访问
queue<int> q;
q.push(u);
while (!q.empty()) {
//队头元素u出队
u = q.front();
q.pop();
for (int j = 0; j < G.vexNum; j++) {
if (!visit[j] && G.arc[u][j] != inf) { //访问u尚未被访问过的邻接点
d[j] = d[u] + 1; //路径长度加一
path[j] = u; //j前驱为u
q.push(j); //顶点j入队
visit[j] = true; //设为访问标记
}
}
}
}
int main() {
MGraph G;
CreateUDG(G);
int u = 0;
cin>>u;
BFS_MIN_Distance(G, u-1);//这里u-1是因为节点从一开始,数组下标从0开始
for(int i:d){
cout<<i<<" ";
}
cout<<endl;
for(int i:path){
cout<<i<<" ";
}
return 0;
}
8 10
1 2 3 4 5 6 7 8
1 5
1 2
2 6
6 3
6 7
3 7
3 4
7 4
7 8
4 8
2
1 0 2 3 2 1 2 3
1 -1 5 2 0 1 5 6(注意这里path整体减一是因为数组下标是从0开始的)
(2)邻接表
//邻接表
#define MVNum 100//最大顶点数
#define inf 0x7fffffff
vector<int> path;
vector<int> d;
typedef char VerTexType;
typedef int OtherInfo;
typedef struct ArcNode {
int adjvex;//所指向的顶点的位置
struct ArcNode* nextarc;//指向下一条边的指针
OtherInfo info;//和边相关的其他信息
ArcNode(int i) :adjvex(i), nextarc(NULL), info(NULL) {};
}ArcNode;
typedef struct VNode {
VerTexType data;//顶点信息
ArcNode* firstarc;//指向第一条依附该顶点的边的指针
}VNode, AdjList[MVNum];//AdjList表示邻接表类型
typedef struct {
AdjList vertices;//顶点
int vexnum, arcnum;//图的顶点数和弧数
}ALGraph;
//BFS算法求解单源最短路径(无权连通图/所有边权值都相同的连通图)
//求顶点u到其他顶点的最短路径
void BFS_MIN_Distance(ALGraph G, int u) {
//标记访问
vector<bool> visit(G.vexnum, false);
//初始化path(先驱)和d(距离)
path.resize(G.vexnum);
d.resize(G.vexnum);
for (int i = 0; i < G.vexnum; ++i) {
d[i] = inf; //初始化路径长度
path[i] = -1; //节点先驱
}
d[u] = 0;
visit[u] = true; //标记访问
queue<int> q;
q.push(u);
while (!q.empty()) {
//队头元素u出队
u = q.front();
q.pop();
ArcNode* p = G.vertices[u].firstarc;
while(p){
int j = p->adjvex;
if(!visit[j]){
d[j] = d[u] + 1; //路径长度加一
path[j] = u; //j前驱为u
q.push(j); //顶点j入队
visit[j] = true; //设为访问标记
}
p = p->nextarc;
}
}
}
int main() {
ALGraph G;
CreateUDG(G);
int u = 0;
cin>>u;
BFS_MIN_Distance(G, u-1);//这里u-1是因为节点从一开始,数组下标从0开始
for(int i:d){
cout<<i<<" ";
}
cout<<endl;
for(int i:path){
cout<<i<<" ";
}
return 0;
}
8 10
1 2 3 4 5 6 7 8
1 5
1 2
2 6
6 3
6 7
3 7
3 4
7 4
7 8
4 8
2
1 0 2 3 2 1 2 3
1 -1 5 6 0 1 5 6 (注意这里path整体减一也是因为数组下标是从0开始的)