在复习的开始我还是想先说一下对于这一天上课我的感受,
怎么说,day1当然是我们上课的第一天,老师是一个漂亮(心虚)小姐姐,
讲今天课的时候老师前面内容基本上一带而过,许多基础内容直接默认我们都会,一上午大部分时间都在讲题,而我,由于自学图论并没学会多少,前面的还好说,到后面几到题的时候基本上就是挂机听思路了。QAQ
图的储存
图的储存其实对于刚接触图论的初学者来说会有点生涩,对于后面写多了的大佬们,不懂其实也会。
那我们先说最容易理解的矩阵存图
矩阵存图
其实就是用一个二维数组存图中点与点之间的关系
比如这个有向图我们用v[MAXN][MAXN]来存储
表示为v[1][2] = 1;v[2][4] = 1,v[3][4] = 1, v[4][5] = 1
对于无向图只需要反向再建一次就行;
v[2][1] = 1, v[4][2] = 1, v[4][3] = 1, v[5][4] = 1;
l
另一个比较常用的就是邻接表和链式前向星;
这两个其实差不多,都是用于存边的,也就是记录每个边的属性;
这里就说链式前向星吧
#define MAXN 10010
struct node {
int x,y,next;//x可以不要,有时做题时要用;
}
int lin[MAXN],len = 0;
//这里lin数组记录的是最近读入的连接x的边
inline addedge(int x,int y){
e[++len].x = x;e[len].y = ;
e[len].next = lin[x];
lin[x] = len;
}
//从x开始遍历;
for(int i = lin[x]; i ; i = e[i].next){
......
}
emm
这里先挖个坑回来再填详解;.
一些相关知识点
顶点的度:在无向图中,某个顶点的度是与它相关联的边的数目在有向图中,一个顶点的出度是以它为起始的边的数它为终止的边的数目。
简单路径:顶点不重复的路径。
自环:从某个顶点出发连向它自身的边。
环:从某个顶点出发再回到自身的路径,又称回路。
重边:从一个顶点到另一个顶点有两条边直接
连通性:
在无向图中,若从顶点u 到v 存在路径,那么称顶点u 和v 是连通
的。
如果无向图中任意一对顶点都是连通的,那么称此图为连通图。
如果一个无向图不是连通的,则称它的一个极大连通子图为连通分量。
这里的极大是指顶点个数极大。
在有向图中,如果每一对顶点u 和v,既存在从u 到v 的路径,又存
在从v 到u 的路径,那么称此图为强连通图。
对于非强连通图,其极大强连通子图成为其强连通分量。
最短路算法
Floyd 算法
Floyd算法其实就是暴力更新点与点之间的最短距离
对于较小的数据Floyd其实是非常好的算法,又短又明了,也方便更新时进行其他操作
但显然这是O(|n|3)的复杂度,大部分时候它并不适用
Floyd第一层循环是枚举每个点能否作为中间点使i到j的距离更小;
里面两层很显然就是枚举i,j是否能用k更新;
void floyd(){
for(int k = 1 ; k <= n; ++k)
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
a[i][j] = min(a[i][j],a[i][k] + a[k][j]);//更新a[i][j]的值
}
Dijkstra算法
如果有向图的边权值全为正数,那么有一种复杂度有保证的单源最
段路算法——Dijkstra 算法。它的时间复杂度是O(|n|2)。
简而言之Dijkstra不能跑负权图求最短路,并且复杂度稳定的O(|n|2)。
对于单源最短路的话就是一个点到其他点的最短距离;
对于Dijkstra算法我们需要一个dist数组来记录所求点到其他每个点的最短距离,
和一个vis数组标记节点;
首先
我们先初始化数组dist[1] = 0;其余赋为最大值memset(dist,0x3f3f3f,sizeof(dist));
然后找出没有被vis数组标记的dist[x]最小的节点x,然后标记节点x,vis[x] = 1。
then遍历节点x的所有出边(无向图就是所有边)(x,y,z)//x为当前点y为x连接的另一个点,z为改边的权值
如果dist[y] > dis[x] + z//也就是找到一条所求点到y更短的路,即先到x再到y这条路径;
就用dist[x] + z 的值去更新dist[y]的值
最后 我们再重复上面步骤(不用再初始化),知道vis数组标记所有点;
代码
#define MAXN 10010
#define INF 0x3f3f3f
int dis[MAXN],vis[MAXN];
int a[500][500]//存图
void dijkstra(){
memset(dis,INF,sizeof(dis));
memset(vis,false,sizeof(vis));
dis[1] = 0;
for(int i = 1; i < n; ++i){
int x = 0;
for(int j = 1; j <= n; ++j){
if(!v[j] && (x == 0 || dis[j] < dis[x]))
x = j;
vis[x] = true;
for(int j = 1; j <= n; ++j){
dis[j] = min(dis[j], dis[x] + a[x][y])
}
}
}
}
Dijkstra 算法的堆优化
上面dijkstra的复杂度是O(|n|2),主要在于第一步找最小值的过程,可以用二叉堆优化dis数组,O(logn)的时间获取最小值并从堆中删除O(logn)的时间对一条边扩展并更新,这里我们用链式前向星存图,最终时间复杂度为O(mlogn);
代码
struct node{
int y,z,ne;
}e[MAXN];
int lin[MAXN],len = 0;
inline addedge(int x,int y, int z){{
e[++len].y = y;e[len].z = z;
e[len].ne = lin[x]; lin[x] = len;
}
int dis[MAXN],vis[MAXN];
priority_queue< pair < int , int > > q;
void dijkstra(){
memset(dis,INF,sizeof(dis));
memset(vis,false,sizeof(vis));
while(!q.empty()){
int x = q.top().second;q.pop();
if(vis[x]) continue;
vis[x] = 1;
for(int i = lin[x]; i ; i = e[i].ne){
int y = e[i].y, z = e[i].z;
if(dis[y] > dis[x] + z){//更新dis[y]的值
dis[y] = dis[x] + z;
q.push(make_pair(-dis[y],y));//放入二叉堆
}
}
}
}
Bellman-Ford
给定了一个边带有权值(可以为负数)的有向图(不包含负环)和一个指定的顶点s。要求求出从s 到其余各点的最短路径长度。Bellman-Ford 算法是一个比较直观的求解单源最短路问题的算法。我们可以肯定最短路径包含的边的条数不会超过n − 1 个,如果超过这个数,那么肯定形成了一个环,又因为这个环权值是正的,我们可以将路径上这个环删除,路径长度就会变短
Bellman-Ford 算法描述:
创建源顶点 v 到图中所有顶点的距离的集合 distSet,为图中的所有顶点指定一个距离值,初始均为极大值,源顶点距离为 0;
计算最短路径,执行 V - 1 次遍历;
对于图中的每条边:如果起点 u 的距离 d 加上边的权值 w 小于终点 v 的距离 d,则更新终点 v 的距离值 d;
检测图中是否有负权边形成了环,遍历图中的所有边,计算 u 至 v 的距离,如果对于 v 存在更小的距离,则说明存在环;
值得一提的是Floyd算法也可以通过这样检测负环 两边Floyd,如果第二遍仍被更新就说明有负环。
代码
bool bellman_ford(int s){
memset(dis,INF,sizeof(dis));
dis[s] = 0;
bool flag;
for(int i = 1;i <= n; ++i){
flag = false;
for(int j = 1; j <= len; ++j){
int x = e[i].x, y = e[i].y, z = e[i].z;
if(dis[y] > dis[x] + z){
dis[y] = dis[x] + z;
flag = true;
}
}
if(!flag) return true;
}
//判负环
for(int i = 1; i <= len; ++i){ //第二遍遍历,如果还能更新则有负环。
int x = e[i].x, y = e[i].y, z = e[i].z;
if(dis[y] > dis[x] + z)
return false;
return true;
}
}
这个代码是判负环用的,如果想单纯的找最短路的话吧判负环的内容去掉就行了。
SPFA
假设我们现在已经得到了Bellman-Ford 算法某个阶段的dist 数组,然后我们发现了一条s 到u 的距离比dist[u] 更加短的路径。我们更新了dist[u]。
那么接下来直接受到影响的就是与u 直接关联的顶点v,也就是如果dist[u] + w[u][v] < dist[v] 的话,s 到v 的最短路就可以利用s 到u 的最短路加上u 到v 的边来更新。这样的话与v 直接关联的顶点又会受到影响. . . . . . 不断这样持续下去直到最后没有顶点能被影响。
那么一个优化就是我们利用队列存储这些需要更新的结点,每次从队列中取出一个结点,计算是否有结点需要更新,如果有,并且这个结点不在队列中,那么就将它加入队列。
这样的算法被称为SPFA——一种优化的Bellman-Ford 算法
代码
void spfa(int s){
memset(dis,INF,sizeof(dis));
memset(vis,false,sizeof(vis));
queue< int > q;
q.push(s); vis[s] = true; dis[s] = 0;
while(!q.empty()){
int x = q.front(); q.pop();
vis[x] = false;
for(int i = lin[s]; i ; i = e[i].ne){
int y = e[i].y, z = e[i].z;
if(dis[y] > dis[s] + z){
dis[y] = dis[s] + z;
if(!vis[y]){
vis[y] = true;
q.push(y);
}
}
}
}
}
部分例题
回来再说
。。。゛(ノ><)ノ逃