图的三种存储方式

博客介绍了图的三种存储方式。邻接矩阵用二维数组表示图,对稀疏图和顶点数多的情况不友好;邻接表将与顶点相邻的顶点接在其后形成链表,常用STL的vector记录;链式前向星记录与顶点相连的边的信息,通过head数组可快速找到以某顶点为起点的所有边。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、邻接矩阵

  • 使用一个二维数组表示图,例如graph[i][j]graph[i][j]graph[i][j]表示顶点iiijjj之间的权值,但是对于稀疏图,以及顶点数稍微多一些的情况非常不友好,浪费很多空间,不过有时也能使用。随便贴一道题如下

问题描述:
对于一个有向图,求从指定顶点出发能够找到的顶点数

输入

第1行:2个空格分开的整数n(2&lt;=n&lt;=200)n(2&lt;=n&lt;=200)n(2<=n<=200)m(10&lt;=m&lt;=20000)m(10&lt;=m&lt;=20000)m(10<=m<=20000),分别表示图的顶点数和边数。
2..m+12..m+12..m+1行:每行222个空格分开的整数iiijjjiii表示一条边的起点,jjj表示终点。
m+2m+2m2行:111个整数k(1&lt;=k&lt;=n)k(1&lt;=k&lt;=n)k(1<=k<=n),表示指定的顶点。

输出
一个整数,能够找到的最多顶点数

样例输入

5 8
3 5
4 5
4 3
2 3
5 4
4 1
4 2
2 4
1

样例输出

1

策略分析:

邻接矩阵存储,DFS即可

代码

#include <iostream>
#define MAXN 205
using namespace std; 
bool g[MAXN][MAXN], vst[MAXN];
int n, m, s, ans;
  
void dfs(int x) {
    if(vst[x]) return; //减枝
    for(int i = 1; i <= n; i++) 
        if(g[x][i]) {
            g[x][i] = 0;
            dfs(i);
        }
    vst[x] = 1; //记录一下,避免重复搜索
}
  
int main () {
    ios::sync_with_stdio(0);
    int u, v, s;
    cin >> n >> m;
    for(int i = 1; i <= m; i++) {
        cin >> u >> v;
        g[u][v] = 1;
    }
    cin >> s;
    dfs(s);
    for(int i = 1; i <= n; i++)
        if(vst[i])
            ans++;
    cout << ans << endl;
    return 0;
} 

二、邻接表

  • 对于一个顶点v,把所有和它相邻的顶点接在它后面,形成一个以它为头结点的链表,故有n个顶点,就有n个链表
  • 对于每一个顶点的链表,一般使用STL中的vector记录
题目同上
#include <iostream>
#include <vector>
#include <queue>
#define MAXN 205
using namespace std;
 
bool vst[MAXN]; 
vector<int> g[MAXN];
int n, m, s, ans = 1;
 
void bfs(int x) {
    queue<int> q;
    q.push(x);
    vst[x] = 1;
    while(!q.empty()) {
        int t = q.front();
        q.pop();
        int len = g[t].size(), u;
        for(int i = 0; i < len; i++) {
            u = g[t][i];
            if(!vst[u]) {
                vst[u] = 1;
                q.push(u);
                ans++;
            } 
        }
    }
}
 
int main () {
    ios::sync_with_stdio(0);
    int u, v, s;
    cin >> n >> m;
    for(int i = 1; i <= m; i++) {
        cin >> u >> v;
        g[u].push_back(v);
    }
    cin >> s;
    bfs(s);
    cout << ans << endl;
    return 0;
}

三、链式前向星

  • 记录了与每一个顶点相连的边的信息,方便快捷
/*
	链式前向星存图 
*/
struct Edge {
	//其中to表示当前边指向的终点,w表示该边的权值,next表示与该条边同起点的下一条边的存储位置 
	int to, next, w; 
}edge[MAXN];

// 其中head[i]表示的是以i为起点的,最后输出进head的那条边的编号(也被叫做第一条) 一般初始化-1 
int head[MAXN];
// 其中cnt表示当前这条边的编号,因为这条边的起点是u所以head[u] = cnt,更新连接顶点的编号 
inline void add(int u, int v, int cost) {
	edge[++cnt].to = v;
	edge[cnt].w = cost;
	edge[cnt].next = head[u];
	head[u] = cnt;
} 
for(int i = head[s]; i != -1; i = edge[i].next[i]) //链式前向星搜边

考虑这样一组数据,每一行两个数据分别表示这两个顶点相连接(为了简便,暂不考虑权值)

1 2
2 3
3 4
1 3
4 1
1 5
4 5

如果使用链式前向星存储,那么会有这样的结果:
edge[1].to=2,&ThinSpace;&ThinSpace;edge[1].next=head[1]=−1,&ThinSpace;&ThinSpace;head[1]=1edge[1] .to = 2,\,\,edge[1].next=head[1]=-1,\,\,head[1]=1edge[1].to=2,edge[1].next=head[1]=1,head[1]=1
edge[2].to=3,&ThinSpace;&ThinSpace;edge[2].next=head[2]=−1,&ThinSpace;&ThinSpace;head[2]=2edge[2] .to = 3,\,\,edge[2].next=head[2]=-1,\,\,head[2]=2edge[2].to=3,edge[2].next=head[2]=1,head[2]=2
edge[3].to=4,&ThinSpace;&ThinSpace;edge[3].next=head[3]=−1,&ThinSpace;&ThinSpace;head[3]=3edge[3] .to = 4,\,\,edge[3].next=head[3]=-1,\,\,head[3]=3edge[3].to=4,edge[3].next=head[3]=1,head[3]=3
edge[4].to=3,&ThinSpace;&ThinSpace;edge[4].next=head[1]=1,&ThinSpace;&ThinSpace;head[1]=4edge[4] .to = 3,\,\,edge[4].next=head[1]= 1,\,\,head[1]=4edge[4].to=3,edge[4].next=head[1]=1,head[1]=4
edge[5].to=1,&ThinSpace;&ThinSpace;edge[5].next=head[4]=−1,&ThinSpace;&ThinSpace;head[4]=5edge[5] .to = 1,\,\,edge[5].next=head[4]=-1,\,\,head[4]=5edge[5].to=1,edge[5].next=head[4]=1,head[4]=5
edge[6].to=5,&ThinSpace;&ThinSpace;edge[6].next=head[1]=4,&ThinSpace;&ThinSpace;head[1]=6edge[6] .to = 5,\,\,edge[6].next=head[1]=4,\,\,head[1]=6edge[6].to=5,edge[6].next=head[1]=4,head[1]=6
edge[7].to=5,&ThinSpace;&ThinSpace;edge[7].next=head[4]=5,&ThinSpace;&ThinSpace;head[i]=7edge[7] .to = 5,\,\,edge[7].next=head[4]=5,\,\,head[i]=7edge[7].to=5,edge[7].next=head[4]=5,head[i]=7

存储完毕后,其中head数组被保存如下
head[1]=6head[1]=6head[1]=6
head[2]=2head[2]=2head[2]=2
head[3]=3head[3]=3head[3]=3
head[4]=7head[4]=7head[4]=7

  • 其中head[i]head[i]head[i]表示的是以i为起点的边的编号(边的编号就是在顺序输入边的时候产生的,就是cntcntcnt,第一行的边,编号就是111,第二行就是222,依次往后)
  • 那么我们可以试着找一下看下是否正确,对于head[1]head[1]head[1],表示的是与起点111相连的最后存进去的一条边,head[1]head[1]head[1]的值为666,因此与起点1连接的是第666条边,可以看到输出的时候第666行为1&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;51 \,\,\,\,515,这条边的信息被存在edge[6]edge[6]edge[6]中,然后我们继续往后找,edge[6].next=4edge[6].next = 4edge[6].next=4,说明下一次要访问的是第4条边,连接的是1&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;31 \,\,\,\,313顶点,不断继续找,这样就可以快速找到顶点1为起点的所有边。同理其他顶点也是如此(可以自行画图理解)
  • 因此,对于建图的过程,每一次添加一条新的边的时候,关键在于把相同起点的这条边的上一条边记录在head数组中,同时对于新来的这条边,更新head数组(因为每一次添加边的时候都包含了当前顶点的信息,所以要不断更新head数组,才能倒着回去找到每一条边)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值