• 复习3图的全家桶


    图论还是来个全家桶吧,其实图论这种东西还是蛮好理解的

    -1.什么是图

    图(Graph)是表示物件与物件之间的关系的数学对象,是图论的基本研究对象。一个不带权图中若两点不相邻,邻接矩阵相应位置为0,对带权图(网),相应位置为∞。

    有向图与无向图

    如果给图的每条边规定一个方向,那么得到的图称为有向图。在有向图中,与一个节点相关联的边有出边和入边之分。相反,边没有方向的图称为无向图。

    图的术语

    阶(Order):

    图G中顶集V的大小称作图G的阶。

    子图(Sub-Graph):

    当图G'=(V',E')其中V‘包含于V,E’包含于E,则G'称作图G=(V,E)的子图。每个图都是本身的子图。

    生成子图(Spanning Sub-Graph):

    指满足条件V(G') = V(G)的G的子图G'。

    导出子图(Induced Subgraph):

    以图G的顶点集V的非空子集V1为顶点集,以两端点均在V1中的全体边为边集的G的子图,称为V1导出的导出子图;以图G的边集E的非空子集E1为边集,以E1中边关联的顶点的全体为顶点集的G的子图,称为E1导出的导出子图。

    度(Degree):

    一个顶点的度是指与该顶点相关联的边的条数,顶点v的度记作d(v)。

    入度(In-degree)和出度(Out-degree):

    对于有向图来说,一个顶点的度可细分为入度和出度。一个顶点的入度是指与其关联的各边之中,以其为终点的边数;出度则是相对的概念,指以该顶点为起点的边数。

    自环(Loop):

    若一条边的两个顶点为同一顶点,则此边称作自环。

    路径(Path):

    从u到v的一条路径是指一个序列v0,e1,v1,e2,v2,...ek,vk,其中ei的顶点为vi及vi - 1,k称作路径的长度。如果它的起止顶点相同,该路径是“闭”的,反之,则称为“开”的。一条路径称为一简单路径(simple path),如果路径中除起始与终止顶点可以重合外,所有顶点两两不等。

    行迹(Trace):

    如果路径P(u,v)中的边各不相同,则该路径称为u到v的一条行迹。

    轨道(Track):

    如果路径P(u,v)中的顶点各不相同,则该路径称为u到v的一条轨道。

    0. 图的存储

    邻接矩阵

    实际上就是一个二维数组,我们假设它是G,那么(G[i][j]=k)就表示((i,j))之间有一条长为k的边,这里我们插入和查询的都是(O(1))的,但是我们的空间复杂度达到(O(n^2))的,所以实际上并不是特别实用

    领接表

    这里我们就上到我们比较高大上的领接表了,我们用它遍历就比较简单了
    邻接表,存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构。如这个表头结点所对应的顶点存在相邻顶点,则把相邻顶点依次存放于表头结点所指向的单向链表中。
    对于下面这幅图
    这里写图片描述
    我们所建出来的邻接表就是这样的
    这里写图片描述
    刚刚开始这个我们还是不能理解这个神奇的东西,那么我们就可以先看下代码再来理解。对于建图中最重要的就是(addedge)函数了
    我们就来看下(addedge)函数的具体的代码

    void addedge(int x,int y){
    	nxt[++tot]=head[x];
    	head[x]=tot;
    	to[tot]=y;
    }
    

    代码很短这里我建了一条有向但是没有边权的边
    ps:其实无向图就正着建一次,反着在建一次

    head[x]:从点x出发的最后一条边的标号
    nxt[i]:与标号i从同一点出发的下一条边
    to[i]:标号i的边到的点

    我们遍历的代码就很简单了

    for(int i=haed[x];i;i=nxt[i]){
    	int v=to[i];
    	//do something
    }
    

    当然我们这里可能会遇到一些边是还有一个属性的,那就是边权,那么我们代码就是长这样的

    void addedge(int x,int y,int z){
    	nxt[++tot]=head[x];
    	head[x]=tot;
    	to[tot]=y;
    	cost[tot]=z;
    }
    

    和上面同一理论cost[i]:标号i的边的权值

    1.搜索

    显然图论中许多算法都是基于搜索的啊,但是搜索的顺序可能有一点不同

    这是所有算法中最最基础的吧,这里也就不多说了,也就是按深度来搜索,代码大多数时候就是递归实现的啊

    void dfs(int x){
    	if(some resons)return;
    	//do someting
    	for(int i=head[x];i;i=nxt[i]){
    		int v=to[i];
    		if(!vis[v]){
    			vis[v]=1;
    			dfs(v);
    		}
    		//do someting
    	}
    	//do someting
    }
    

    这种算法其实就是一种以广度或者说宽度来决定优先顺序的算法,它往往是以队列为基础的一种算法
    代码也就很简单,主要就是以队列为核心

    void bfs(int s){
    	queue<int> q;
    	q.push(s);
    	vis[s]=1;
    	//do something
    	while(!q.empty()){
    		int x=q.front();
    		q.pop();
    		for(int i=head[x];i;i=nxt[i]){
    			int v=to[i];
    			//do something
    			if(!vis[v]){
    				//do something
    				vis[v]=1;
    				q.push(v);
    			}
    		}
    	}
    }
    

    2.最短路算法

    松弛

    这里我们将松弛操作单独来讲,它算是所有最短路算法的核心
    我们可以看个很有趣的东西
    令我们找到当前(i,j)之间的最短路为(dis(i,j))
    现在我们找到一个点(k)
    如果满足

    [dis(i,k)+dis(k,j)<dis(i,j) ]

    我们就更新(dis(i,j)=dis(i,k)+dis(k,j))

    Floyd

    Floyd就是一种基于松弛的dp啊,时间复杂度(O(n^3))
    这里我就不写代码了

    Bellman-Ford (SPFA)

    我是真的不会Bellman-Ford啊只能讲讲它的队列优化SPFA,其实我是在(NOIP2017)场上突然明白了SPFA的,一定是我太弱了。直白的说,它就是BFS+松弛,代码也十分的好写
    这是luogu3371 的代码,这就是一道模板题

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    
    using namespace std;
    
    int n,m,s,tot,nxt[500001],cost[500001],to[500001],head[100001],d[100001];
    bool inque[100001];
    
    void addedge(int x,int y,int z){
        nxt[++tot]=head[x];
        head[x]=tot;
        to[tot]=y;
        cost[tot]=z;
    }
    
    void spfa(int x){
        memset(d,0x7f,sizeof(d));
        queue<int> q;
        q.push(x);
        inque[x]=1;
        d[x]=0;
        while(!q.empty()){
            int now=q.front();q.pop();
            inque[now]=0;
            for(int i=head[now];i;i=nxt[i]){
                int u=to[i];
                if(d[u]>d[now]+cost[i]){
                    d[u]=d[now]+cost[i];
                    if(!inque[u]){
                        q.push(u);
                        inque[u]=1;
                    }
                }
            }
        }
    }
    
    int main(){
        scanf("%d%d%d",&n,&m,&s);
        for(int i=1;i<=m;i++){
            int x,y,z;scanf("%d%d%d",&x,&y,&z);
            addedge(x,y,z);
        }
        spfa(s);
        for(int i=1;i<=n;i++){
            printf("%d ",d[i]==0x7f7f7f7f?0x7fffffff:d[i]);
        }
        return 0;
    }
    

    Dijkstra

    这个算法我也不好说,直白点就是堆广搜
    有时候SPFA会被卡,我们就要有Dijkstra,但是不能有负边

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    
    using namespace std;
    
    int n,m,s,tot,nxt[200001],head[100001],to[200001],d[100001],cost[200001];
    
    struct node{
        int x;int dis;
        inline bool operator < (const node &b) const {
            return dis>b.dis;
        }
    };
    
    inline void addedge(int x,int y,int z){
        nxt[++tot]=head[x];
        head[x]=tot;
        to[tot]=y;
        cost[tot]=z;
    }
    
    void dijkstra(int xx){
        memset(d,0x7f,sizeof(d));
        d[xx]=0;
        priority_queue<node> q;
        q.push(node{xx,0});
        while(!q.empty()){
            int x=q.top().x;
            int dis=q.top().dis;
            q.pop();
            if(dis>d[x])continue;
            for(int i=head[x];i;i=nxt[i]){
                int v=to[i];
                if(d[v]>d[x]+cost[i]){
                    d[v]=d[x]+cost[i];
                    q.push(node{v,d[v]});
                }
            }
        }
    }
    
    int main(){
        scanf("%d%d%d",&n,&m,&s);
        for(int i=1;i<=m;i++){
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            addedge(x,y,z);
        }
        dijkstra(s);
        for(int i=1;i<=n;i++){
            printf("%d ",d[i]);
        }
        return 0;
    }
    

    3.Tarjan算法

    Tarjan算法有很多种这里我就讲下最常见的

    割点

    了解定义,算法浅显

    // luogu-judger-enable-o2
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    
    using namespace std;
    
    int n,m,ind,ans,tot,dfn[100001],low[100001],head[100001],nxt[200001],to[200001];
    bool iscp[100001];
    
    void addedge(int x,int y){
        nxt[++tot]=head[x];
        head[x]=tot;
        to[tot]=y;
    }
    
    void find_cp(int u,int fa) {
        int child=0;
        dfn[u]=low[u]=++ind;
        for(int i=head[u];i;i=nxt[i]) {
            int v=to[i];
            if(!dfn[v]) {
                child++;
                find_cp(v,u);
                low[u]=min(low[u],low[v]);
                if(low[v]>=dfn[u])iscp[u]=1;
            } else {
                if(dfn[v]<dfn[u] and v!=fa) {
                    low[u]=min(low[u],dfn[v]);
                }
            }
        }
        if(fa<0 and child==1){
            iscp[u]=0;
       }
    }
    
    int main() {
        scanf("%d%d",&n,&m);
        for(int i=1; i<=m; i++) {
        	int x,y;
            scanf("%d%d",&x,&y);
            addedge(x,y);
            addedge(y,x);
        }
        ind=0;
        for(int i=1; i<=n; i++) {
            if(!dfn[i]) {
                find_cp(i,-1);
            }
        }
        for(int i=1; i<=n; i++) {
            if(iscp[i]) {
                ans++;
            }
        }
        printf("%d
    ",ans);
        for(int i=1; i<=n; i++) {
            if(iscp[i]) {
                printf("%d ",i);
            }
        }
        return 0;
    }
    

    强联通分量&缩点

  • 相关阅读:
    RPC笔记之初探RPC:DIY简单RPC框架
    zookeeper笔记之基于zk实现分布式锁
    scala笔记之惰性赋值(lazy)
    Hive笔记之宏(macro)
    Zookeeper笔记之使用zk实现集群选主
    Zookeeper笔记之基于zk的分布式配置中心
    Zookeeper笔记之四字命令
    Zookeeper笔记之命令行操作
    复盘2018,展望2019
    爬虫笔记之w3cschool注册页面滑块验证码破解(巨简单滑块位置识别,非鼠标模拟轨迹)
  • 原文地址:https://www.cnblogs.com/ezoihy/p/9431047.html
Copyright © 2020-2023  润新知