图的简介及图的存储方式

图论无疑是我们接下来将要学习的一个重点部分,DFS,BFS,最短路径,最小生成树等等,都需要用到图论的相关概念和知识。首先让我们了解一下图的基本概念

1、什么是图?

在其他结构中,数据之间往往具有一定结构性的联系,比如在“树”这种结构中,数据之间是有明确的层级关系的。而在图中,数据之间没有任何强制性的关系,任何两个元素都有可能相关。

2、图的结构是什么?

一个图一般由两部分构成,顶点和连接他们的边。顶点一般不做区分,而边可以分为有向边和无向边。
无向边
在顶点U和V之间的边没有方向,则称为无向边。
有向边
在顶点U和V之间的边存在方向,则称为有向边。

由边的类型,我们可以引入下面几种图的类型:

无向图
如果一个图中任意一条边都为无向边,则该图为无向图;
有向图
如果一个图中任意一条边都为有向边,则该图为有向图。

3、图的存储

邻接矩阵

邻接矩阵是最为简单直接的一种存储图的方式,它使用了矩阵的方式对图进行存储。
使用这种方式存储图有以下几点好处:
1、直观、容易理解;
2、方便检查两点之间是否存在边(虽然实际中用不太到)

比如我们有这么一个图:
在这里插入图片描述
那么使用邻接矩阵进行储存就是下面的样子:
在这里插入图片描述
如果我们把这个图变为有向图,那么在邻接表中该图会变为如下的情况:
在这里插入图片描述
对于这种存储方式,坏处也是非常明显的,它的时间和空间复杂度都比较高,都为n^2,在存储较为稠密的图(边的数量较多而点的数量较少)时,空间和时间浪费不多,但在存储边较少而点较多的图时,对时间和空间的浪费都不容小觑。在这种情况下,我们可以使用一种链式结构,将稀疏图中没有没有连接上的边跳过。

练习题:图的存储方式一:https://acm.sdut.edu.cn/onlinejudge3/problems/3116
该题就是很普通的邻接矩阵存图查询,需要注意需使用bool开数组

#include <iostream>
#include <cstring>
#include <cstdio>
#include <string>
#include <algorithm>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <map>
#include <sstream>
using namespace std;
bool Map[5005][5005];
int main() {
    ios :: sync_with_stdio(false);
    int n,m,u,v,q;
    while(cin>>n>>m) {
        memset(Map,0,sizeof(Map));
        while(m--){
            cin>>u>>v;
            Map[u][v] = true;
        }
        cin>>q;
        while(q--){
            cin>>u>>v;
            if(Map[u][v]){
                cout<<"Yes\n";
            }else{
                cout<<"No\n";
            }
        }
    }
    return 0;
}

邻接表

邻接表是一种顺序结构和链式结构相结合的存储结构,使用数组存储顶点的数据信息,使用链表存放边的信息。
首先邻接表有一个数组,用于存放图的顶点信息,并以每一个顶点作为头结点,根据与顶点连接的边的信息生成链表,在代码上则是跟链表相差无几。

该题是图的存储方式四:https://acm.sdut.edu.cn/onlinejudge3/problems/3467
模板题的简单变形

#include <iostream>
#include <cstring>
#include <cstdio>
#include <string>
#include <algorithm>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <map>
#include <sstream>
using namespace std;
struct node{
    int v;
    node *next;
}*arr[5005],*p;
void add(int u,int v){//插入一条顶点为u终点为v的边
    p = new node;
    if(arr[u]==NULL){
        p->v = v;
        p->next = NULL;
        arr[u]= p;
    }else{
        p->v = v;
        p->next = arr[u]->next;
        arr[u]->next = p;
    }
}
bool query(int u,int v){//查询一条起点为u终点为v的边
    node *a = arr[u];
    while(a){
        if(a->v == v){
            return true;
        }
        a = a->next;
    }
    return false;
}
int main() {
    ios :: sync_with_stdio(false);
    int n,m,u,v,q;
    while(cin>>n){
        memset(arr,0,sizeof(arr));
        for(int i = 0;i<n;i++){
            for(int j = 0;j<n;j++){
                cin>>u;
                if(u){
                    add(i,j);
                }
            }

        }
        cin>>q;
        while(q--){
            cin>>u>>v;
            if(query(u,v)){
                cout<<"Yes\n";
            }else{
                cout<<"No\n";
            }
        }
    }
    return 0;
}

邻接表这种储存图的数据结构应该是实际应用中较为广泛的一种,而且其实现方式不止一种,如接下来要讲的链式前向星以及使用vector实现的邻接表。

链式前向星

链式前向星其实就是不是用指针而使用下标来进行链接的邻接表,相对于使用指针的邻接表来说,链式前向星在代码上更清爽一些,且如果有多组输入的需求,使用指针的邻接表无法及时释放内存,容易造成内存溢出的错误。

在建表操作上,与邻接表的建表思想是一样的,都是通过不断向链式前向星的最前端插入边并不断更新最新存入边的编号,来达到链接的效果。

在讲的过程中我发现链式前向星其实就是链表,只不过将普通链表中的地址由编译器生成改成了自己掌控,使用cnt变量来分配地址,使用edge来进行地址元素的管理,其他操作都与链表结构的邻接表无异,放到链表里来说就是逆序建立数组的操作。

图的存储方式二:https://acm.sdut.edu.cn/onlinejudge3/problems/3117
这道题由于数据量较大,需要使用链式前向星,也是一道模板题

#include <iostream>
#include <cstring>
#include <cstdio>
#include <string>
#include <algorithm>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <map>
#include <sstream>
using namespace std;
struct node{
    int data;
    int next;
}edge[500005];
int cnt = 0;
int head[500005];
void add(int u,int v){
    edge[cnt].data = v;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}
bool query(int u,int v){
    for(int i = head[u];i!=-1;i = edge[i].next){
        if(edge[i].data==v){
            return true;
        }
    }
    return false;
}
int main() {
    ios :: sync_with_stdio(false);
    int n,m,u,v,q;
    while(cin>>n>>m) {
        memset(head,-1,sizeof(head));
        while(m--){
            cin>>u>>v;
            add(u,v);
        }
        cin>>q;
        while(q--){
            cin>>u>>v;
            if(query(u,v)){
                cout<<"Yes\n";
            }else{
                cout<<"No\n";
            }
        }
    }
    return 0;
}

邻接表(vector实现)

上述的两种邻接表的实现方式都较为复杂,如果有时间、空间不那么严格,但又需要邻接表存图的题,不妨试一试vector存图的方式。

vector是c++中stl的一个类,通俗的讲就是一种可以自由增减长度的数组,而且有很多很方便的操作,这里只讲几种对于邻接表实现有用的操作。
1、.push_back(t):这个操作可以将参数放到vector数组的最后一位
2、.size():这个操作可以返回vector数组的元素个数

仍然是一道模板题的使用案例

#include <iostream>
#include <cstring>
#include <cstdio>
#include <string>
#include <algorithm>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <map>
#include <sstream>
using namespace std;

int main() {
    ios :: sync_with_stdio(false);
    int n,m,u,v,q;
    while(cin>>n>>m){
        vector<int> a[500005];
        while(m--){
            cin>>u>>v;
            a[u].push_back(v);
        }
        cin>>q;
        while(q--){
            cin>>u>>v;
            int flag = 0;
            for(int i = 0;i<a[u].size();i++){
                if(a[u][i] == v){
                    flag = 1;
                    break;
                }
            }
            if(flag){
                cout<<"Yes\n";
            }else{
                cout<<"No\n";
            }
        }
    }
    return 0;
}

练习题目:https://acm.sdut.edu.cn/onlinejudge3/contests/3174/overview
参考博客:https://blog.csdn.net/weixin_43721423/article/details/86681572
https://jhcloud.top/blog/?p=2147

猜你喜欢

转载自blog.csdn.net/rwbyblake/article/details/106281836
今日推荐