算分-GRAPH ALGORITHMS

Graph Search:

  图的种类:最常用的是简单无向图,而且假设顶点始终是有穷多个。复杂图是指有环和平行边的图。我们称两个顶点是相邻的(不妨设u,v),如果(u,v)之间有一条边。

  图的表示:有两种最流行的表示方式,第一种的用n^2的对称矩阵(元素为0或1)来表示,如果是0的话,代表顶点i,j之间没有边连接;如果是1的话,代表顶点i,j之间有边连接,一个不好的地方是空间代价比较大。第二种表示方式是考虑到一个事实,也就是多顶点的图常常为稀疏的图,即边的数量不会比顶点数大很多,此时用邻接表的数据结构来存储。

  DFS搜索:由于图一般无序,通常可以有多种访问的方法。但是为了避免重复访问,我们需要对每个顶点是否访问过做一个标记,可以用一个数组来存储,也可以在顶点的数据结构中加入这个标志。伪代码如下:

 1 void visit(int i)
 2     time++; V[i].d = time
 3     for all outgoing edges ij do:
 4         if V[j].d = 0 then
 5             V[j].p = i; visit(j)
 6         end if
 7     end for;
 8     time++;V[i].f = time.
 9 
10 time = 0
11 for all vertice i do V[i].d = 0 end for;
12 for all vertice i do
13     if V[i].d = 0 then
14         V[i].p = 0; visit(i)
15     end if
16 end for.

  不难发现这个算法复杂度是O(n+m)的,其中n为顶点个数, m为总边数。而且这样的算法会让我们得到每个结点的开始访问时间d和结束访问时间f,对于两个节点i和j,j的访问顺序在i的后面当且仅当v[j].d > v[i].d && v[j].f < v[i].f.接着介绍一个有向非周期(循环)图,一个有向图是非周期(循环的)的,当且仅当它是一个linear extension的,即线性拓展的(留给大家证明)。最后介绍了bfs和dfs做拓扑排序的问题,不再介绍。

Shortest Paths:

  第一种是借助bfs来解决边长为等长的情况,复杂度是O(n+m)。第二种是边长带权重的情况,此时用迪杰斯特拉算法解决问题,算法详情及其正确性的证明我不再介绍,见下图:

1 while priority queue is not-empty do
2     i = ExtractMIN; mark i
3     for all neighbors j of i do
4          if j is unmarked then
5             V[j].d = min(V[j].d, V[i].d + weight(i,j))
6         end if
7     end for
8 end while

  最多执行n次extractmin操作,最后执行m次decrease key操作,根据斐波那契堆的性质,我们可以把复杂度降到O(nlogn + m)d的复杂度。

  另一个问题是解决一个源点到其他所有点之间的最短路径问题,可以两两使用迪杰斯特拉算法,但是要n次,复杂度为O(n^2logn + nm),第二种是使用floyd算法,复杂度为O(n^3),代码如下:

1 for k = 1 to n do
2     for i = 1 to n do 
3         for j = 1 to n do 
4             A[i,j] = min{A[i,j]], A[i,k] + A[k,j]}
5         end for
6     end for
7 end for

Minimum Spanning Trees:

  MST是最小生成树,prim算法+kruskal算法,不再介绍。

Union find:

  我们在实现kruskal算法中要用到Union find的操作,这个问题的有趣之处在于m个元素的复杂度只是比线性的m稍微大一点点。抽象数据类型ADT如下:

  set FIND(i):return P ∈ C with i = P;

  void UNION(P,Q): C = C - {P,Q} U {P U Q}.

  在很多情况下,集合本身是不相关的,但是知道两个元素之间的关系才是最重要的,比如在Kruskal算法中要用到的几步如下:

1 P = FIND(i)
2 Q = FIND(j)
3 if P != Q then UNION(P,Q) end if;

  第一种实现的方法是用一些数组,存储每个元素的所属的set,对应set的代表元素存集合的set,以及每个元素的next(代表元素为0)。UNION方法实现如下:(同时可以证明n-1次union操作的代价是O(NlogN)的。

1 void UNION(int P, Q)
2      if C[P].size < C[Q].size then P ↔ Q endif;
3     C[P].size = C[P].size + C[Q].size;
4     second = C[P].next; C[P].next = Q; t = Q;
5     while t != 0 do
6         C[t].set = P; u = t; t = C[t].next
7     endwhile; C[u].next = second

  第二种是用树来实现,之前讲过,同时也涉及按权重合并+路径压缩的方法来优化问题。可以得到,m次find操作的复杂度为O(mlog*n)其中log*n可以被优化为ACKERMAN函数,比log*n增长得更加慢。

Homework:

  

  

  

  

      

  

原文地址:https://www.cnblogs.com/zyna/p/12256303.html