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 函数

令人茫然的集训图论

posted @ 2020-01-18 20:36  orange_lyc  阅读(386)  评论(0编辑  收藏  举报