当前位置:   article > 正文

数据结构:图的存储与遍历

图的存储与遍历

目录

一、基本概念和一般结论

二、图的存储结构

2.1、邻接矩阵

2.2、邻接表

2.3、链式前向星

2.4、vector简洁存储

三、图的遍历

3.1、深度优先遍历DFS

3.2、广度优先遍历

3.3、多源BFS

四、应用

4.1、判断无向图是否连通及连通分量数目

4.2、判断图中顶点u到v是否存在路径

4.3、给定n个顶点的有向无环图,输出顶点0到顶点n-1的所有路径

4.4、判断无向图中是否有环

4.5、判断有向图中是否有环

4.5.1、三色标记法

4.5.2、拓扑排序


        图(Graph)是一种较线性表和树更为复杂的非线性结构。在图结构中,对结点(图中常称为顶点)的前驱和后继个数不加限制, 即结点之间的关系是任意的。

一、基本概念和一般结论

13477d960fab4f0daf6e0470456f7a54.png
因为一条边关联两个顶点,而且使得这两个顶点的度数 分别增加1。因此顶点的度数之和就是边的两倍。

6cf67d371ede449e9043f50c392ed36f.png

关于连通图:

b669c912b6164173b42df7b7f7feda68.png

dad206945a8a4300b0670bd27c7b127f.png

无向图:连通图,连通子图,极大连通子图(连通分量,即一个图中最大的某个连通子图,即无法增加顶点以及边 使其构成更大的连通图)。无向图的连通关系是等价关系,具有传递性,自反性以及对称性。

有向图:强连通图,强连通子图,强连通分量。

连通分量不一定唯一。

二、图的存储结构

2.1、邻接矩阵

6f57cc288c1f4f9a8e28d2783b1a24b4.png

邻接矩阵可以方便求出图中顶点的度:

对于邻接矩阵的第i行,如果其第j列有一个1(或者一个权值),则说明有一条边从vi指向vj。如果是有向图,则表示存在一条边<vi,vj>,通过行可以看出出度,通过列可以看出入度。


2.2、邻接表

f6ca0085a9894a18bd46fa5ea8dffad1.pngcb53ffaf68c14e19967c0cf6b82f24f2.png

        总的来说就是,顺序存储图中的所有顶点信息,每个顶点信息包括顶点编号(也可以不用,因为顺序存储用数组标识的话,数组位置就是顶点编号),以及顶点的边链表头结点指针。一个顶点的边链表是将 以该顶点为起始的 连向的其他顶点信息。

       有向图的邻接表,边链表结点个数等于有向边的条数。(因为每条有向边有且仅有一个起始和一个终止)

        无向图的邻接表,边链表结点个数等于无向边的条数*2。(无向边是双向的)

7a5ff73cae74411e9ffe15ea88e1449b.png

  1. struct Edge{//边链表结点
  2. int VerName;//顶点编号
  3. int cost;//边权值
  4. Edge *next;//指向下一个边链表结点
  5. }
  6. struct Vertex{//顶点表结点
  7. int VerName;//顶点编号
  8. Edge * adjacent;//边链表指针
  9. }

2.3、链式前向星

        实际上就是使用静态链表实现的邻接表。数据结构:静态链表(编程技巧)-CSDN博客

这样做的好处是,不需要使用指针,也不需要使用指针访问,直接通过数组下标即可访问,速度更快,内存更少。

插入表尾:

  1. vector<int> head(n,-1);//n个顶点,head[i]指向边链表,初始都没有
  2. vector<int> VerName;//边链表,adjacent[i]存的是边链表顶点信息
  3. vector<int> next;//边链表的指针信息
  4. vector<int> cost;//边的权值
  5. /*可以理解为:
  6. struct head{
  7. Edge* head[i];//head[i]表示的是第i个顶点的边链表表头。这里实际上是一个下标
  8. }
  9. struct Edge{//他们仨 下标是一样的
  10. int VerName;
  11. Edge* next;
  12. int cost;
  13. };
  14. */
  15. int Vertex;
  16. int edge;
  17. int cos;
  18. while(cin>>Vertex>>edge>>cos){//假定输入都是正确的,没有重边
  19. //为了复杂化,我们先找到边链表的最后一个结点
  20. int adj=head[Vertex];
  21. if(adj!=-1)
  22. while(next[adj]!=-1){
  23. adj=next[adj];
  24. }
  25. /*创建一个新结点 其之后无指向即next=-1*/
  26. VerName.push_back(edge);
  27. next.push_back(-1);
  28. cost.push_back(cos);
  29. /*-adj为Vertex边链表的最后一个元素-*/
  30. if(adj!=-1)
  31. next[adj]=next.size()-1;//VerName.size()-1、cost.size()-1均可 就是指向最后一个嘛。
  32. if(adj==-1){//只可能是顶点还没有边
  33. head[Vertex]=next.size()-1;
  34. }
  35. }
  36. //顺次输出边
  37. for(int i=0;i<n;++i){
  38. int adj=head[i];
  39. while(adj!=-1){
  40. cout<<i<<"--->"<<VerName[adj]<<" cost为:"<<cost[adj]<<endl;
  41. adj=next[adj];
  42. }
  43. }
  44. //我们会发现,将adj当做一个结构体指针Edge *,那么VerName[adj]相当于adj->VerName了

直接插入表头:

  1. while(cin>>Vertex>>edge>>cos){/*直接创建点,插入在Vertex的边链表表头*/
  2. VerName.push_back(edge);
  3. next.push_back(head[Vertex]);
  4. cost.push_back(cos);
  5. head[Vertex]=next.size()-1;
  6. }

2.4、vector简洁存储

由于STL中vector的引用,实际上vector也很像一个链表了,所以我们已经不需要写邻接表来存储。

  1. vector<vector<pair<int,int> > > g(n);//使用pair<int,int> first存储顶点信息,second存储边权值
  2. //如果包含更多信息可以自定义结构体。
  3. while(cin>>Vertex>>edge>>cos){/*直接创建点,插入在Vertex的边链表表头*/
  4. g[Vertex].emplace_back(edge,cost);
  5. }
  6. //g[i] 是一个vector<pair<int,int> > 类型,存储的是顶点i的边链表
  7. //g[i].size()==0 则没有出边

三、图的遍历

3.1、深度优先遍历DFS

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define n 100
  4. struct Edge {
  5. int VerName;
  6. int cost;
  7. Edge * next=nullptr;
  8. };
  9. struct Vertex{
  10. int VerName;
  11. Edge *edge=nullptr;
  12. };
  13. int vis[n];
  14. Vertex Head[n];
  15. void DFS(Vertex *Head,int v,int vis[]) {
  16. vis[v]=1;//遍历到则标记,也可以入栈时标记,DFS每次只走一条路,所以无所谓都一样。
  17. printf("%d ",v);
  18. for(Edge * edge=Head[v].edge;edge!=nullptr;edge=edge->next)
  19. if(!vis[edge->VerName])
  20. DFS(Head,edge->VerName,vis);
  21. return;
  22. }
  23. int main(void){
  24. for(int i=0;i<n;++i)//非连通图,需要从多个非连通分量开始遍历。
  25. if(vis[i]==0)
  26. DFS(Head,i,vis);
  27. return 0;
  28. }

3.2、广度优先遍历

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define n 100
  4. struct Edge {
  5. int VerName;
  6. int cost;
  7. Edge * next=nullptr;
  8. };
  9. struct Vertex{
  10. int VerName;
  11. Edge *edge=nullptr;
  12. };
  13. int vis[n];
  14. Vertex Head[n];
  15. void BFS(Vertex *Head,int v,int vis[]) {
  16. queue<int> q;
  17. vis[v]=1;
  18. q.push(v);
  19. while(!q.empty()) {
  20. int cur=q.front();
  21. printf("%d ",cur);
  22. q.pop();
  23. for(Edge * edge=Head[cur].edge;edge!=nullptr;edge=edge->next) {
  24. if(!vis[edge->VerName]) {
  25. vis[edge->VerName]=1;//入队时标记,防止多次入队
  26. q.push(edge->VerName);
  27. }
  28. }
  29. }
  30. return;
  31. }
  32. int main(void){
  33. for(int i=0;i<n;++i)
  34. if(vis[i]==0)
  35. BFS(Head,i,vis);
  36. return 0;
  37. }

3.3、多源BFS

        主要思路大概是初始时,多个点进入队列然后进行BFS。将某一等价集合视作同一个起始点(超级源点),先将此源点入队,再一层一层向外扩张。相当于这些初始出队的源点在整个问题中地位等价。另外还可以视作不同的起点同时进行BFS操作,而不需要依次作为起点依次BFS,有时写在一起更方便。

  1. queue<int> q;
  2. for(auto i:graph)
  3. if(start(i)==true)//如果i是起点的话就入队
  4. q.push(i);
  5. while(!q.empty()){//所有起点一起BFS
  6. //BFS
  7. }

四、应用

4.1、判断无向图是否连通及连通分量数目

(1)图的遍历:每遍历一次会遍历图的一个连通分量,与该连通分量不连通的必须再次遍历。

  1. int cnt=0;
  2. for(int i=0;i<n;++i){
  3. if(!vis[i]){
  4. cnt++;
  5. DFS(Head,i,vis);//BFS(Head,i,vist);
  6. }
  7. }

(2)并查集:将存在边的两个顶点放入一个并查集中,可以找到连通分量数目

4.2、判断图中顶点uv是否存在路径

这里无向图有向图均可,所以实际上只需要从u开始遍历,能遍历到v则说明存在路径。

  1. DFS(Head,u,vis);//BFS(Head,u,vis);
  2. if(vis[v]==1) printf("u到v存在路径");
  3. //修改DFS:
  4. bool DFS(Vertex * Head,int u,int vis[]){
  5. if(u==v) return true;
  6. for(Edge * edge=Head[u].edge;edge!=nullptr;edge=edge->next){
  7. if(vis[edge->VerName]==0){
  8. vis[edge->VerName]=1;
  9. if(DFS(Head,edge->VerName,vis)) return true;
  10. }
  11. }
  12. return false;
  13. }
  14. main:
  15. vis[u]=1;
  16. if(DFS(Head,u,vis)) printf("u到v存在路径");

4.3、给定n个顶点的有向无环图,输出顶点0到顶点n-1的所有路径

        这里实际上是一个回溯问题,对于有向无环图,它不可能往回走,并且需要输出所有路径,我们甚至不用标记点(不标记点的两个原因:①有向无环图在递归遍历时不可能走重复路径②同一个点在不同路径上可以重复遍历)。

  1. #define n 100
  2. struct Edge {
  3. int VerName;
  4. int cost;
  5. Edge * next=nullptr;
  6. };
  7. struct Vertex{
  8. int VerName;
  9. Edge *edge=nullptr;
  10. };
  11. Vertex Head[n];
  12. vector<vector<int>> Allpath;
  13. vector<int> path;
  14. void FindPath(Vertex *Head,int u){
  15. path.push_back(u);
  16. if(u==n-1){
  17. Allpath.push_back(path);
  18. path.pop_back();
  19. return;
  20. }
  21. for(Edge * edge=Head[u].edge;edge!=nullptr;edge=edge->next) {
  22. FindPath(Head,edge->VerName);
  23. }
  24. path.pop_back();
  25. return;
  26. }
  27. main:
  28. FindPath(Head,0,n-1);

4.4、判断无向图中是否有环

(1)深度优先遍历:访问的过程中遇到非前驱并且被访问过的顶点,则有环。

这必然是可以找到的,如果存在环,进入该环的任意一个点开始遍历,一定可以通过若干步后回到该点。

(2)并查集:每次读入一条边(x,y),先判断x和y是否具有连通性(并查集),如果具有连通性,那么说明原本x就可以走到y,现在加入边(x,y)就构成了环。

  1. while(cin>>x>>y){
  2. if(Find(x)==Find(y)
  3. return true;
  4. else Union(x,y);
  5. }

4.5、判断有向图中是否有环

4.5.1、三色标记法

        对于一个有向图,在DFS遍历的过程中访问到已经被遍历的顶点,这并不一定就是当前路径上的点。因此我们需要不同的标记:多标记法。DFS:O(n+e)

从未被遍历过vis[i]=0;

正在遍历的路径上vis[i]=1;

已经遍历完的路径上vis[i]=2;//可以想象二叉树的结果理解什么时候回变成vis[i]=2,根的左子树遍历时不会影响到右子树,左子树遍历完了 则有 左子树vis[i]=2

只有遍历到vis[i]=1的点,才构成环。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define n 100
  4. struct Edge {
  5. int VerName;
  6. int cost;
  7. Edge * next=nullptr;
  8. };
  9. struct Vertex{
  10. int VerName;
  11. Edge *edge=nullptr;
  12. };
  13. int vis[n];
  14. Vertex Head[n];
  15. bool DFS(Vertex * Head,int u,int vis[]) {
  16. vis[u]=1;
  17. for(Edge * edge=Head[u].edge;edge!=nullptr;edge=edge->next) {
  18. if(vis[edge->VerName]==1) return true;//遍历到了!
  19. if(vis[edge->VerName]==0)//等于2的情况下 已经遍历完了的,不需要再遍历了
  20. if(DFS(Head,edge->VerName,vis)) return true;
  21. }
  22. vis[u]=2;//它的邻居遍历完了,它也没地方走了
  23. return false;
  24. }
  25. int main(void){
  26. for(int i=0;i<n;++i)
  27. if(vis[i]==0)
  28. if(DFS(Head,i,vis)) printf("存在环!\n");
  29. return 0;
  30. }

(虽然无向图也不一定是当前路径但是 已经被遍历过的顶点v一定与当前顶点u是连通的(有路径可走,如v-->原点-->u),然后还有现在的一条边(v,u),因此一定构成环)。

4.5.2、拓扑排序

O(n+e)

数据结构:图的拓扑排序与关键路径-CSDN博客

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/877144
推荐阅读
相关标签
  

闽ICP备14008679号