数据结构之图的基本运算方法.docx
- 文档编号:23676468
- 上传时间:2023-05-19
- 格式:DOCX
- 页数:37
- 大小:355.46KB
数据结构之图的基本运算方法.docx
《数据结构之图的基本运算方法.docx》由会员分享,可在线阅读,更多相关《数据结构之图的基本运算方法.docx(37页珍藏版)》请在冰豆网上搜索。
数据结构之图的基本运算方法
数据结构A:
实验--图的基本运算方法
一、实验目的和要求
1.掌握在图的邻接矩阵和邻接表存储结构实现图的基本运算的算法。
学习使用图算法解决应用问题的方法。
(1).验证教材中关于在邻接矩阵和邻接表两种不同存储结构上实现图的基本运算的算法
(2)在邻接矩阵和邻接表存储结构上实现图的深度和宽度优先遍历算法。
(3)在两种储存结构上面分别实现Dijkstra、prim、Floyd算法
2.飞机最少换乘次数问题。
二、实验环境(实验设备)
PC计算机,Windows7操作系统,
IDE:
Codeblocks16.01
编译器:
GNUGCCCompiler
、实验原理及内容
程序一:
邻接矩阵表示的图的运算
本程序包含邻接矩阵表示的图的基本运算,包括插入边,移除边,判断边是否存在,深度优先遍历DFS,宽度优先遍历BFS,单源最短路Dijkstra算法,多源最短路Floyd算法,以及最小生成树prim算法。
程序定义了一个图类包含三个数据成员,分别是指向二维数据的指针T**a,记录顶点数的n和记录边数量的e。
构造函数负责申请二维数组动态空间,析构函数负责释放空间。
类的声明:
template
classMGraph
{
public:
MGraph(intmSize);
~MGraph();
boolInsert(intu,intv,intw);
boolRemove(intu,intv);
boolExist(intu,intv)const;
voidDFS();
voidBFS();
voidDijkstra(intv,T*d,int*path);
voidFloyd(intd[][Size],intpath[][Size]);
voidprim(intk,int*nearest,T*lowcost);
protected:
intChoose(int*d,bool*s);
voidDFS(intv,bool*visited);
voidBFS(intv,bool*visited);
T**a;
intn,e;
};
(1)深度优先遍历DFS算法
图的深度优先遍历重载了两个DFS函数,分别是面向用户的DFS()和私有成员DFS(intv,bool*visited)。
面向用户的不带参数版本,首先定义一个一位数组visited来标记当前已经访问过的结点,并初始化为false。
为了防止图不是一个连通图,在外层DFS中,要通过一个循环一遍一遍的调用带参数版本的DFS(i,visited),如果图是一个连通图,那么在第一次调用之后,所以的visited都变成true,后续就不再调用,如果图不是一个连通图,那么还要多次调用带参数版本DFS(i,visited)
通过这个方法,也可以用来判断图是不是一个连通图。
代码:
template
voidMGraph
:
DFS()
{
inti;
bool*visited=newbool[n];
for(i=0;i visited[i]=false; for(i=0;i { if(visited[i]==false) { DFS(i,visited); } } cout< delete[]visited; } template voidMGraph : DFS(intv,bool*visited) { visited[v]=true; printf("%d",v); for(intu=0;u { if(a[v][u]! =INF&&visited[u]==false) { DFS(u,visited); } } } 算法分析: 深度优先遍历,没嵌套调用一次,实际是对一个顶点v查看所有的邻接点,设图有n个顶点,时间复杂度是O(n^2) 空间复杂度: 由于要定义一个临时数组标记访问过的顶点,所以空间复杂度是O(n)。 (2)宽度优先遍历BFS 图的宽度优先遍历BFS重载了两个版本,一个是面向用户的不带参数版本,一个是带参数版本。 不带参数版本的作用主要是为了防止图不是一个连通图,导致不能访问所有顶点,所有通过一个循环来一遍一遍调用带参数版本BFS,与DFS类似,如果图是一个连通图,那么第一次调用就能访问所有顶点,如果不是连通图,得多次调用,通过此方法也可以判断是不是连通图。 带参数版本BFS通过队列来实现宽度优先遍历,首先将第一个顶点加入队列,然后,每次从队列取出一个顶点,并将与之相邻并且还没有访问过的顶点加入到队列,直到队列为空停止。 为了代码简洁,队列使用了STL库的queue头文件。 代码: template voidMGraph : BFS() { inti; bool*visited=newbool[n]; for(i=0;i visited[i]=false; for(i=0;i { if(visited[i]==false) { BFS(i,visited); } } cout< delete[]visited; } template voidMGraph : BFS(intv,bool*visited) { queue visited[v]=true; Q.push(v); while(! Q.empty()) { v=Q.front(); Q.pop(); cout< for(inti=0;i { if(visited[i]==false&&a[v][i]! =INF) { visited[i]=true; Q.push(i); } } } } 算法分析: 每个顶点只进入一次队列,对于每个从队列取走的点,都查看其所有的邻接点,设顶点数是n,边数是e,时间复杂度是O(n^2) 空间复杂度: 同样需要定义临时数组标记访问过的顶点,队列中元素个数不超过n个,所以空间复杂度是O(n)。 (3)prim最小生成树算法 prim算法的整体思路为,首先加入一个顶点到生成树中,然后,每次加入一条代价最小的边,边的一个顶点已被加入生成树,一个顶点未被加入生成树,然后标记这个边的顶点,表示将这个顶点也加入,加入之后更新其lowcost数组,然后继续选边,直到所有顶点都被标记。 定义三个临时数组,mark数组来实现上述标记,lowcost[j]记录与顶点j相接的边中最小的权值(边的另一个顶点未被标记),nearest[j]表示顶点j上述最小权值边的另一个顶点。 主函数打印最小生成树的边集(nearest[j],j,lowcost[j]) 代码: template voidMGraph : prim(intk,int*nearest,T*lowcost) { bool*mark=newbool[n]; if(k<0||k>n-1) { printf("outofbounds\n"); return; } for(inti=0;i { nearest[i]=-1; lowcost[i]=INF; mark[i]=false; } lowcost[k]=0; nearest[k]=k; mark[k]=true; intcnt=n-1; while(cnt--) { for(inti=0;i { if(a[k][i]! =INF) { if((! mark[i])&&(lowcost[i]>a[k][i])) { lowcost[i]=a[k][i]; nearest[i]=k; } } } Tminn=INF; for(intj=0;j { if((! mark[j])&&(lowcost[j] { minn=lowcost[j]; k=j; } } mark[k]=true; } } 算法分析: 由于要加入n个顶点,每次加入都要找最小边和更新lowcost,所以时间复杂度是O(n^2)。 空间复杂度: 定义两个一维数组,空间复杂度是O(n) (4)Dijkstra单源最短路算法 Dijkstra的整体思路是,首先加入起点到s集合中,然后更新与之相邻的顶点的数组d的值,然后每次选择不在s中并且d[j]最小的顶点,将顶点加入到s集合,并更新与这个顶点相邻的顶点d[j]的值,然后再选择不在s中并且d[j]最小的顶点,如此重复n-1次,将所有顶点都加入s中。 利用s[]数组来标记是不是在集合s中,调用choose函数来选择不在s中且d最小的顶点,每次更新d的时候也要更新path数组来记录路径。 代码: template voidMGraph : Dijkstra(intv,T*d,int*path) { inti,k,w; if(v<0||v>n-1) { cout<<"outofbounds! "< return; } bool*s=newbool[n]; for(i=0;i { s[i]=false; d[i]=a[v][i]; if(i! =v&&d[i] path[i]=v; elsepath[i]=-1; } s[v]=true; d[v]=0; for(i=1;i { k=Choose(d,s); s[k]=true; for(w=0;w { if(! s[w]&&d[k]+a[k][w] { d[w]=d[k]+a[k][w]; path[w]=k; } } } } 算法复杂度: 要加入n-1个顶点,每次加入都要调用choose函数,choose函数时间复杂度是O(n),综上,时间复杂度是O(n^2)。 空间复杂度: 定义path数组,d数组,和s数组,都是一维数组,空间复杂度是O(n)。 (5)Floyd多源最短路算法 Floyd算法又称为插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法 其状态转移方程如下: map[i,j]: =min{map[i,k]+map[k,j],map[i,j]}; map[i,j]表示i到j的最短距离,K是穷举i,j的断点,map[n,n]初值应该为0,或者按照题目意思来做。 算法过程为,首先,从任意一条单边路径开始。 所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。 然后,对于每一对顶点u和v,看看是否存在一个顶点w使得从u到w再到v比已知的路径更短。 如果存在则更新它。 代码: template voidMGraph : Floyd(intd[][Size],intpath[][Size]) { inti,j,k; for(inti=0;i { for(intj=0;j { d[i][j]=a[i][j]; if(i! =j&&a[i][j] { path[i][j]=i; } else { path[i][j]=-1; } } } for(k=0;k { for(i=0;i { for(j=0;j { if(d[i][k]+d[k][j] { d[i][j]=d[i][k]+d[k][j]; path[i][j]=path[k][j]; } } } } } 算法分析: 时间复杂度是O(n^3),用二维数组保存结果,空间复杂度是O(n^2)。 程序测试: 输入课本179页下方图 结果正确 程序测试: 176页上方图 结果正确。 程序二: 邻接表表示的图的基本运算 本程序包含邻接表表示的图的基本运算,包括插入边,移除边,判断边是否存在,深度优先遍历DFS,宽度优先遍历BFS,单源最短路Dijkstra算法,多源最短路Floyd算法,以及最小生成树prim算法。 程序定义了一个图类包含三个数据成员,分别是指向vector的数组的指针,记录顶点数的n和记录边数量的e。 构造函数负责申请n个vector数组,M[i]为一个vector数组,存放顶点i相接的边,相当于链表,为了代码的简洁,程序用STL库的vector代替了链表。 析构函数负责释放空间。 Vector的元素类型为定义的结构体表示的边,边上有两个属性,分别是边的另一侧的顶点值,和边的权值。 表示边的结构体: structedge{ intto; intcost; edge(intx,inty) { to=x; cost=y; } }; 表示边的图例说明: 类的声明: template classLGraph { private: vector intn; inte; voidDFS(intu,bool*vis); voidBFS(intv,bool*vis); public: LGraph(intmSize); ~LGraph(); boolInsert(intu,intv,intw); boolExist(intu,intv); boolRemove(intu,intv); voidDijkstra(intv,T*d,int*path); voidprim(intk,int*nearest,T*lowcost); voidFloyd(intd[][Size],intpath[][Size]); voidDFS(); voidBFS(); }; 类的图例说明: (1)构造函数负责申请n个vector数组,用来存放邻接边 代码: template LGraph : LGraph(intmSize) { M=newvector n=mSize; e=0; } (2)插入函数Insert Insert函数首先判断插入的边是否存在,如果存在返回false,不存在则插入边,插入的时候首先构造一个边,然后调用vector的push_back函数插入。 代码: template boolLGraph : Insert(intu,intv,intw) { inti; if(u<0||u<0||v>n-1||u>n-1)returnfalse; for(i=0;i { if(M[u][i].to==v) { returnfalse; } } edgetmp(v,w); M[u].push_back(tmp); e++; returntrue; } (3)Remove移除边 移除边首先要判断参数合理性,然后搜索这条边是否存在,如果存在移除,不存在返回false 代码: template boolLGraph : Remove(intu,intv) { inti; if(u<0||v<0||u>n-1||v>n-1)returnfalse; for(i=0;i { if(M[u][i].to==v) { M[u].erase(M[u].begin()+i); returntrue; } } returnfalse; } (4)深度优先遍历DFS 与上面邻接矩阵的DFS类似,邻接表由于直接记录与当前顶点相邻的顶点,所有循环的时候不需要遍历所有顶点判断INF,循环的时间复杂度更加低。 思路与邻接矩阵类似,但是由于邻接表在插入的时候没有考虑顺序,导致每个顶点后面接的顶点不一定按照从小到大的顺序排列,而邻接矩阵循环是从0到n-1按照从小到大的顺序来的,所以为了保持和邻接矩阵的一致性,在DFS的时候第一步首先将每个vector进行排序,将vector中元素按照边的另一头顶点从小到大的顺序排列,调用STL中的 cmp定义: intcmp(edgea,edgeb) { returna.to } DFS代码: template voidLGraph : DFS() { for(inti=0;i { sort(M[i].begin(),M[i].end(),cmp); } inti; bool*vis=newbool[n+1]; for(i=0;i { vis[i]=false; } for(i=0;i { if(vis[i]==false) { DFS(i,vis); } } delete[]vis; cout< } template voidLGraph : DFS(intv,bool*vis) { vis[v]=true; printf("%d",v); inti; for(i=0;i { intt=M[v][i].to; if(vis[t]==false) { DFS(t,vis); } } } 算法分析: 深度优先遍历,没嵌套调用一次,实际是对一个顶点v查看所有的邻接点,设图有n个顶点,时间复杂度是O(n+e)。 空间复杂度是O(n)。 (5)宽度优先遍历BFS BFS与邻接矩阵类似,重载了两个版本,一个是面向用户的不带参数版本,一个是带参数版本。 不带参数版本的作用主要是为了防止图不是一个连通图,导致不能访问所有顶点,所有通过一个循环来一遍一遍调用带参数版本BFS,与DFS类似,如果图是一个连通图,那么第一次调用就能访问所有顶点,如果不是连通图,得多次调用,通过此方法也可以判断是不是连通图。 带参数版本BFS通过队列来实现宽度优先遍历,首先将第一个顶点加入队列,然后,每次从队列取出一个顶点,并将与之相邻并且还没有访问过的顶点加入到队列,直到队列为空停止。 为了代码简洁,队列使用了STL库的queue头文件。 代码: template voidLGraph : BFS() { inti; bool*vi=newbool[n]; for(i=0;i { vi[i]=false; } for(i=0;i { if(vi[i]==false) { BFS(i,vi); } } cout< delete[]vi; } template voidLGraph : BFS(intv,bool*vi) { inti; vi[v]=true; queue Q.push(v); while(! Q.empty()) { v=Q.front(); printf("%d",v); Q.pop(); for(i=0;i { intt=M[v][i].to; if(vi[t]==false) { Q.push(t); vi[t]=true; } } } } 算法分析: 每个顶点只进入一次队列,对于每个从队列取走的点,都查看其所有的邻接点,设顶点数是n,边数是e,时间复杂度是O(n+e)。 空间复杂度: 队列中元素个数不超过n个,同时需要定义数组标记已访问过的顶点,所以空间复杂度是O(n)。 (6)prim最小生成树算法 邻接表的prim算法与邻接矩阵类似,只是在循环的时候稍有不同,要注意邻接点是edge中的to元素。 算法的整体思路为,首先加入一个顶点到生成树中,然后,每次加入一条代价最小的边,边的一个顶点已被加入生成树,一个顶点未被加入生成树,然后标记这个边的顶点,表示将这个顶点也加入,加入之后更新其lowcost数组,然后继续选边,直到所有顶点都被标记。 定义三个临时数组,mark数组来实现上述标记,lowcost[j]记录与顶点j相接的边中最小的权值(边的另一个顶点未被标记),nearest[j]表示顶点j上述最小权值边的另一个顶点。 主函数打印最小生成树的边集(nearest[j],j,lowcost[j]) 代码: template voidLGraph : prim(intk,int*nearest,T*lowcost) { bool*mark=newbool[n+1]; for(inti=0;i { nearest[i]=-1; mark[i]=false; lowcost[i]=INF; } mark[k]=true; lowcost[k]=0; nearest[k]=k; for(inti=1;i { for(intj=0;j { intt=M[k][j].to; if(lowcost[t]>M[k][j].cost&&! (mark[t])) { lowcost[t]=M[k][j].cost; nearest[t]=k; } } intmin=INF; for(intj=0;j { if(! mark[j]&&lowcost[j] { min=lowcost[j]; k=j; } } mark[k]=true; } delete[]mark; } 算法分析: 由于要加入n个顶点,每次加入都要找最小边和更新lowcost,所以时间复杂度是O(n^2)。 空间复杂度: 定义两个一维数组,空间复杂度是O(n) (7)Dijkstra单源最短路算法 算法思路与邻接矩阵类似,不同的地方是开始的时候要将d数组和path数组分别赋值为INF何-1,将s数组赋
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据结构 基本 运算 方法