zoukankan      html  css  js  c++  java
  • Day4--图论(我补图了。。。)

    图论

    • 有向图
    • 无向图
    • 自环
    • 重边
    • 简单图
    • 度 degree(有多少条边连接了这个节点)
    • 入度
    • 出度
    • 链式前向星:为图的每一个顶点建立一个存储它的邻接顶点的链表
    • 路径 path:一个边的序列,且相邻两条边首尾相连
    • 简单路径:同一条边只经过一次的路径(简单路径上可能有相同的节点)
    • 环 cycle:一个起点和终点相同的路径
    • 简单环:是简单路径,还是环
    • 连通 connected:如果无向图中两个节点是连通的,则存在从a->b的路径
    • 可达:有向图中
    • 一个无向图是连通的,当无向图中任何两个节点都连通。
    • 有向图没有连通的概念。
    • 弱连通:把有向图所有边改成无向的,则该有向图弱连通。
    • 强连通:有向图中任意两个节点可达
    • 子图 subgraph:在一张图中选择一个边的子集和节点的子集,形成的一张新图,一个图本身是自己的子图。
    • 生成子图:节点和原图相同,只删边的子图
    • 导出子图:选出节点的子集,并将与该图中两端都有节点的边加入
    • 边导出子图:选出边集,再选出相连的节点集合
    • 连通子图:对于无向图来说的 连通的子图
    • 连通分量 connected component:对于无向图来说,是一个无向图中的极大的连通子图。即再加入一个点,该图 不连通。
    • 稀疏图:(m=θ(n));
    • 稠密图:(m=θ(n^2));
    • 完全图:任何两个节点之间都有边(对于无向图),m=n*(n-1)/2的简单图;
    • 路径的长度:路径上边的长度和
    • 最短路径:当不存在负环时存在。
    • 树:有n个节点,n-1条边的连通无向图。
    • 树:一个无向,无环的连通图。
    • 树:任意两个节点之间有且只有一条简单路径的简单无向图。
    • 森林:一个可能不连通的无向图
    • 生成树:对于无向连通图G的一个子图,如果它是包含G中所有顶点的树,那么这个子图称为 G 的生成树。是无向连通图的包含所有顶点的极小联通子图。
    • 有根树:在一棵树上选定一个节点作为根,则为有根树;
    • 无根树:没根的树。
    • 点的深度:当前节点到根节点的距离。根节点深度=0
    • 树的深度:根节点儿子的深度的最大值
    • 叶子节点:度为0或1的点
    • 父亲:在有根树上,某结点到根的路径上的第二个节点即为父亲节点
    • 祖先:到根路径上除了本身以外的所有节点
    • 儿子/子节点/孩子/child/children:如果u是v的父亲,则v是u的儿子。
    • 兄弟:同一个父亲的多个子节点之间为兄弟关系。
    • 后代/子孙:儿子和儿子的后代们。
    • 子树:删去该节点和该节点父亲之间的点之后该节点所在的连通分量。
    • 一种树:一条链。
    • 第二种树:菊花。
    • 二叉树binary tree:每个节点最多有两个子节点的有根树。
    • 左儿子:左边的儿子。
    • 右儿子:右边的儿子。
    • 真二叉树proper:每个点要么有2个儿子,要么有0个儿子。
    • 满二叉树:对于每一层都有2^x个节点。
    • 完全二叉树:除了最后一层以外都是满的,最后一排左对齐
    • 树的存储方法:vector < int> childs[n];
    • 最小生成树:各边权重总和最小的生成树称为最小权重生成树,简称最小生成树

    Kruskal算法(贪心)

    • 得到一张图之后,首先按照权重从小到大给边排一个序。
    • 把图中所有边删掉,再按顺序一条一条地试着向图中加边。
    • 加边的同时维护点之间的连通性。如果一条边的两个端点已经连通,则这条边不包含于最小生成树中。
    • 重复上述操作,加边!加边!直到向图中加迚去(|n|-1)条边为止!
    • 维护连通性?只需要并查集就可以啦!
    • 有两条边的边权一样?随便加哪一条都可以,毕竟最小生成树不一定是唯一的。
    • 时间复杂度 O(nlogn)简洁实用的算法,适用于稀疏图。
    • 分为n部分分别找,再将这n部分之间找到最短路合并。
    图例 描述
    k1 首先第一步,我们有一张图Graph,有若干点和边
    k2 将所有的边的长度排序,用排序的结果作为我们选择边的依据。这里再次体现了贪心算法的思想。资源排序,对局部最优的资源进行选择,排序完成后,我们率先选择了边AD。
    k4 在剩下的变中寻找。我们找到了CE。这里边的权重也是5
    k5 依次类推我们找到了6,7,7,即DF,AB,BE。
    k6 下面继续选择, BC或者EF尽管现在长度为8的边是最小的未选择的边。但是现在他们已经连通了(对于BC可以通过CE,EB来连接,类似的EF可以通过EB,BA,AD,DF来接连)。所以不需要选择他们。类似的BD也已经连通了(这里上图的连通线用红色表示了)。最后就剩下EG和FG了。当然我们选择了EG。最后成功。

    看看洛谷上的最小生成树模板,用Kruskal写就是:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cctype>
    using namespace std;
    int cnt, head[2000005];
    int n, m;
    struct node
    {
    	int to, next, val;
    }edge[4000005];
    void add(int x, int y, int val_)
    {
    	edge[++cnt].next=x;
    	edge[cnt].to=y;
    	edge[cnt].val=val_;
    }
    int find(int x)
    {
    	return head[x]==x?head[x]:head[x]=find(head[x]);
    }
    int ans, num;
    void k()
    {
    	for(int i=1; i<=cnt; i++)
    	{
    		int x=find(edge[i].next);
    		int y=find(edge[i].to);
    		if(x==y) continue;
    		head[x]=y;
    		ans+=edge[i].val;
    		if(++num==n-1) break;
    	}
    }
    inline int read()
    {
        int x=0, ch=getchar(), f=1;
        while(!isdigit(ch))
    	{
    		if(ch == '-') f=-1;
    		ch=getchar();
    	}
        while(isdigit(ch)) x=x*10+ch-'0', ch=getchar();
        return x*f;
    }
    bool cmp(node x, node y)
    {
    	return x.val < y.val;
    }
    int main()
    {
    	n=read();
    	m=read();
    	for(int i=1; i<=n; i++) head[i]=i;
    	while(m--)
    	{
    		int x, y, z;
    		x=read();
    		y=read();
    		z=read();
    		add(x, y, z);
    	}
    	sort(edge+1, edge+1+cnt, cmp);
    	k();
    	printf("%d", ans);
    	return 0;
    }
    
    • 当然,这里(洛谷)不用读入优化也可以
    • 链式前向星+Kruskal

    Prim算法(贪心+1)

    • 将图的顶点分为两部分:处理完了的顶点和还未处理的顶点。
    • 任选一个起点,将它加入到处理完了的集合里面。
    • 找到连接两个集合的最短边,将它加入到最短路中,并把它的未处理过的顶点标记为端点。
    • 实际上,可以给每个顶点记录一个权值,表示和它关联的从处理过的顶点射出的最短边权(初始值设为无穷)。
    • 之后每一次只需要遍历一遍未处理的顶点,把其中权值最小点的标记为已处理,接着更新和它邻接的顶点就好啦。
    • 重复,直到所有顶点都处理完。
    • 时间复杂度(O(n^2))适用于稠密图。
    • 和Kruskal相比的话,它并没有用到并查集。也就是选择一个开始找,而不是分为n部分分别找,再选择最短路合并。
    图例 说明 不可选 可选 已选(Vnew)
    1 此为原始的加权连通图。每条边一侧的数字代表其权值。 - - -
    2 顶点D被任意选为起始点。顶点A、B、E和F通过单条边与D相连。A是距离D最近的顶点,因此将A及对应边AD以高亮表示。 C, G A, B, E, F D
    3 下一个顶点为距离D或A最近的顶点。B距D为9,距A为7,E为15,F为6。因此,F距D或A最近,因此将顶点F与相应边DF以高亮表示。 C, G B, E, F A, D
    4 算法继续重复上面的步骤。距离A为7的顶点B被高亮表示。 C B, E, G A, D, F
    5 在当前情况下,可以在C、E与G间进行选择。C距B为8,E距B为7,G距F为11。E最近,因此将顶点E与相应边BE高亮表示。 C, E, G A, D, F, B
    6 这里,可供选择的顶点只有C和G。C距E为5,G距E为9,故选取C,并与边EC一同高亮表示。 C, G A, D, F, B, E
    7 顶点G是唯一剩下的顶点,它距F为11,距E为9,E最近,故高亮表示G及相应边EG。 G A, D, F, B, E, C
    8 现在,所有顶点均已被选取,图中绿色部分即为连通图的最小生成树。在此例中,最小生成树的权值之和为39。 A, D, F, B, E, C, G

    Floyd(???我不会,略)

    Dijkstra(目光长远的贪心,但不常用)

    • 这个算法可以解决单源最短路径问题。类似于 Prim 算法,把顶点分为两类。
    • 首先,当然是源点距离源点的距离啦!
    • 然后枚举出边,更新它的邻接顶点的最短路径估计值。
    • 之后从未确定最短路径的顶点中选出最短路径估计值最小的,将它标记为已确定,并继续更新它的周围顶点。
    • 重复上述操作,直到源点到所有顶点的 最短路径都已确定。
    • 时间复杂度(O(n^2))

    堆优化的Dijkstra(这个才常用)

    • 回顾 Dijkstra 算法,我们常常需要求最短路径估计值的最小值及对应结点很简单,拿一个堆来维护一下,时间复杂度瞬间降为O(nlogn)。类似地,Prim算法也可以使用堆优化。
    • 用什么堆呢?平时用STL里的优先队列就好。如果想要卡常数,可以尝试一下配对堆/二项堆/斐波那契堆——在 pb_ds 库中都写好了哦!

    单源最短路径【标准版】

    #include <bits/stdc++.h>
    #define re register
    using namespace std;
    inline int read() 
    {
        int X=0,w=1; char c=getchar();
        while (c<'0'||c>'9') { if (c=='-') w=-1; c=getchar(); }
        while (c>='0'&&c<='9') X=(X<<3)+(X<<1)+c-'0',c=getchar();
        return X*w;
    }
    struct Edge { int v,w,nxt; };
    Edge e[500010];
    int head[100010],cnt=0;
    inline void addEdge(int u,int v,int w) 
    {
        e[++cnt].v=v;
        e[cnt].w=w;
        e[cnt].nxt=head[u];
        head[u]=cnt;
    }
    
    int n,m,s;
    int dis[100010];
    struct node 
    { //堆节点
        int u,d;
        bool operator <(const node& rhs) const 
        {
            return d>rhs.d;
        }
    };
    
    inline void Dijkstra() 
    {
        for (re int i=1;i<=n;i++) dis[i]=2147483647;
        dis[s]=0;
        priority_queue<node> Q; //堆
        Q.push((node){s,0});
        while (!Q.empty()) 
        {
            node fr=Q.top(); Q.pop();
            int u=fr.u,d=fr.d;
            if (d!=dis[u]) continue;
            for (re int i=head[u];i;i=e[i].nxt) 
            {
                int v=e[i].v,w=e[i].w;
                if (dis[u]+w<dis[v]) 
                {
                    dis[v]=dis[u]+w;
                    Q.push((node){v,dis[v]});
                }
            }
        }
    }
    
    int main() 
    {
        n=read(),m=read(),s=read();
        for (re int i=1;i<=m;i++) 
        {
            int X=read(),Y=read(),Z=read();
            addEdge(X,Y,Z);
        }
        Dijkstra();
        for (re int i=1;i<=n;i++) printf("%d ",dis[i]);
        return 0;
    }
    
    • 这不是我写的,不要问我他写了些什么。
    • 我才52分。。。
    • 但是算法的话是一样的,Dijkstra+堆优化。
    • 与Prim相比,Dijkstra更高级,毕竟它是在每一个节点都扫一遍,然后将这些路径都取最优,合并出最短路;而Prim则是一条路走到黑。

    单源最短路径【弱化版】

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    int head[20000];
    int cnt=0;
    int n, m, s;
    struct node
    {
        int to, val, next;
    }edge[1000000];
    void add(int x, int y, int val_)
    {
    	edge[++cnt].next=head[x];
        edge[cnt].to=y;
        edge[cnt].val=val_;
        head[x]=cnt;
    }
    bool v[20000];
    long long d[20000];
    int x, y, z;
    int main()
    {
        scanf("%d%d%d", &n, &m, &s);
        for(int i=1; i<=n; i++) d[i]=2147483647;
        for(int i=0; i<m; i++)
        {
            scanf("%d%d%d", &x, &y, &z);
            add(x, y, z);
        }
        int c=s;
        d[s]=0;
        long long minn;
        while(!v[c])
        {
            v[c]=true;
            for(int i=head[c]; i!=0; i=edge[i].next)
            {
                if(!v[edge[i].to] && d[edge[i].to]>d[c]+edge[i].val)
                d[edge[i].to]=d[c]+edge[i].val;
            }
            minn=2147483647;
            for(int i=1; i<=n; i++)
            {
                if(!v[i] && minn>d[i])
                {
                    minn=d[i];
                    c=i;
                }
            }
        }
        for(int i=1; i<=n; i++) printf("%lld ", d[i]);
        return 0;
    }
    
    • 这个都不用算法,一个链式前向星就OK。

    Pair

    make_pair 函数

    令人茫然的集训图论

  • 相关阅读:
    K8s环境搭建
    opencv一些重要的函数或者类
    opencv的点的表示
    opencv矩阵的格式输出
    opencv矩阵运算(二)
    opencv矩阵运算(一)
    如何安装指定版本的Kubernetes
    使用minikube快速部署k8s集群
    Ceph 存储集群
    学习tcpIp必备的抓包工具wireshark
  • 原文地址:https://www.cnblogs.com/orange-233/p/12210119.html
Copyright © 2011-2022 走看看