首先是图的表示。G=(V, E),两种标准表示方法。一是邻接链表,一是邻接矩阵。邻接链表有|V|条链表,链表Adj[u]包含所有与结点u有边关联的结点v。邻接矩阵是|V|*|V|的二维矩阵,aij可用来表示结点i与j之间有无边或边的权重。
广度优先搜素。从源结点出发向外扩展,访问当前结点的全部相连结点并一一入队列,然后将队列中最前结点出列以相同方法访问其子结点,直到所有结点都被访问。访问过程中作两种标记来控制路线,已访问全部子结点的结点标为visited,已访问自身但未访问完其全部子结点的结点标为visiting。s到v的最短距离为v.d,v.d=u.d+1。树边,前驱子图,广度优先树。
深度优先搜索。探索最近发现的结点的边,若无未访问结点,则回溯其前驱结点继续搜索。直到所有结点都访问完全。访问过程中作两种标记来控制路线,已访问全部子结点的结点标为visited,已访问自身但未访问完其全部子结点的结点标为visiting。time来标记结点第一次访问的时间u.d与最后完成全部子结点访问的时间u.f。括号化定理,白色路径定理。树边,后向边,前向边,横向边。深度优先森林。
拓扑排序。有向无环图的所有结点排成线性序列,任意边的起点排在终点之前。方法是在深度优先搜索时,每当结点完成visited则加入一个链表。
强连通分量。集合内任意两个点,路径(u, v)与(v, u)同时存在,则称为一个强连通分量。寻找强连通分量的算法是运行两次深度优先搜索,第二次是转置图G(V, E' )即所有边方向逆转后的深度优先搜索,并且按照第一次搜索后各结点f值最大值开始搜索。两次搜索完成后形成的深度优先森林是各最大连通分量。证明算法的正确性:假设有结点u与连通分量C,若u.f>C.f,只有两种可能:1,图(V, E)中有路径u->C,此时若在转置图(V, E' )中有路径从u->C则证明u、C在同一连通分量中,u、C可以合并;2,图(V, E)中u、C互相隔离,则转置图G(V, E' )中,u、C也不相连,u不会加入C所在的连通分量。
最小生成树。已有最小生成树的子集,然后用贪心法选择一条边加入子集。最终所有点加入后,得到最小生成树。 Kruskal算法,选择加入子集的原则是每次一条最短的边加入森林,直到所有点构成一棵最小生成树。Prim算法,每次在子集之外与子集连接的边中选择一条最短的边加入。
广度优先搜素。从源结点出发向外扩展,访问当前结点的全部相连结点并一一入队列,然后将队列中最前结点出列以相同方法访问其子结点,直到所有结点都被访问。访问过程中作两种标记来控制路线,已访问全部子结点的结点标为visited,已访问自身但未访问完其全部子结点的结点标为visiting。s到v的最短距离为v.d,v.d=u.d+1。树边,前驱子图,广度优先树。
BFS(G, s){
u=s; mark u as visited; u.d=0; queue.push(u);
while(u){
for each v in G.Adj[u]{
if(v not visited && v not visiting) {v.d= u.d + 1; v.p=u; queue.push(v); mark v as visiting; }
}
mark u as visited;
u=queue.pop();
}
}
深度优先搜索。探索最近发现的结点的边,若无未访问结点,则回溯其前驱结点继续搜索。直到所有结点都访问完全。访问过程中作两种标记来控制路线,已访问全部子结点的结点标为visited,已访问自身但未访问完其全部子结点的结点标为visiting。time来标记结点第一次访问的时间u.d与最后完成全部子结点访问的时间u.f。括号化定理,白色路径定理。树边,后向边,前向边,横向边。深度优先森林。
DFS(G){
for each u in G{
if(u not visited && u has no prev_node) visit(u);
}
}
visit(u){
time++; //注意,time作为全局变量,如果多线程同时搜索不同分支的子树,则有同步的问题
mark u as visiting; u.d=time;
for each v in G.Adj[u]{
if(v not visited && v not visiting) {v.p=u; visit(v); }
}
time++;
mark u as visited; u.f=time;
}
拓扑排序。有向无环图的所有结点排成线性序列,任意边的起点排在终点之前。方法是在深度优先搜索时,每当结点完成visited则加入一个链表。
强连通分量。集合内任意两个点,路径(u, v)与(v, u)同时存在,则称为一个强连通分量。寻找强连通分量的算法是运行两次深度优先搜索,第二次是转置图G(V, E' )即所有边方向逆转后的深度优先搜索,并且按照第一次搜索后各结点f值最大值开始搜索。两次搜索完成后形成的深度优先森林是各最大连通分量。证明算法的正确性:假设有结点u与连通分量C,若u.f>C.f,只有两种可能:1,图(V, E)中有路径u->C,此时若在转置图(V, E' )中有路径从u->C则证明u、C在同一连通分量中,u、C可以合并;2,图(V, E)中u、C互相隔离,则转置图G(V, E' )中,u、C也不相连,u不会加入C所在的连通分量。
最小生成树。已有最小生成树的子集,然后用贪心法选择一条边加入子集。最终所有点加入后,得到最小生成树。 Kruskal算法,选择加入子集的原则是每次一条最短的边加入森林,直到所有点构成一棵最小生成树。Prim算法,每次在子集之外与子集连接的边中选择一条最短的边加入。
MST_Kruskal(G){
E[]= sorted G.E;
for each edge(u,v) in E[] {
if(v not in u.Set){
add edge(u,v) to A;
union(u.Set, v.Set) in one Set;
}
}
}
MST_Prim(G){
add all vertex in Q;
edge(u,v) = minimum(G.E); u.key=edge;
while(Q not empty){
u=Q.extract_min; mark u as MST;
for each v in G.Adj[v]{
if(v not MST && v.key>w(u,v) ) {v.key=w(u,v); v.p=u; update v in Q;}
}
}
}