C++算法:图论基础
背景
废话不多说,了解这么多。潦草结束
洛谷博客也有这篇文章
相关知识
基础模板涉及:
进阶算法涉及:(这篇文章不写)
一、图的基本概念
图
图是一种数据结构,从严格意义上讲,它的定义为:
G在这里代表的是图,而V是一个非空的有限集合,代表顶点(也就是结点),E代表边的集合。
如下图,下面这个就是图
图的一些定义与概念
因为有很多,我一项一项列出来
1.有向与无向:
有向图就是图的边有方向,按箭头的方向从一点到另一点。
无向图就是图的边没有方向,双向通行。
2.度、出度、入度
度是无向图中的,而出度和入度是有向图中的
上图中每个结点的周围都写着它的度(或者是出度,入度)
无向图的度就是这个点所连接的边的数量
有向图的出度就是从这个点出发的边数,入度就是从任一点(除自己外)出发,连接自己的边数就叫入度
这么说你可能很绕脑,你看图就行了
3.边权(权值)
边权,你可以抽象的理解为走这条边的费用(也可以理解为长度、代价等),也就是下图
比如有向图,从1到2或者从2到1都要有1的代价,再比如无向图里,从3到4要有4的代价,这就是边权了。
4.连通
连通并不是两个点之间有边就算连通,只要是从A出发,不论怎么走,只要能走到B,那么就可以说A和B是连通的。
准确、科学的说只要能从U点出发,经过若干点后达到点V,那么就称UV是连通的
上图中,1和2是连通的,1和3也是连通的,2和3同理
5.完全图、稀疏图、稠密图
完全图就是在有n个点,且有n(n−1)/2条边的图,叫做完全图
而稀疏图和稠密图就是一些比较模糊的了,稠密图就是边的数量接近完全图但不是完全图的图,稀疏图是边更少,比稠密图的边还要少的图
6.环(回路)
起点与终点相同的路径,叫做环。每个图有环,就叫环图(也叫哈尔密顿图),否则叫无环图。
7.强连通分量
在一个有向图中,由n个点构成环的子图叫做强连通分量,单个点(未算在其他子图中的点)也算强连通分量。
在上图中,有2个强连通分量: 第1个是由1->3->5->4构成的子图 第2个是由2构成的子图
二、图的存储结构与建边
存图有很多种方法,这里介绍两种
1.邻接矩阵
邻接矩阵是个 二维数组 ~~
它的调用格式是这样的
g[i][j]//表示从i到j的边的权值(也可以用来存是否存在这个信息)
我这里给出几个图康康吧
这个图,用邻接矩阵存储后是:(给出邻接矩阵)
你有没有发现什么呢?
你会发现这个图他是对称的。。。
这就证明,他是个无向图。
为什么呢?不因为什么,就因为他是无向图,两边都可以通行
那么为什么要这样存图呢?
我先来说明一下啊
第 i行存储的是结点 i所连接的边的权值(或者是否连接这个点)
那么第i行的第j列不就是从i到j的那条边了吗?
你会发现,哇!原来这么简单
当然,邻接矩阵的好处之一就是方便调用边
在有边权的时候,要先初始化才可以使用
for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ g[i][j]=0x7fffffff; } }
当然了,你也可以写这个
memset(g,0x7f,sizeof(g));
但是要注意写cstring或者string.h头文件
存图该怎么存呢?
总不能一个一个去存吧,,,而且你还不知道数据
那么就要用到for了
for(int i=1;i<=m;i++){//m是边的数量 int from,to,w; cin>>from>>to>>w; g[from][to]=w; }
g[from][to]意思就是从from到to的那条边,w就是权值,从from到to的边的权值是w
当然,如果你要存的是这条边是否存在,那么你初始化要写0,然后有边就写1
当然!这只是有向图
无向图的话就证明两边都可以走对吧?那么如果写一个双向的箭头不也是一样吗?所以说我们要这样存图
for(int i=1;i<=m;i++){//m是边的数量 int from,to,w; cin>>from>>to>>w; g[from][to]=w; g[to][from]=w; }
没错,就是存两边,只不过起点和终点反过来了而已(虽然只是起点终点反过来了,但是他们的意义完全不一样)
总结:邻接矩阵的优点是方便度的计算(读入时就可以算)、方便判断两点是否有边及其权值、大小是n*n、占用的单元只与顶点有关。缺点是寻找一个点的所有相连的边需要1-n循环,而且内存过大容易爆
2.邻接表
哇,,,终于码到邻接表了,累死我了啊啊啊
邻接表非常像链表
要用到结构体了。。。
首先定义一个名为Edge(边)的结构体并定义一个Edge类型的edge数组
struct Edge{ int nxt,to,w; }edge[MAX];
nxt就是nxet的意思,就是下一条边的指针。不写next的原因是评测机可能会吞特殊字,所以这里写了nxt
to是这条边指向的结点
w就是权值了
MAX嘛,,,自己定义,但是要注意存题上给的二倍(比如1e5+9要变成2e5+9),因为可能是无向图
然后建边嘛。。。这个就比较麻烦了
首先需要定义一个变量和一个数组
int num_edge=0,head[MAX];
num_edge就是当前edge数组存到的位置
head[i]就代表结点i连接的最后一条边
这么说你又双叒叕可能会有点晕(其实我更晕),一会写建边给你讲
void add_edge(int from,int to,int w){ edge[++num_edge].nxt=head[from]; edge[num_edge].to=to; edge[num_edge].w=w; head[from]=num_edge;
}
这个就是建边的手写数组
需要3个参数,我就不讲了。。。。
首先函数的第一行,++num_edge就是先自增后存储,这样就不用额外写一个num_edge++了(我这里是从1开始存的,要从0开始存的话,你num_edge初始值可以为-1)
也就是说,建一个新边,首先边的总数+1了(就是++num_edge),他的下一条边的值就是from(也就是新边的起始点)的最后一条边的指针(这里是之前的最后一条边的指针,它加进来之后就是最后一条边就是他了)
然后第二、三步,建立新边指向的结点和权值
第四步,就是第一步写的“它加进来之后就是最后一条边就是他了”,只不过要存他的指针而已,当然就是num_edge。就是from(新边的起始点)的最后一条边更新
然后就变成了这样:
for(int i=1;i<=n;i++){ int from,to,w; cin>>from>>to>>w; add_edge(from,to,w); add_edge(to,from,w); }
这就是建边的全过程了,累死我了
你可能会觉得,还是邻接矩阵好用,邻接表根本没听懂(???),还不好用,这么麻烦
但是到了后面写Dijkstra等高级算法的时候,特别是学优化的时候,你不仅会习惯写邻接表,还会说真香。。。(我直呼内行!)
三、结束了,总结一下下
唉鸭累死我了先写这些吧
做图、码字、写代码都累死我了,用了2个多小时才写出来
所以各方面都体谅一下吧。。。
这里练习题我就不提供了,大家自学一下吧
回顾:
这次我们学了邻接矩阵、邻接表以及图的概念,你都学会了吗?
没学会再去学一遍吧qaq
这次就到这里了886
转载请注明出处作者~