图论基础及例题

总结

图(graph)并不是指图形图像(image)或地图(map)。通常来说,我们会把图视为一种由“顶点”组成的抽象网络,网络中的各顶点可以通过“边”实现彼此的连接,表示两顶点有关联。注意上面图定义中的两个关键字,由此得到我们最基础最基本的2个概念,顶点(vertex)和边(edge)。


一、最短路问题

从图中的某个顶点出发到达另外一个顶点的所经过的边的权重和最小的一条路径,称为最短路径

floyd算法

弗洛伊德算法是解决任意两点间的最短路径的一种算法,可以正确处理有向图或无向图或负权(但不可存在负权回路)的最短路径问题,同时也被用于计算有向图的传递闭包。

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

思想:枚举额外点k,更新i点和j点之间的最短距离

优点:简单!

缺点:局限,三重循环效率低,计算了大量无关数据

队列优化Dijkstra算法

迪科斯彻算法使用了广度优先搜索解决赋权有向图或者无向图的单源最短路径问题,算法最终得到一个最短路径树。该算法常用于路由算法或者作为其他图算法的一个子模块。

dis[ ]记录起点到各点距离
pre[ ]前驱数组
vis[ ]记录点是否合并

struct node{
    int id;
    ll val;
    node(int a,int b):id(a),val(b){}
    bool operator < (const node & b)const{
        return val>b.val;
    }
};

priority_queue<node>q;
vector<node>v1[N];
ll dis[N];
int vis[N],pre[N];

void init(){
    for(int i=0;i<N;i++){
        v1[i].clear();
    }
}

void dijkstra1(int start){
    for(int i=0;i<N;i++){dis[i]=1e18;vis[i]=0;}
    dis[start]=0;
    pre[start]=start;//记录最短路路径
    q.push(node(start,0));
    while(!q.empty()){
        node front=q.top();
        q.pop();
        int now=front.id;//因为重载了运算符<,存队列要用node存
        if(vis[now])continue;//该点已被合并
        vis[now]=1;//标记为已合并
        for(int i=0;i<v1[now].size();i++){//now点能去的点
            int to=v1[now][i].id;
            //cout<<now<<endl;
            if(!vis[to]&&dis[now]+v1[now][i].val<dis[to]){
                dis[to]=dis[now]+v1[now][i].val;
                pre[to]=now;
                q.push(node(to,dis[to]));
            }
        }
    }
}

适用范围:单源最短路问题,边的权重不能是负数

SPFA

struct node{
    int to,val;
};

vector<node>v1[N];
ll dis[N];
int vis[N];

void SPFA(int s){
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++) dis[i]=INF;//初始化
    dis[s]=0;//起点到起点0
    queue<int>q;
    q.push(s);//入队
    vis[s]=1;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        for(int i=0;i<v1[u].size();i++){
            int to=v1[u][i].to;
            ll val=v1[u][i].val;
            if(dis[to]>dis[u]+val){
                dis[to]=dis[u]+val;
                if(!vis[to]){
                    q.push(to);
                    vis[to]=1;
                }
            }
        }
        vis[u]=0;
    }
}

二、最小生成树

树的遍历

DFS(深度优先搜索)

从根节点出发,沿着左子树方向进行纵向遍历,直到找到叶子节点为止。然后回溯到前一个节点,进行右子树节点的遍历,直到遍历完所有可达节点为止。

父节点入栈,父节点出栈,先右子节点入栈,后左子节点入栈。递归遍历全部节点

BFS(广度优先搜索)

从根节点出发,在横向遍历二叉树层段节点的基础上纵向遍历二叉树的层次。

父节点入队,父节点出队列,先左子节点入队,后右子节点入队。递归遍历全部节点


并查集

找祖宗,同一个根值相同

void init(){/*初始化*/
	for(int i=1;i<=n;i++)fa[i]=i;
}
int find(int x){/*查找*/
	 if(fa[x]==x)return x;
	 else return fa[x] = find(fa[x]);
}
void unite(int x,int y){/*合并*/
	int t=fa[find(x)];
    int s=find(y);
    fa[find(x)] = s;
    for(int i=1;i<=n;i++)
    	if(fa[i]==t)fa[i]=s;
}

边权排序

struct node{
	int x,y,dis;
	}edge[MAXM];

	bool cmp(node a,node b){return a.dis<b.dis;}
	sort(edge+1,edge+1+m,cmp);

结构体多变量排序

bool cmp(node x,node y){
	if(x.b==y.b)  
		return x.a<y.a;
	return x.b<y.b;
}


kruskal算法

**给定一个图,有n个节点,m条边,求最小生成树
**

步骤 :
1.初始化并查集,读入数据,用结构体存边,将边按权重排序
2.用sum记录所选边的权重和,num记录所选边的条数
3.循环,当循环数到m次或所选边数num==n-1时,循环结束(或一些额外条件break)
4.在第i层循环中,选择排好序的第i条边,判断其两个端点x,y是否在同一集合中if(find(x)!=find(y)),如果不在,则将两点合并unity(x,y),边权和sum加上这条边的边权,所选变数num+1;

void kruskal(){
	for(int i=1;i<=n;i++)fa[i]=i;//初始化并查集
	sort(edge+1,edge+1+m,cmp);//结构体排序
	sum=0;num=0;//权重和,已选边数
	for(int i=1;i<=m;i++){
		if(num==n-1)break;//已选n-1条边,跳出(或其他题目给的条件)
		int x=edge[i].x;
		int y=edge[i].y;
		if(find(x)!=find(y)){//不在同一个集合
			unite(x,y);//合并
			sum+=edge[i].dis;
			num++;
		}
	}
}

例题

一、A - Cow Contest

传送门

N (1 ≤ N ≤ 100) cows, conveniently numbered 1…N, are participating in a programming contest. As we all know, some cows code better than others. Each cow has a certain constant skill rating that is unique among the competitors.
The contest is conducted in several head-to-head rounds, each between two cows. If cow A has a greater skill level than cow B (1 ≤ A ≤ N; 1 ≤ B ≤ N; A ≠ B), then cow A will always beat cow B.
Farmer John is trying to rank the cows by skill level. Given a list the results of M (1 ≤ M ≤ 4,500) two-cow rounds, determine the number of cows whose ranks can be precisely determined from the results. It is guaranteed that the results of the rounds will not be contradictory.

Input
Line 1: Two space-separated integers: N and M
Lines 2…M+1: Each line contains two space-separated integers that describe the competitors and results (the first integer, A, is the winner) of a single round of competition: A and B

Output
Line 1: A single integer representing the number of cows whose ranks can be determined

Sample Input
5 5
4 3
4 2
3 2
1 2
2 5

Sample Output
2


思路:传递思想,等级低的指向等级高的。如果在n头牛中,有一头牛被指向和指向别的牛的总数是n-1,那就可以确定这头牛的等级

AC代码如下

#include<iostream>
#include<cstring>
using namespace std;
int main(){
    int n,m,a,b,g[105][105],ans=0;
    memset(g,0,sizeof(g));
    cin>>n>>m;
    while(m--){
        cin>>a>>b;
        g[a][b]=1;
    }
    for(int k=1;k<=n;k++){//floyd
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(g[i][k]==1&&g[k][j]==1)g[i][j]=1;
            }
        }
    }for(int i=1;i<=n;i++){
        int sum=0;
        for(int j=1;j<=n;j++)
            if(g[i][j])sum++;//第i头牛指向别的牛
        for(int j=1;j<=n;j++)
            if(g[j][i])sum++;//别的牛指向第i头牛
        if(sum==n-1)ans++;
    }
    cout<<ans<<endl;
    return 0;
}

二、J-畅通工程续

传送门

某省自从实行了很多年的畅通工程计划后,终于修建了很多路。不过路多了也不好,每次要从一个城镇到另一个城镇时,都有许多种道路方案可以选择,而某些方案要比另一些方案行走的距离要短很多。这让行人很困扰。
现在,已知起点和终点,请你计算出要从起点到终点,最短需要行走多少距离。

Input
本题目包含多组数据,请处理到文件结束。
每组数据第一行包含两个正整数N和M(0<N<200,0<M<1000),分别代表现有城镇的数目和已修建的道路的数目。城镇分别以0~N-1编号。
接下来是M行道路信息。每一行有三个整数A,B,X(0<=A,B<N,A!=B,0<X<10000),表示城镇A和城镇B之间有一条长度为X的双向道路。
再接下一行有两个整数S,T(0<=S,T<N),分别代表起点和终点。

Output
对于每组数据,请在一行里输出最短需要行走的距离。如果不存在从S到T的路线,就输出-1.

Sample Input
3 3
0 1 1
0 2 3
1 2 1
0 2
3 1
0 1 1
1 2

Sample Output
2
-1


思路:用Dijkstra算法,定义起始点s,数组记录s到其他点的距离,不断遍历数组,找到到起点最近的且未被合并的点

AC代码如下

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define INF 1000000000
int main(){
    int n,m,a,b,s,t,flag,l,g[205][205],dis[205],vis[205];
    while(cin>>n>>m){
        memset(vis,0,sizeof(vis));
        for(int i=0;i<=204;i++)
            for(int j=0;j<=204;j++)
                if(i==j)g[i][j]=0;
                else g[i][j]=INF;
        for(int i=1;i<=m;i++){
            cin>>a>>b>>l;
            if(l<g[a][b])g[a][b]=g[b][a]=l;
        }
        cin>>s>>t;
        for(int i=0;i<n;i++)dis[i]=g[s][i];//s是起点
        vis[s]=1,dis[s]=0;
        while(1){
            int minn=INF;
            flag=-1;
            for(int i=0;i<n;i++)//找此时到起点最近的且未被合并的点
                if(dis[i]<minn&&vis[i]==0){minn=dis[i];flag=i;}
                if(flag<0)break;//全部点处理好跳出
            vis[flag]=1;
            for(int i=0;i<n;i++)if(vis[i]==0)dis[i]=min(dis[i],dis[flag]+g[flag][i]);
        }
        if(dis[t]!=INF)cout<<dis[t]<<endl;
        else cout<<"-1"<<endl;
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值