2021_GDUT_新生专题训练_图论

基本概念

图 是 由 顶 点 V 的 集 合 和 边 E 的 集 合 组 成 的 二 元 组 记 G = ( V , E )

一些种类区分

无向图 :边 没 有 方 向
有向图 :边 有 方 向
带权图 :边 有 数 量 意 义
完全图 :每 一 对 不 同 的 顶 点 都 有 一 条 边 相 连
n 个 顶 点 的 完 全 图 共 有 n*(n-1)/2 条 边
连通图 :在 无 向 图 G 中 , 如 果 从 顶 点 u 到 顶 点 V 有 路 径 , 则 称 u 和 v 连 通 的 。 对 于 图 中 任 意 两 个 顶 点 u 和 v 都 是 连 通 的 , 则 称 图 g 是 连 通 图 , 否 则 称 为 非 连 通 图 。 在 有 向 图 G 中 , 如 果 对 于 任 意 两 个 顶 点 u 和 v , 从 u 到 v 和 从 v 到 u 都 存 在 路 径 , 则 称 图 G 是 强 连 通 图
顶点的度 :与 顶 点 关 联 的 边 的 数 目 , 有 向 图 中 等 于 该 顶 点 的 入 度 与 出 度 之 和
入 度 —— 以 该 顶 点 为 终 点 的 边 的 数 目
出 度 —— 以 该 顶 点 为 起 点 的 边 的 数 目 和 度 数 为 奇 数 的 顶 点 叫 做 奇 点 , 度 数 为 偶 数 的 点 叫 做 偶 点 。

图的存储

邻接矩阵

用mp[ i ] [ j ]来表示i和j是否连接或它们间的权值

int mp[N][N];
for(int i=1;i<=m;i++){
	int u,v,val;
	cin >> u >> v >> val;
	mp[u][v] = mp[v][u] = val;
}
邻接表

下面是从大佬那抄来的

为了实现链表的结构,我们还要加上一个后继指针存它的下标,同时开一个头指针数组 h[],初始化为 h[i]=i记录每一个点的头指针,然后每当有新的边时就让新的边的后继指针指向 e[h[i]],然后把 h[i]换成当前边的数组下标,就完成了存图过程

struct {
    int v;//终点
    int val;//权值
    int next;//下一个的下标
}e[N];//边
int h[N];//h[i]表示i的上家
int cnt = 0;
void add(int a,int b,int c){
    e[++cnt].next = h[a];
    e[cnt].v = b;
    e[cnt].val = c;
    h[a] = cnt;
}

最短路

求取某个图中两个点(常用u,v点来表示)的最短路径,常见的有Floyd和dijkstra算法

关于图其实我也不知道写些什么就随便乱写吧,看到一个大佬的博客挺不错的传送门

floyd

本质是一个dp,但是非常暴力,听说280以下比较稳

for(int k=1;k<=n;k++){
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            mp[i][j] = min(mp[i][k]+mp[k][j],mp[i][j]);
        }
    }
}

时间复杂度为O(|V|3)

dijkstra

用到邻接矩阵

原理是维护起点到其他所有点的最小值,最开始只有起点,每次加入一个dis最小的点,然后更新dis

void dijkstra()
{
    //这里以1为起点
	for(int i=1;i<=n;i++)
	{
		dis[i]=mp[1][i];
	}
    
    
	for(int i=1;i<=n-1;i++)//找n-1轮 
	{
		int pos = 0;//用pos来表示dis最小的位置
		for(int j=1;j<=n;j++)
		{
			if(!vis[j] && (pos==0 || dis[j]<dis[pos]))
				pos = j;
        }
		vis[pos]=1;
		for(int j=1;j<=n;j++)//找着最近的点后通过这个最近的点更新其余的点到起点的距离 
		{
			if(vis[j]==0&&dis[u]+mp[u][j]<dis[j])
			{
				dis[j]=dis[u]+mp[u][j];
			}
		}
	}
	printf("%d\n",dis[n]);
}

时间复杂度为O(|V|2+|E|)

最短路(例题

在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt。但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你可以帮助他们吗?

input

输入包括多组数据。每组数据第一行是两个整数N、M(N<=100,M<=10000),N表示成都的大街上有几个路口,标号为1的路口是商店所在地,标号为N的路口是赛场所在地,M则表示在成都有几条路。N=M=0表示输入结束。接下来M行,每行包括3个整数A,B,C(1<=A,B<=N,1<=C<=1000),表示在路口A与路口B之间有一条路,我们的工作人员需要C分钟的时间走过这条路。
输入保证至少存在1条商店到赛场的路线。

** ouput**

对于每组输入,输出一行,表示工作人员从商店走到赛场的最短时间

Sample Input

2 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
0 0

Sample Output

3
2

模板题直接上代码

#include <iostream>
using namespace std;
#include <string.h>
#include <algorithm>
int n,m;
int mp[105][105];
void floyd(){
    for(int k=1;k<n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(mp[i][k] && mp[k][j])
                    if(mp[i][j])
                        mp[i][j] = min(mp[i][j],mp[i][k]+mp[k][j]);
                    else
                        mp[i][j] = mp[i][k]+mp[k][j];
            }
        }
    }
}
int main(){
    cin >> n >> m;
    while(n&&m){
        memset(mp,0,sizeof(mp));
        for(int i=1;i<=m;i++){
            int a,b,c;
            cin >> a >> b >> c;
            mp[a][b] = mp[b][a] = c;
        }
        floyd();
        cout << mp[1][n] << endl;
        cin >> n >> m;
    }
}

最小生成树

prim

模板

int mp[N][N] // 邻接矩阵存图
int dis[N];
bool vis[N];
int n;
void init(){
    for(int i = 1; i <= n; i++)
        dis[i] = INF, vis[i] = 0;
}
ll prim(){
    dis[1] = 0;
    vis[1] = 1;
    for(int i = 1; i <= n; i++)
        dis[i] = mp[1][i];
   	for(int k = 1; k < n; k++){ //仅需连接n-1次
		int v = 0; // 新目标点
        for(int i = 1; i <= n; i++)
            if(!vis[i] && (!v || dis[i] < dis[v])) v = i;
       	vis[v] = 1;
        for(int i = 1; i <= n; i++)
            if(!vis[i]) dis[i] = min(dis[i], mp[v][i]);
    }
    ll ans = 0;
    for(int i = 1; i <= n; i++) ans += dis[i];
   	return ans;
}

并查集

并查集还是挺实用的,是一种简洁而优雅的数据结构,储存节点的父子关系。通过Find寻找父根节点,Union将两个节点相连.

Find
int Find(int x){//朴素找法,一个一个向上找
    if(fa[x]!=x) return Find(fa[x]);
    return fa[x];
}
int Find(int x){//路径压缩
    if(fa[x]!=x) fa[x]=Find(fa[x]);
    return fa[x];
}
Union
int Union(int u,int v){
    int fu = Find(fu);
    int fv = Find(fv);
    fa[fv] = fu;
}
还是畅通工程(例题

某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。

input

测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( < 100 );随后的N(N-1)/2行对应村庄间的距离,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间的距离。为简单起见,村庄从1到N编号。
当N为0时,输入结束,该用例不被处理。

ouput

对每个测试用例,在1行里输出最小的公路总长度。

Sample Input

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

Sample Output

3
5

这道题的意思就是求取联通块~~(自取名)~~的数量,容易想到其实就是fa[x] = x的数量

#include <iostream>
using namespace std;
#include <algorithm>
#include <string.h>
#define INF 0x3f3f3f3f
int n;
int fa[105];
int Find(int x){
    if(fa[x]!=x) fa[x]=Find(fa[x]);
    return fa[x];
}
struct Node{
    int a;
    int b;
    int c;
}node[10005];
int cmp(Node x,Node y){
    return x.c < y.c;
}
int mp[105][105];
int main(){
    while(1){
        cin >> n;
        if(n==0) break;
        for(int i=1;i<=n;i++) fa[i]=i;
        int num=n*(n-1)/2;
        for(int i=1;i<=num;i++){
            cin >> node[i].a >> node[i].b >> node[i].c;

        }
        sort(node+1,node+1+num,cmp);
        int ans=0;
        for(int i=1;i<=num;i++){
            int fx=Find(node[i].a);
            int fy=Find(node[i].b);
            if(fx!=fy){
                ans+=node[i].c;
                fa[fy]=fx;
            }
        }
        cout << ans<< endl;
    }
}

割点问题

概念 :在无向连通图中,如果将其中一个点以及所有连接该点的边去掉,图就不再连通,那么这个点就叫做割点。

tarjian算法

看看大佬博客

首先选定一个根节点,从该根节点开始遍历整个图(使用DFS)。

对于根节点,判断是不是割点很简单——计算其子树数量,如果有2棵即以上的子树,就是割点。因为如果去掉这个点,这两棵子树就不能互相到达。

对于非根节点,判断是不是割点就有些麻烦了。我们维护两个数组dfn[]和low[],dfn[u]表示顶点u第几个被(首次)访问,low[u]表示顶点u及其子树中的点,通过非父子边(回边),能够回溯到的最早的点(dfn最小)的dfn值(但不能通过连接u与其父节点的边)。对于边(u, v),如果low[v]>=dfn[u],此时u就是割点。

但这里也出现一个问题:怎么计算low[u]。

假设当前顶点为u,则默认low[u]=dfn[u],即最早只能回溯到自身。

有一条边(u, v),如果v未访问过,继续DFS,DFS完之后,low[u]=min(low[u], low[v]);

如果v访问过(且u不是v的父亲),就不需要继续DFS了,一定有dfn[v]<dfn[u],low[u]=min(low[u], dfn[v])。

【模板】割点(割顶)

传送门
给出一个 nn 个点,mm 条边的无向图,求图的割点。

input

第一行输入两个正整数 n,m。
下面 mm 行每行输入两个正整数 x,y 表示 x 到 y 有一条边。

ouput

第一行输出割点个数。
第二行按照节点编号从小到大输出节点,用空格隔开。


对于全部数据,1<=n<=24, 1<=m<=25
点的编号均大于 0 小于等于 n。

tarjan图不一定联通。

#include <iostream>
using namespace std;
#include <algorithm>
#include <cstdio>
#include <queue>
#include <vector>
#include <stack>    
#include <string.h>
#include <string>
#include <math.h>
#define IOS std::ios::sync_with_stdio(false);
#define INF 0x7fffffff
typedef long long ll;
const int N = 1e5+5;
const int mod = 1000000007;
int dfn[N];
int low[N];
vector <int> E[N];
int cnt = 0;//时间戳
vector <int> cut;//割点
bool flag[N];//是否是割点
void tarjan(int x,int fa){
    dfn[x] = low[x] = ++cnt;
    int son = 0;//子树
    for(auto i:E[x]){
        if(!dfn[i]){
            tarjan(i,fa);
            low[x] = min(low[x],low[i]);
            if(low[i]>=dfn[x] && x!=fa && flag[x]==0){
                flag[x] = 1;
                cut.push_back(x);
            }
            if(x==fa) son++;
        }
        low[x] = min(low[x],dfn[i]);
    }
    if(son>=2 && x==fa && flag[x]==0){
        flag[x] = 1;
        cut.push_back(x);
    }
}
int main(){
    IOS;
    int n,m;
    cin >> n >> m;
    for(int i=1;i<=m;i++){
        int u,v;
        cin >> u >> v;
        E[u].push_back(v);
        E[v].push_back(u);
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i]){
            tarjan(i,i);
        }
    }
    sort(cut.begin(),cut.end());
    cout << cut.size() << endl;
    for(auto i:cut)
        cout << i << " ";
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值