目录
一、图的基本知识
二、图的存储结构
(1)邻接矩阵
(2)邻接表
三、图的遍历方式
(1)DFS(Deep First Search 深度优先搜索)
(2)BFS(Breadth First Search 宽度优先搜索&广度优先搜索)
(3)略谈DFS与BFS其他用途
四、图的应用
(1)最小生成树(MST-Minimum Spanning Tree)
1.普里姆算法(Prim)
2.克鲁斯卡尔算法(Kruskal)
3.算法分析
(2)最短路径
1.迪杰斯特拉算法(Dijkstra)
2.弗洛伊德算法(Floyd)
3.算法分析:
(3)拓扑排序
对于带权图可表示
邻接矩阵存储表示
#define MaxInt 32768 //表示极大值, 即
#define MVNum //最大顶点
typedef char VerTexType; //设顶点的数据类型为字符型
typedef int ArcType; //设边的权值为整形
typedef struct
{
VerTexType vexs[MVNum]; //顶点表
ArcType arcs[MVNum][MVNum] //邻接矩阵
int vexnum, arcnum //顶点数、边数
}AMGraph;
/*
确定顶点vex在图G中的位置
*/
int LocateVex(AMGraph G, VerTexType vex)
{
for(int i = 0; i < G.vexnum; i ++)
if(vex == G.vexs[i])return i;
}
/*
以无向带权图为例
*/
void CreateUDN(AMGraph &G)
{
cout<<"请输入顶点和边的数目:"<>G.vexnum>>G.arcnum;
//对无向图初始化
for(int i = 0; i < G.vexnum; i ++)
{
for(int j = 0; j < G.vexnum; j ++)
{
G.arcs[i][j] = MaxInt;
}
}
cout<<"请输入顶点:"<>G.vexs[i];
cout<<"请输入边:"<>u>>v>>w;
int uid = LocateVex(G, u), vid = LocateVex(G, v);
G.arcs[uid][vid] = G.arcs[vid][uid] = w;
}
}
邻接矩阵优缺点:
优点:
直接判断两点是否右边相连
方面计算个点的度(入度与出度)
缺点:
不便增加和删除顶点
不便统计边的数目
空间复杂度高O(n^2),浪费空间
综上:邻接矩阵适用于稠密图
typedef char VerTexType; //顶点的数据类型
typedef int ArcType; //边的权值的数据类型
typedef struct ArcNode //定义边节点
{
int adjvex; //指向下一个顶点的位置
int val; //边的的权值
struct ArcNode *nextarc; //指向下一个边的指针
// OtherInfo inof;
}ArcNode;
typedef struct VNode //定义每一个顶点
{
VerTexType data; //顶点表示的值
ArcNode *firstarc; //指向第一条依附于该顶点的边的指针
}VNode, AdjList[MVNum]; //邻接表
typedef struct //定义图
{
AdjList vertices; //图的顶点
int vexnum, arcnum; //图的顶点数、边数
}ALGraph;
/*
以无向带权图图为例
*/
void CreateUDN(ALGraph &G)
{
ArcNode *pu, *pv;
cout<<"请输入顶点和边的数目:"<>G.vexnum>>G.arcnum;
cout<<"请输入顶点:"<>G.vertices[i].data;
G.vertices[i].firstarc = NULL; //将改顶点的表头指针域置为NULL
}
cout<<"请输入边:"<>u>>v>>w;
int uid = LocateVex(G, u), vid = LocateVex(G, v);
pu = new ArcNode; pv = new ArcNode;
pu->adjvex = vid; pu->val = w;
pu->nextarc = G.vertices[uid].firstarc; G.vertices[uid].firstarc = pu;
// //无向图
pv->adjvex = uid; pv->val = w;
pv->nextarc = G.vertices[vid].firstarc; G.vertices[vid].firstarc = pv;
}
}
邻接表的优缺点:
优点:
便于增加和删除顶点
便于统计边的数目
空间效率高
缺点:
不便于判断顶点与顶点之间是否存在边
不便于计算图各顶点的度
综上:邻接表使用稀疏图
类似于树的先序遍历,用递归实现,其思路于下:
a.从图中某一顶点v出发,访问v;
b.找出刚访问的顶点的第一个未被访问的邻接点,访问该顶点。以该顶点为新起点,重复此操作,直至刚访问过得顶点中没有未被访问的邻接点为之;
c.返回前一个访问过的且仍有未被访问的邻接的顶点,
d.重复操作b和c, 直至图中所有顶点都被访问过,结束搜索。
代码实现
/*
以存储方式为邻接矩阵的无向图为例
新定义数组 visited[MVNum]
visited[i] = 0 表示序号为i的顶点未被访问
visited[i] = 1 表示序号为i的顶点已被访问
*/
void DFS(AMGraph G, VerTexType vert)
{
int vid = LocateVex(G, vert);
visited[vid] = 1; cout<
类似于树的按层次遍历的过程,用队列实现,其过程于下
a.从图的一个顶点v出发, 访问该点;
b.以此访问顶点v的各个未被访问的邻接点;
c.分别从这些邻接点出发依次访问他们的邻接点,重复此操作,直至所有已被访问的顶点的邻接点都被访问到。
代码实现
/*
以存储方式为邻接矩阵的无向图为例
为方便,使用C++STL中已集成好的队列queue
同时使用visited[MVNum]数组, 标记顶点的访问情况
*/
void BFS(AMGraph G, VerTexType v)
{
//以顶点v为起始点
for(int i = 0; i < MVNum; i ++)visited[i] = 0; //将所有顶点初始化为未访问
queueque;
que.push(G.vexs[v]);
while(!que.empty())
{
VerTexType vert = que.front(); que.pop();
int vid = LocateVex(G, vert);
if(!visited[vid]) //若顶点vert未被访问
{
visited[vid] = 1;
cout<
二者都可用于求图的连通度
最短路问题一般用BFS实现
其他用途可查略其他资料
思想:
a.从图中一点v出发,选择与点v相邻的权值最小的边,将其加入MST的顶点集合U中;
b.再从集合U出发,将不再集合U中且与集合U相连的各边中权值最小的边加入集合U中;
c.重复操作b,知道所有顶点都加入集合U中。
代码实现:
/*
邻接矩阵存储的无向带权
*/
#include
using namespace std;
#define MaxInt 32768
#define MVNum 100
typedef char VerTexType ;
typedef int ArcType;
typedef struct
{
VerTexType vexs[MVNum];
ArcType arcs[MVNum][MVNum];
int vexnum, arcnum;
}AMGraph;
struct closedge
{
ArcType cost;
int adjvex;
}ClosEdge[MVNum];
int LocateVex(AMGraph G, VerTexType v)
{
for(int i = 0; i < G.vexnum; i ++)
{
if(G.vexs[i] == v)return i;
}
}
//建立无向带权图
void CreateUDN(AMGraph &G)
{
cout<<"输入顶和边的数目:"<>G.vexnum>>G.arcnum;
for(int i = 0; i < G.vexnum; i ++)cin>>G.vexs[i];
for(int i = 0; i < G.vexnum; i ++)
{
for(int j = 0; j < G.vexnum; j ++)
{
G.arcs[i][j] = MaxInt;
}
}
VerTexType u, v;
ArcType w;
for(int i = 0; i < G.arcnum; i ++)
{
cin>>u>>v>>w;
int uid = LocateVex(G, u), vid = LocateVex(G, v);
G.arcs[uid][vid] = G.arcs[vid][uid] = w;
}
}
void Prim(AMGraph G, VerTexType v)
{
int visited[MVNum]; //记录顶点是否被访问
for(int i = 0; i < G.vexnum; i ++) visited[i] = 0;
int vid = LocateVex(G, v);
visited[vid] = 1;
int Min;
//初始化
for(int i = 0; i < G.vexnum; i ++)
{
if(!visited[i])
{
ClosEdge[i].cost = G.arcs[vid][i];
ClosEdge[i].adjvex = vid;
}
}
int u;
ArcType res = 0;
for(int i = 1; i < G.vexnum; i ++)
{
Min = MaxInt;
//找出权值最小的边
for(int j = 0; j < G.vexnum; j ++)
{
if(!visited[j] && ClosEdge[j].cost < Min)
{
Min = ClosEdge[j].cost;
u = j;
}
}
res += ClosEdge[u].cost;
cout< "<2.克鲁斯卡尔算法(Kruskal)
思想:
a.将左右边按权值从小到大排序;
b.依次选取选取权值小的边,若该边的两个顶点均在MST集合U中,则放弃次边,否则将次边加入集合U中
c.重复操作c,直至所选的边数等于顶点的数目减一结束。
代码实现:
使用并查集,两个操作
a.判断边的两个顶点是否拥有相同祖先(即两个顶点是否同在集合U中)
b.合并边的两个顶点的公共祖先(即将两个顶点加入集合U中)
#include
using namespace std;
/*
邻接矩阵实现无向带权图的最小生成树
*/
#define MaxInt 32768
#define MVNum 100
typedef char VerTexType;
typedef int ArcType;
int father[MVNum]; //记录顶点的祖宗节点
int NumEdge; //记录边的数目
typedef struct
{
VerTexType vexs[MVNum];
ArcType arcs[MVNum][MVNum];
int vexnum, arcnum;
}AMGraph;
//克鲁斯卡尔算法
struct ArcEdge //定义边
{
int head, tail; //边的头和尾
ArcType cost; //边的权值
}Edge[MVNum * MVNum];
//按边的从小到大排序
bool cmp(const ArcEdge &a, const ArcEdge &b)
{
return a.cost < b.cost;
}
//添加边
void AddEdge(int u, int v, ArcType w)
{
Edge[NumEdge].head = u;
Edge[NumEdge].tail = v;
Edge[NumEdge].cost = w;
NumEdge ++;
}
//获取父节点
int GetFather(int a)
{
return father[a] == a ? a : GetFather(father[a]);
}
//合并两个节点
void Unionn(int x, int y)
{
father[GetFather(x)] = GetFather(y);
}
int LocateVex(AMGraph G, VerTexType vex)
{
for(int i = 0; i < G.vexnum; i ++)
if(vex == G.vexs[i])return i;
}
//创建一个图
void CreateUDN(AMGraph &G)
{
cout<<"请输入顶点和边的数目:"<>G.vexnum>>G.arcnum;
//对无向图初始化
for(int i = 0; i < G.vexnum; i ++)
{
for(int j = 0; j < G.vexnum; j ++)
{
G.arcs[i][j] = MaxInt;
}
}
cout<<"请输入顶点:"<>G.vexs[i];
cout<<"请输入边:"<>u>>v>>w;
int uid = LocateVex(G, u), vid = LocateVex(G, v);
G.arcs[uid][vid] = G.arcs[vid][uid] = w;
/*
此处即为克鲁斯卡尔算法的建边
因为是无向图,要建两次边
*/
AddEdge(uid, vid, w);
AddEdge(vid, uid, w);
}
}
void Kruskal(AMGraph G)
{
//初始化个顶点的祖宗节点为本身,即初始各个顶点互不相连
sort(Edge, Edge + NumEdge, cmp); //按边排序
for(int i = 0; i < G.vexnum; i ++)father[i] = i;
int tot = 0;
ArcType res = 0;
for(int i = 0; i < NumEdge; i ++)
{
int head = Edge[i].head;
int tail = Edge[i].tail;
ArcType cost = Edge[i].cost;
if(GetFather(head) == GetFather(tail))continue;
//如何该边的两个顶点的祖宗节点相同,则说明这两个顶点属于一个集合,所以要跳出循环
//否则该边可以作为生成树的一条边
cout<"<
3.算法分析
分析:Prim算法的复杂度为O(n^2),适用于稠密图
Kruskal算法复杂度为O(n + e)使用稀疏图
(2)最短路径
1.迪杰斯特拉算法(Dijkstra)
#include
using namespace std;
#define MaxInt 32768
#define MVNum 100
typedef char VerTexType ;
typedef int ArcType;
typedef struct
{
VerTexType vexs[MVNum];
ArcType arcs[MVNum][MVNum];
int vexnum, arcnum;
}AMGraph;
int LocateVex(AMGraph G, VerTexType v)
{
for(int i = 0; i < G.vexnum; i ++)
{
if(G.vexs[i] == v)return i;
}
}
//建立无向带权图
void CreateUDN(AMGraph &G)
{
cout<<"输入顶和边的数目:"<>G.vexnum>>G.arcnum;
for(int i = 0; i < G.vexnum; i ++)cin>>G.vexs[i];
for(int i = 0; i < G.vexnum; i ++)
{
for(int j = 0; j < G.vexnum; j ++)
{
G.arcs[i][j] = MaxInt;
}
}
VerTexType u, v;
ArcType w;
for(int i = 0; i < G.arcnum; i ++)
{
cin>>u>>v>>w;
int uid = LocateVex(G, u), vid = LocateVex(G, v);
G.arcs[uid][vid] = G.arcs[vid][uid] = w;
}
}
void Dijkstra(AMGraph G, VerTexType v)
{
int visited[MVNum], lowcost[MVNum], pre[MVNum];
//pre[j]表示到顶点j前驱
int vid = LocateVex(G, v);
for(int i = 0; i < G.vexnum; i ++)
{
lowcost[i] = MaxInt; pre[i] = -1; visited[i] = 0;
}
lowcost[vid] = 0;
int u;
while(1)
{
u = -1;
//找较小的路径
for(int v = 0; v < G.vexnum; v ++)
{
if(!visited[v] && (u == -1 || lowcost[v] < lowcost[u]))
{
u = v;
}
}
if(u == -1)break;
visited[u] = 1;
cout< lowcost[u] + G.arcs[u][v]))
{
lowcost[v] = lowcost[u] + G.arcs[u][v];
pre[v] = u;
}
}
}
int path[MVNum];
//回溯最短路的路径
for(int i = 0; i < G.vexnum; i ++)
{
if(vid == i)continue;
int t = i, ans = 0;
while(pre[t] != -1)
{
path[ans++] = pre[t];
t = pre[t];
}
cout<<"to the vert the "<= 0; j--)cout<
2.弗洛伊德算法(Floyd)
关键方程为 lowcost[i][j] = min(lowcost[i][j], lowcost[i][k] + lowcost[k][j]);(初始化时lowcost[i][j] = G.arcs[i][j])
#include
using namespace std;
#define MaxInt 32768
#define MVNum 100
typedef char VerTexType ;
typedef int ArcType;
typedef struct
{
VerTexType vexs[MVNum];
ArcType arcs[MVNum][MVNum];
int vexnum, arcnum;
}AMGraph;
int LocateVex(AMGraph G, VerTexType v)
{
for(int i = 0; i < G.vexnum; i ++)
{
if(G.vexs[i] == v)return i;
}
}
//建立无向带权图
void CreateUDN(AMGraph &G)
{
cout<<"输入顶和边的数目:"<>G.vexnum>>G.arcnum;
for(int i = 0; i < G.vexnum; i ++)cin>>G.vexs[i];
for(int i = 0; i < G.vexnum; i ++)
{
for(int j = 0; j < G.vexnum; j ++)
{
G.arcs[i][j] = MaxInt;
}
}
VerTexType u, v;
ArcType w;
for(int i = 0; i < G.arcnum; i ++)
{
cin>>u>>v>>w;
int uid = LocateVex(G, u), vid = LocateVex(G, v);
G.arcs[uid][vid] = G.arcs[vid][uid] = w;
}
}
void Floyd(AMGraph G)
{
ArcType lowcost[MVNum][MVNum];
for(int i = 0; i < G.vexnum; i ++)
{
for(int j = 0; j < G.vexnum; j ++)
{
lowcost[i][j] = G.arcs[i][j];
}
}
for(int k = 0; k < G.vexnum; k ++)
{
for(int i = 0; i < G.vexnum; i ++)
{
for(int j = 0; j < G.vexnum; j ++)
{
lowcost[i][j] = min(lowcost[i][j], lowcost[i][k] + lowcost[k][j]);
}
}
}
for(int i = 0 ; i < G.vexnum; i ++)
{
for(int j = 0; j < G.vexnum; j ++)
{
cout<
3.算法分析:
Dijkstra算法的复杂度为O(n^2),Floyd算法的复杂度为O(n^3)
Dijkstra算法只能求一点到其他点的最短路径,Floyd算法能求任意两点的最短路径
(3)拓扑排序
拓扑排序:将AOV-网(Activity On Vertex Network)中所有顶点排成一个线性序列,该线性序列满足:若在AOV-网中由顶点vi到顶点vj有一条路径,则在该线性序列中的顶点vi必在顶点vj之前。(前提:AOV-网不能出现有向环)
其拓扑序列可以为 e b g f a d c h
代码实现:(栈实现)
/*
使用邻接表存储的有向图
为方便起见,,使用C++STL中集成的栈stack
*/
#include
using namespace std;
#define MaxInt 32768
#define MVNum 100
typedef char VerTexType; //节点的数据类型
typedef int ArcType;
int indegree[MVNum];
typedef struct ArcNode //定义节点
{
int adjvex; //指向下一个节点的位置
struct ArcNode *nextarc; //指向下一个节点
}ArcNode;
typedef struct VNode //定义每一个顶点
{
VerTexType data; //顶点表示的值
ArcNode *firstarc; //指向第一条依附于该顶点的边的指针
}VNode, AdjList[MVNum]; //邻接表
typedef struct //定义图
{
AdjList vertices; //图的顶点
int vexnum, arcnum; //图的顶点数、边数
}ALGraph;
int LocateVex(ALGraph G, VerTexType v)
{
for(int i = 0; i < G.vexnum; i ++)
if(v == G.vertices[i].data)return i;
}
//建立有向图(无环)
void CreateDN(ALGraph &G)
{
ArcNode *p;
cout<<"请输入顶点和边的数目:"<>G.vexnum>>G.arcnum;
cout<<"请输入顶点:"<>G.vertices[i].data;
G.vertices[i].firstarc = NULL;
}
for(int i = 0; i < G.vexnum; i ++)indegree[i] = 0;
VerTexType u, v;
cout<<"请输入边:"<>u>>v;
uid = LocateVex(G, u); vid = LocateVex(G, v);
p = new ArcNode;
indegree[vid] ++;
p->adjvex = vid;
p->nextarc = G.vertices[uid].firstarc; G.vertices[uid].firstarc = p;
}
}
stackst;
void TopoSort(ALGraph G)
{
VerTexType topo[MVNum];
for(int i = 0; i < G.vexnum; i ++)
{
if(!indegree[i])st.push(i); //入度为零的顶点入栈
}
int m = 0; //记录顶点的个数
while(!st.empty())
{
int v = st.top(); st.pop(); //将栈顶顶点弹出
topo[m++] = G.vertices[v].data;
ArcNode *p;
p = G.vertices[v].firstarc;
while(p)
{
//将与顶点p邻接点顶点的入度减一
int k = p->adjvex;
indegree[k]--;
if(!indegree[k]) st.push(k); //入度为零的顶点入栈
p = p->nextarc;
}
}
if(m < G.vexnum) {cout<<"存在有向环"<