林瀚老师的算法课(小学期讲图论)讲得很棒 :)
WEEK 1
1、 G(V,E) 表示方法
临接矩阵
在Warshall算法里喜欢用这个
临界表
vector (可变长,我最熟悉这种,方便), 链表(学数据结构我喜欢把G和T(tree)的连接点放在这儿,
边集合
刚刚想到以前做过这种题,用边集合吧是DP好像。。记一下。遇见了再说。
离散课蔡国扬老师说有8种表示方法
2、建图
有些问题是不能直接建图的,要边解问题边建立图。这两个问题是以解的状态作为节点,边的建立是两个状态之间关系决定的。例子八数码 , 农夫过河
八数码
3*3格子里一个空的,手机上的拼图游戏。一个状态就是从(1,1)...(3,3)的排列比如123456780(0代表空格子).总共有9!种状态,有些状态可以转换,比如012345678和102345678,把第一个的1向左移动. 如果AB可转换,则AB间有一条边。问题转换成一个很大的图,找一条从初始点到终点的路。之前用A*算法解这问题。
农夫过河
农夫有菜羊狼,狼吃羊吃菜. 求过河方案. 如上每只羊、狼、菜、农夫有在河的0岸和1岸两种状态。如0000是初始状态,1111是结束状态。总共[0000,1111]种状态,每种状态为安全/不安全,如0011狼吃羊。过河的方案就是招路。
总的来说就是不一样的编码方式,之前把一个点、地方等元素看作是点,边是之间的关系,现在把解看作是点,解之间的关系看作是边。
3、BFS
-无权树最短路
隔壁女生说像Dijstra,Dijstra就是BFS思路写的。或者说在Dikstra中,如果全部路径为1则就是BFS
- 复杂度分析(Why is the complexity of BFS O(V+E) instead of O(V*E)?)
曾经觉得算复杂度就是看循环,用队列模拟BFS
while(!q.empty()){ // 每个点进一次队
u = q.top();
q.pop()
for each v satisfy (u,v) // 每条边检查一次
q.push();
}
while()最多V次,for最多E,于是O(V*E), 真相是...
(v1+v1 的临接边)+(v2+v2的临接边)+...+(vn+vn 的临接边)
= (v1+v2+...+vn) + (v1临接边+v2临接边+..+vn临接边) // 注意每条边检查一次
= V+E
误会产生是因为第两个循环的变量的增加依赖于第一个循环的变量。
4、DFS
看到DFS树的时候觉得啊哈。挺熟练的。。一段时间后。。啧啧。。那些(1,18)数对(pre,post)是什么鬼,之前没见过。联想树遍历pre()、post(),找规律,应该是第一次和最后一次访问结点的,在递归函数开头结尾可以求,怎么求,放个计数器cnt就可以了嘛,哈!那这个有什么用呢?恩,可以判断圈,好像2-连通的是什么也有用到,怎么用的嘞。。(林翰)DFS树有四种边。分类耶,我喜欢,mark下听课。哇塞!听了下面的感觉好神奇好兴奋。所以说我下次要认真听课。林翰讲的简单的东西可能会蹦出好玩的。 :) 求中排....看不见课件。废话没了..
研究对象:DFS树,带有pre和post值,pre pst 就是begin end
//
eg: G = [ (1, [2, 4]), (2, [3]), (3, []),
(4, [6]), (5, [8]), (6, [5, 8]),
(7, []), (8, []), (9, []) ]
pre post实现
void DFS(u,timer/cnt) { timer ++ ; pre[u] = timer; // if post[v] exist ,Cross edge for (u,v) if not visit v // 如果之前已经访问了,就是回边。 DFS(v) timer ++ ; post[u] = timer; }
四种边:
实边:树边 Tree Edge (u,v)
虚边:回边 Forward Edge (当前节点,祖先) 形成环
潜向边 Back Edge (当前节点,后代)
横跨边 Cross Edge (当前节点,祖先别的后代)
设用‘[u’表示pre[u],‘]u’表示post[u]
对于节点u,v
1、u,v都在DFS树中 ((v))
- 如果u是v祖先,则pre[u]<pre[v], post[u]>post[v],再加上pre[u]<post[u], pre[v]<post[v],得pre[u]<pre[v]<post[v]<post[u],即 [u, [v, ]v, ]v。
- if (u,v) is Tree Edge,then pre[v]=pre[u]+1,因为访问完u下一个就是v所以(u,v)才会出现在DFS树中,反之不成立,反例当访问完A的一颗子树最后一个节点u,开始访问下一棵子树的根节点v。
- (u,v) is Forward Edge,v是u祖先
2、u,v不都在DFS树中
- Back Edge : (v(u,u)v) v是u的某个后代,那么在pre[u]出现后,递归肯定会访问到v_pre[v],然后离开v_post[v]后才会继续u的其他分支,接着离开u_post[u]
- Cross Edge : [u ]u [v ]v == (u)(v) 在数轴上,u v 的[pre,post]两个区间不交叉!!! 显然,找一个uv共同祖先p,p先访问到u_pre[u],因为v在另一颗子树上,所以离开u_post[u]后才会访问另一棵子树,pre[v],post[v]才会出现。就是想象数轴是一条时间轴,跟着算法走、我被惊艳到了、、之前没有想过居然可以用区间的形式来判断。。
作用
Back Edge 判断有无环
post 排序可用于拓扑排序, 可以用反证法/演绎法证明。
- 如果post[u]<post[v]出现了,意味着u还没访问完的话v不能访问,赤裸裸的拓扑排序 。。
- 可画图看出/ 老师将DFS树按post排成线性的,可以看到u如果在v左边,即post[u]<post[v],则没有回边(v,u)。
// 果然思路被打断之后有点忘记要写什么了、、、
5、拓扑排序
1- 度序列,不断找一个度为0的更新遍历
for all v if(degree[v]==0) q.push(v) while(!q.empty()){ u = q.top(); q.pop(); // update degree and check for all v (u,v) if(--degree[v]) q.push(v) }
2- 用post值,DFS,O(N+E)
timer = 0; for all v in V set pre = post = 0. for each node v that post[v]= 0; DFS(v). sort(post)
各种实现方法复杂度
DFS
queue // priority_queue
UPDATE :
今天买了课本《算法概论》(注释版),机械工业出版社,英文的,注解是中文的,375页。
如果是清华大学出版的,就是中文的。
之前写的内容和书中3.1~3.3节相同。
摘要:
p90 3.3 DFS
Property :
In a dag,every edge leads to a vertex with aq lower post number.
每一条边都指向post值更低的。
togetther,acyclicity linearity and the absence of back edge during DFS is the same thing
在DFS中,无环 线性(拓扑) 和无back edge 是相同的。
-----------------------------------------------------------------------
UPDATE 2
今天上课和同学聊天突然想到。
4种边,只有对于有向如来说才有意义,对于无向图来说,没多大必要,back edge 和 forward edge是同时存在的; 如果(v->u)是cross edge则u->v一定存在,那么v在DFS树中应在u的孩子当中
const int notgo = 0; const int go1 = 1; const int go2 = 2; void DFS(int u) { if(go[u]==notgo) go[i] = go1; else if (go[u]==go1) go[i] = go2; find adjanced v if(go1) // back edge, cycle else if(go2) // cross edg e else DFS(); }