图的基本应用

图的基本概念

图一

图二,边上数字代表通过需要的时间

图三

假设图一是某一个位置的地图,其中1,2,3,4,为该地方的建筑物,建筑物与建筑物之间的线段为一条双向可通的路。

对于图一而言,每个建筑物被称为顶点,建筑物与建筑物之间的道路被称为而图这种数据结构就是这些顶点与关联这些顶点的集合。比如对于上图而言,其中顶点的集合就是{1,2,3,4},其边的集合就是{(1,2),(1,3),(1,4),(2,4)}。

另外,对于图而言,分为有向图无向图 : 无向图简单而言就是每条道路都可以双向通行的图,如图一图二。 有向图就是像图三一样,道路都是单向通行,也有可能是一来一回的,这种图被称为有向图。

在无向图中,一条结点连边的条数被称为这个图的度数。在有向图中,一个结点向别的结点连边的条数被称为入度,别的结点向一个结点连边的条数被称为出度

对于图二而言,我们标记了走每条道路需要消耗的时间。那么对于每条边的属性值,我们称之为边权。除了边以外,点也具有属性值,我们称点的属性值为点权。如果两个顶点之间不止一条边相连,我们这称之为重边。甚至有时候会出现一条边的起点和终点是一样的,这就会被称之为自环。在大多数时候,图的重边和自环都会被简化掉。而孤点是指不与任何其他其他结点相连的点,但是孤点参与节点对的构成,影响节点对的数量。

是指一条只有第一个和最后一个顶点重复的非空路径

图的存储

1.邻接矩阵

我们现在若想将图二和图三存储进计算机,在没有重边的情况下,可以采用邻接矩阵的方法。

使用一个二维数组arr[i][j]表示,arr[i][j]表示从点i到点j的边权。图二的邻接矩阵如下图所示。 图三就不搬了,与图二类似,与之不同的是,无向图的邻接矩阵是对称的,但是有向图就不一定对称了。

代码实现,就是声明一个二维数组,然后读入邻接矩阵的数据就好了,比较简单,这里就不写出来了。

2.邻接表

邻接表的概念
虽然邻接矩阵确实可以将图二图三这样的结构存储进计算机,但是由于其开辟了一个 n 2 n^2 n2的二维数组,其空间复杂度为 O ( n 2 ) O(n^2) O(n2),后续遍历该数组,时间复杂度为 O ( n 2 ) O(n^2) O(n2)。毫无疑问,邻接矩阵的效率十分低下,所以大部分时候存储图结构,我们会采用邻接表。

邻接表的思想是,对于有向图而言,其中一条有向边 ( i , j ) (i,j) ij,不需要用 n 2 n^2 n2的数组来进行存下到其他点是否存在边,只需要存储一个点能够到达的顶点和相应的边长的集合即可。使用邻接表,其空间复杂度会优化到 O ( n ) O(n) O(n)。但是如果要查找到某个路径的边权,因为不知道具体存放位置,需要遍历以起点的所有边来找到,其时间复杂度为 O ( n ) O(n) O(n),不如邻接矩阵的 O ( 1 ) O(1) O(1)

在C++中,我们可以采用vector来实现邻接表。采用STL的pair和vector,pair的first和second分别表示一条边的终点和边权。每次读入一条边 ( u , v , l ) (u,v,l) (u,v,l),就使用p[u].push_back({v,l});即可,来表示为点u增加一条终点为v,边权为l的边。

邻接表的基本代码:

#include <iostream>
#include <vector>
using namespace std;
const int MAX = 1000;
int m, n;//图中m顶点,n条边。
vector<pair<int, int>> P[MAX];
int v[MAX][MAX];
int main()
{
	cin >> m >> n;
	for (int i = 1; i <= m; i++)
	{
		int u, v, l; cin >> u >> v >> l;
		P[u].push_back({ v,l });
		//P[v].push_back({u,l})
		//如果是无向图,则还需要将终点,起始点颠倒过来存储一下
	}

	//将邻接表转换为邻接矩阵
	for (int i = 1; i <= m; i++)
		for (int j = 0; j < P[i].size(); j++)
			v[i][P[i][j].first] = P[i][j].second;
	//对于邻接矩阵v[i][j],j就是终点,即为P[i][j].first,
	//其储存的值为边权,也就是P[i][j].second


	//output
	for (int i = 1; i <= m; i++) {
		for (int j = 1; j <= m; j++)
			cout << v[i][j] << " ";
		cout << endl;
	}
	return 0;
}

图的遍历

遍历图有两种方法,DFS(深度优先搜索)与BFS(广度优先搜索)。深搜与广搜就不多少了,参考上一篇文章
链接: 搜索算法-深搜与广搜

P5318 【深基18.例3】查找文献 - 洛谷 | 计算机科学教育新生态。

我们通过这题,来分别对图的遍历的两种方式进行举例,掌握DFS和BFS后,遍历图轻而易举。

1.DFS遍历图

void dfs(int x) 
{
	vis[x] = true;
	cout << x << " "; 
	for (int i=0; i<P[x].size(); i++)
		if (!vis[P[x][i]]) 
            dfs(P[x][i]);
}

2.BFS遍历图

queue<int> q;
void bfs(int x) 
{
	memset(vis, false, sizeof(vis));
	vis[x] = true;
	q.push(x);
	while (!q.empty())
     {
		int v = q.front();
		q.pop();
		cout << v << " ";
		for (int i=0; i<P[v].size(); i++) 
			if (!vis[P[v][i]]) 
            {
				vis[P[v][i]] = true;
				q.push(P[v][i]);
			}
	}
}

该题完整题解:

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5+5;
vector<int> P[MAXN];
int n, m;
bool vis[MAXN];
queue<int> q;
void dfs(int x) 
{
	vis[x] = true;
	cout << x << " ";
	for (int i = 0; i < P[x].size(); i++)
		if (!vis[P[x][i]]) 
			dfs(P[x][i]);
}

void bfs(int x) 
{
	memset(vis, false, sizeof(vis));
	vis[x] = true;
	q.push(x);
	while (!q.empty()) 
	{
		int v = q.front();
		q.pop();
		cout << v << " ";
		for (int i = 0; i < P[v].size(); i++)
			if (!vis[P[v][i]]) 
			{
				vis[P[v][i]] = true;
				q.push(P[v][i]);
			}
	}
}

int main() 
{
	cin >> n >> m;
	for (int i = 1; i <= m; i++) 
	{
		int u, v;
		cin >> u >> v;
		P[u].push_back(v);
	}
	for (int i = 1; i <= n; i++) 
		sort(P[i].begin(), P[i].end());
	dfs(1);
	cout << endl;
	bfs(1);
	return 0;
}

DAG与拓扑排序

基本概念

DAG:对于一个图而言,如果这个图是没有环的,但是边是有方向的,那么就称之为有向无环图,即DAG。

拓扑排序:拓扑排序就是在DAG的基础上对点进行排序,使得在搜到点x时所有能到达点x的点y已经被搜过了。其具体实现流程如下: 1.将所有入度为0的点加入处理队列 2.将处于队头的点x取出,遍历x所能到达的所有点y。 3. 对于每一个y,删除从点x到点y的边。 4.如果点y的入度减为0了,说明说明所有能到y的点都被计算过了,再将点y加入处理队列。 5.重复2,直到队列为空。

求一个DAG的最长路

P1113 杂务 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 挺有价值的题目,该题要求人掌握如何求解一个DAG中的最长路。该题要使用简单DP+深搜,我感觉挺难的。另外这题,也可以使用拓扑排序来做。这里,我们需要创建一个value数组,用于存储做第x件事情时需要花费的时间。

记忆化搜索做法

#include<bits/stdc++.h>
using namespace std;
const int MAX = 1e4 + 10;
vector<int> V[MAX];
int value[MAX];
int vis[MAX];
int dfs(int x) {
    if (vis[x]) return vis[x];
    for (int i = 0; i < V[x].size(); i++)
        vis[x] = max(vis[x], dfs(V[x][i]));
    vis[x] += value[x];
    return vis[x];
}
int main() 
{
    int n,x,y,res=0; cin >> n;
    for (int i = 1; i <= n; i++)
    {
         cin >> x >> value[i];
         while (cin >> y) {
             if (!y)break;
             else
                 V[x].push_back(y);
        }
    }
    for (int i = 1; i <= n; i++)
        res = max(res, dfs(i));
    cout << res;
    return 0;
}

拓扑排序做法

#include<bits/stdc++.h>
using namespace std;
const int MAX = 1e5 + 10;
vector<int> V[MAX];
queue<int> Q;
int ind[MAX], value[MAX],ans[MAX];
int main() 
{
    int n, y,res = 0; cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int x; cin >> x >>value[i];
        while (cin >> y) {
            if (!y)break;
            V[y].push_back(x);
            ind[x]++;
        }
    }

    for (int i = 1; i <= n; i++) {
        if (ind[i] == 0)
            Q.push(i),ans[i]=value[i];
    }

    while (!Q.empty()) {
        int x = Q.front(); Q.pop();
        for (int i = 0; i < V[x].size(); i++) {
            int y = V[x][i];
            ind[y]--;
            if (ind[y] == 0)
                Q.push(y);
            ans[y] = max(ans[y], ans[x] + value[y]);
        }
    }

    for (int i = 1; i <= n; i++)
    {
        res = max(res, ans[i]);
    }
    cout << res;
    return 0;
}

拓扑排序例题

P4017 最大食物链计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

深入浅出的拓扑排序经典例题,我们刚才已经了解过,拓扑排序,可以使得搜到点x时所有能到达点x的y都已经被搜索过,这题说求出最大的食物链,这里最大的食物链的定义就是说,一条入度为0的结点到一条出度为0的点的链,那么对于这题,我们就可以采用拓扑排序计数每一个结点从任意入度为0的点到此点的食物链计数,最后再统计所有出度为0的点所对应的计数的值就好了。

#include<bits/stdc++.h>
using namespace std;
const int MAX = 5e5 + 10;
const int MOD = 80112002;
vector<int> V[MAX];
queue<int> Q;
int ind[MAX], outd[MAX], f[MAX];
int main() 
{
    int n, m, res = 0; cin >> n >> m;
    for (int i = 0; i < m; i++)
    {
        int x, y; cin >> x >> y;
        outd[x]++; ind[y]++;
        V[x].push_back(y);
    }

    for (int i = 1; i <= n; i++) {
        if (ind[i] == 0)
            Q.push(i),f[i]=1;
    }

    while (!Q.empty()) {
        int x = Q.front(); Q.pop();
        for (int i = 0; i < V[x].size(); i++) {
            int y = V[x][i];
            f[y] = (f[x] + f[y]) % MOD;
            ind[y]--;
            if (ind[y] == 0)
                Q.push(y);
        }
    }

    for (int i = 1; i <= n; i++)
    {
        if (outd[i] == 0)
            res = (res + f[i]) % MOD;
    }
    cout << res;
    return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值