浅谈图论(清华大学 黄天)

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

Bellman Ford
SPFA
改进自Bellman Ford
用队列来进行优化 多次入队,多次松弛
O(km)?
SPFA int SPFA(int s,int t)
{
memset(inq,false,sizeof(inq)); for (int i=1; i<=N; i++) dis[i]=oo; inq[s]=true; int tail=1; que[tail]=s; dis[s]=0; for (int head=1; head<=tail; head++) { for (int x=Root[que[head]]; x!=0; x=Next[x]) if (dis[que[head]]+Cost[x]<dis[Node[x]]) { dis[Node[x]]=dis[que[head]]+Cost[x]; if (!inq[Node[x]]) { que[++tail]=Node[x]; inq[Node[x]]=true; } } inq[que[head]]=false; } return dis[t];
图论中的常见算法
图的遍历 最短路径 最小生成树
强连通分量
树的一些处理
图的遍历
a)广度遍历 b)深度遍历 c)DAG的拓扑排序
DAG的拓扑排序
对一个有向无环图(DAG)G进 行拓扑排序,是将G中所有顶点排 成一个线性序列,使得图中任意一 对顶点u和v,若边(u,v)∈E(G),则 u在线性序列中出现在v之前。
空间复杂度O(M)
{
int u,v,w; scanf("%d%d%d",&u,&v,&w); Insert(u,v,w); //如果是无向图,再加一句Insert(v,u,w),记得数组翻倍!!!
}
}
(01)邻接链表
搜索边的时候,对于当前点Now,只需要这样写: for (int x=Root[Now]; x!=0; x=Next[x]) Node[x]就是Now连出去的点。 这样就能扫描完所有与Now相关的边。
1
2 3 来自大触vfleaking的著名画作 2 4 3
2
3 1 4 5 5
1
2 1 3 4 2
1 4
4
5 2
3 2
2 3
1 1
10、能记图
记图的常用方法: (00)邻接矩阵 (01)邻接链表 (10)边集 (11)对于树来说,还有记录父亲节点、左儿子右兄弟的 方法。
(00)邻接矩阵
1 2 3 1 3
浅谈图论
清华大学贵系 黄天
图论的前世今生
图论〔Graph Theory〕是数学的一个分支,是应用数学的一部分。 它以图为研究对象,研究顶点和边组成的图形的数学理论和方法 。提出者 为欧拉(Euler) 。
图论起源于一个非常经典的问题——柯尼斯堡(Konigsber)问题, 也就是我们熟知的七桥问题。
原理:用链表把边存起来,直观上我们要用指针来写,但是我们可以用数组模拟指针。
Vector?
(01)邻接链表
void Insert(int u,int v,int w) { cnt++; //注意:在多组数据的时候,cnt和Root一定要初始化,不然很容易错。 Node[cnt]=v; Cost[cnt]=w; Next[cnt]=Root[u]; Root[u]=cnt; } void Build() { for (int i=0; i<M; i++)
Bellman Ford
基本思路: 1、对于没一条边都松弛一遍。 2、重复以上操作N-1(点的个数)遍。 若在n-1次松弛后还能更新,则说明图中有负 环,因此无法得出结果,否则就完成。
int BellmanFord(int s,int t) { for (int i=1; i<=N; i++) dis[i]=oo; dis[s]=0; for (int i=1; i<N; i++) for (int j=1; j<=M; j++) if (dis[From[j]]+Cost[j]<dis[Endv[j]]) dis[Endv[j]]=dis[From[j]]+Cost[j]; }
具体的实现: 1、每次找到入度为0的点,将其加 入已排好序的队列队尾。 2、将当前入度为0的点以及它所连 出去的边都在图中删掉。 3、重复1、2步直至图为空。
DAG的拓扑排序
那是不是每次都要将所有点扫描一 遍看看哪些是入度为0的点呢?这样 一来时间复杂度可是平方级别的呀!
DAG的拓扑排序
当然不用! 用一个数组记录每个点初始状态下 的入度。 在初始的时候将入度为0的点加入宽 度搜索的队列中。 在宽搜的时候,对于当前点,将其 出边对应的所有点的入度都减一, 然后顺便判断一下入度是否变成了0, 如果是,就将该点加入队尾。
使用条件:边权为正(?)
1
4 5
时间复杂度O(N^2),优化之后可以达到O(MlogN), 写得弱会达到O(MlogM)。 3
2
-2
Djikstra
int Dijkstra(int s,int t) { for (int i=1; i<=N; i++) dis[i]=oo; dis[s]=0; memset(vis,false,sizeof(vis)); for (int i=1; i<=N; i++) { int Min=oo, k=0; for (int j=1; j<=N; j++) if ((dis[j]<Min) && (!vis[j])) { Min=dis[j]; k=j; } if (Min==oo) break; vis[k]=true; for (int x=Root[k]; x!=0; x=Next[x]) if ((Min+Cost[x]<dis[Node[x]]) && (!vis[Node[x]])) dis[Node[x]]=Min+Cost[x]; } return dis[t]; }
poj2367
给出一个数n, 然后n行,编号1到n, 每行输入几个数,该行的编号排 在这几个数前面,输出一种符合要求的编号名次排序。 5 0 4510 10 530 30 1
5
2
3
4
最短路径
最短路径
BFS
Bellman-ford SPFA Dijkstra
Floyd
还有Johnson、A*算法,但是这些不那么常用,而且时间有 限,这里不多讲。
后来此问题被远古大神欧拉解决,从此图论迅速发展起来。在图论的 历史中,还出现了许多经典问题,比如四色定理、汉密尔顿回路。 图论发展到我们现在,已经拥有一个很庞大的体系。图论成为了信息 学的一大专题,也成为不少Oier头疼的难题,当然!也是大神们虐场的必 备题(其实,大神们写个A+B也可以虐你>_<)。
2014 2015
Day1 T2、Day2 T2 Day1 T2、Day2 T3
所以说,图论经常会是压轴题的存在。一道图论题会直接导致省一和省 二的区别。 虽然大神可能会说打不打return 0才是我能否AK的关键。。。。
好的,我们现在步入正题。
学习图论的预备知识(大神们可以无视我开始刷题了): 00、认识图 01、能画图 10、能记图
(10)边集
这个其实不算是什么特别的存储方法,只是单纯的把每条 边的起点、终点、边权记录下来,有时会有特殊的作用。比如, 在最小生成树kruskal算法、最短路径Bellman-Ford算法中的应 用。
(11)树的特殊存储方法
a)记录父亲节点 这个就不用讲了吧,每个节点都记录其父亲节点,常用于 比较简单的树的遍历。
图论在竞赛中的应用
年份 2011 2012 2013 2014 2015
题数
0Байду номын сангаас
1
2
2
2
哈哈,图论出现的概率竟然与大模拟是相当的!
于是,精通找规律的我们发现今年会出2~3题!
PS:如果不是,不要打我>_<
竞赛中图论问题的难度
年份 2011 2012 2013 出现的位置 Day2 T3 Day1 T3、Day2 T3
00、认识图
什么是图?
图由点和边组成。根据边是否有方向,可分为有向图、无向图。树是 特殊的无向图。 图可以用二元组(V,E)表示,V表示点的集合,E表示边的集合。每条边 可以有一个边权。
01、能画图
由于我们这些蒟蒻没有vfleaking的秉 异天赋,我们只学习这样的画图: 比如说,给出这样的边: 起点 终点 边权
(11)树的特殊存储方法
b)左儿子右兄弟 具体实现: scanf("%d%d",&father,&son); if (left[father]!=0) { right[son]=left[father]; left[father]=son; } else left[father]=son;
不过其实就我个人来讲,我觉 得这种方法并没有特别方便的 地方,一般可以用邻接链表来 代替。
两个优化策略: a)如果dis[tail_N]<dis[head_N], 插到队首。 b)设当前dis的平均值为x,若当前 dis[head_N]大于x,将其弄到队尾, 继续看新的dis[head_N],直至找到 一个dis[head_N]<=x,才开始松弛。
Djikstra
具体实现: 每次选队列中距离最小的顶点进行松弛。 正确性?
(11)树的特殊存储方法
b)左儿子右兄弟 把一棵多叉树转化为一棵二叉树,便于处理。转化的方式 是:左子树为孩子节点,右子树为兄弟结点。也就是说:
(11)树的特殊存储方法
b)左儿子右兄弟 具体实现: 要开三个数组:节点数组v(也可不开,用下标代替),左 节点数组left,右节点数组right。每次读入一组父亲节点和孩 子节点,如果把它放在后面,那么当多个的时候就还要在前面 再扫描一次,时间复杂度就是O(n^2)。但是可以直接把当前的 这一个孩子节点插在最前面,时间复杂度会降低到O(n)。
DAG的拓扑排序
int tail=0; for (int i=1; i<=N; i++) if (ru[i]==0) { tail++; que[tail]=i; } for (int head=1; head<=tail; head++) for (int x=Root[que[head]]; x!=0; x=Next[x]) { ru[Node[x]]--; if (ru[Node[x]]==0) { tail++; que[tail]=Node[x]; } }
(00)邻接矩阵
void Build() { for (int i=0; i<M; i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); G[u][v]=w; //如果是无向图,则是G[u][v]=G[v][u]=w; } } 空间复杂度:O(N^2)
(01)邻接链表
2 1 2 2
BFS灌水法
2 2 1 2
int BFS(int s,int t) { 2 1 0 1 2 memset(vis,false,sizeof(vis)); 2 1 2 memset(dis,0,sizeof(dis)); int tail=1; 2 que[tail]=s; vis[s]=true; for (int head=1; head<=tail; head++) for (int x=Root[que[head]]; x!=0; x=Next[x]) if (!vis[Node[x]]) { tail++; que[tail]=Node[x]; dis[Node[x]]=dis[que[head]]+1; vis[Node[x]]=true; } return dis[t]; }
BFS灌水法
usaco火情蔓延 一个R*C的方格图,时刻0时某些方格自燃,每秒钟向四个方向蔓延,问 需要多少时间才使得整个方格图都在燃烧。 拯救公主 http://bailian.openjudge.cn/practice/4105/ http://blog.csdn.net/a948433271/article/details/47073971 2 使用条件:边权为1 2 1 2 2 1 0 1 2
0
2 3 4
2
5
0
1 3
2
3 2
4
5
1 2 3 4 5
4
2 3 3 1
5
1 2
其中空着的位置代表该两点之间没有边。 设G[][]为某一幅图的邻接矩阵,那么对于这样的一条边,点i->点j有一条 权值为w的边,那么就令G[i][j]=w 对于无向图来说,i-j这样的一条边就令G[i][j]=G[j][i]=w
相关文档
最新文档