C++算法:图论基础

背景

C++ 有一个非常著名的算法,叫做 图论 。

废话不多说,了解这么多。潦草结束

洛谷博客也有这篇文章


相关知识

基础模板涉及:

结构体

数组

for循环

进阶算法涉及:(这篇文章不写)

while循环

优先队列(队列)

vector


一、图的基本概念

图是一种数据结构,从严格意义上讲,它的定义为:

图论基础

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/qq

转载请注明出处作者~

posted @ 2020-08-29 10:26  无咕  阅读(2055)  评论(1编辑  收藏  举报
/* 点击爆炸效果*/