1.27k likes | 1.37k Views
图论相关算法. 天津大学 2010-03. 简介. PPT 下载: http://oi.tju.edu.cn/learning 主要内容: 图论的常见考点. 图的组织形式. 点、边(有向、无向)、权(容量、费用) 矩阵、邻接表. 广度优先搜索. 通常用队列 ( 先进先出 ,FIFO) 实现 Q={ 起点 s}; 标记 s 为己访问 ; while (Q 非空 ) { 取 Q 队首元素 u; u 出队 ; 所有与 u 相邻且未被访问的点进入队列 ; 标记 u 为已访问 ; }. 广度优先搜索.
E N D
图论相关算法 天津大学 2010-03
简介 • PPT下载: • http://oi.tju.edu.cn/learning • 主要内容: • 图论的常见考点
图的组织形式 • 点、边(有向、无向)、权(容量、费用) • 矩阵、邻接表
广度优先搜索 • 通常用队列(先进先出,FIFO)实现 Q={起点s}; 标记s为己访问; while (Q非空) { 取Q队首元素u; u出队; 所有与u相邻且未被访问的点进入队列; 标记u为已访问; }
广度优先搜索 • 由BFS得到的路径是原图中从S到T的边数最少的路径 • 广度优先搜索树不唯一
广度优先搜索 • 双向广度优先搜索 • 适用于已知出发点和结束点的BFS • 减少不必要的状态,搜索加速 • 分析:搜索分支因子为r(每次可扩展状态数),需要k层,总状态数为rk,双向,前后各rk/2,总状态数2× rk/2 • 哈希判重
广度优先搜索 • 无向图的连通分量 • 无向图的连通分量是指,在连通分量里面的任意两个点之间都有路。 • 易知从某个点v开始进行一次BFS,遍历到的所有点和v就在同一个连通分量内。
广度优先搜索 • 例题: • 无向单位边权值图,300000个点,编号1~n,列出所有中心点的编号 • 中心点:到图中其他所有点的最长距离最小的点
广度优先搜索 • 解法: • 广搜,遍历原图,生成BFS树 • 求树的直径,即树的最长路 • 直径的中间节点(1个或者2个)为中心点
广度优先搜索 • 树的直径求法: • 选任意点开始BFS • 选BFS中深度最大的一个点为直径的一个端点 • 从该端点出发再BFS • 最深的节点为另一端点,且深度为直径长度
深度优先搜索 • 相关概念 • 递归实现 • 结点颜色: • 白色(开始),灰色(发现),黑色(结束) • 一个结点总是从白色变为灰色,再变为黑色 • 当所有点都变为黑色时,遍历结束 • 时间戳(timestamp): d[u]表示一个结点开始被访问的时间,f[u]表示一个结点结束访问的时间
深度优先搜索 int timestamp = 0; dfs(int p) { timestamp = timestamp + 1; col[p] = GREY; d[p] = timestamp; for (每个与p相邻的点i) if (col[i] == WHITE) dfs(i); timestamp = timestamp + 1; f[p] = timestamp; col[p] = BLACK; }
深度优先搜索 • 每个顶点的d[u] < f[u] • DFS中,v是u的子孙 d[u]<d[v]<f[v]<f[u]在搜索中发现u时可以从u出发沿一条完全由白色顶点组成的路径到达v • m条边的连通图,DFS中顺序标号每条边为1~m,则任意与顶点u关联的所有边(边数>=2)的标号的GCD为1
深度优先搜索 • 割点与割边 • 如果从图中删去某点和与该点相关联的边后,图不再连通,那么这个点叫做割点(cut point)。 • 如果从图中删去某条边后,图不再连通,那么这条边叫做割边或桥(bridge)。
深度优先搜索 • 思路 • dep[u]记录节点u在DFS树中的深度 • low[u]记录节点u的子孙所能达到的最浅深度 • u为割点 • u为根,且有大于1个儿子 • u不为根,且u的某个儿子v有low[v] >= dep[u] • (u,v)为割边 • 点u的某个儿子v,有low[v] > dep[u]
深度优先搜索 dfs(int k,int father, int depth) { col[k] = GREY; dep[k] = depth; tot = 0; for (每个与k相邻的点i) { if (i != father && col[i] == GREY) low[k] = min(low[k], dep[i]); if (col[i] == WHITE) { dfs(i, k, depth+1); tot = tot + 1; low[k] = min(low[k], low[i]); if ((k为根 && tot>1)||(k不为根 && low[i] >= dep[k])) k为割点; if (low[i] > dep[k]) then (k,i)为割边; } } col[k] = BLACK; }
深度优先搜索 • 例题: • 某公司拥有N台计算机,并将他们组成局域网,现在要求寻找该局域网中的关键点(即割点),并求出一旦处于该关键点的计算机崩溃,原局域网将会分成多少个子网
深度优先搜索 • Sample Input • 1 2 5 4 3 1 3 2 3 4 3 5 0 • 1 2 2 3 3 4 4 5 5 1 0 • 1 2 2 3 3 4 4 6 6 3 2 5 5 1 0
深度优先搜索 • Sample Output • Network #1 : • SPF node 3 leaves 2 subnets • Network #2 : • No SPF nodes • Network #3 : • SPF node 2 leaves 2 subnets • SPF node 3 leaves 2 subnets
深度优先搜索 • 解法: • 求割点,计算删除该点使连通分量增加数 • 对于DFS森林,对每个割点记录一个cnt值,若有一个儿子v使得low[v] >= dep[u]则使cnt[u]++,则有如下性质: • 对每个非根割点,删除该节点会增加cnt[u]个连通分量 • 对每个根割点,删除该点会使图的连通分量增加其儿子数-1个
拓扑排序 • 图的结点存在一个拓扑序:如果图中有边(u,v),则u必定排在v的前面 • 拓扑排序在有向无环图(DAG)上进行 • 拓扑序列不一定唯一
拓扑排序 • 利用DFS,当节点u已经扩展完成,将标记颜色置为黑时,将u加入已排序列的顶部,搜索完成时,节点序列为拓扑序 • 经过拓扑排序的顶点以与其完成时刻相反的顺序出现
拓扑排序 • 算法 • (1) 计算每个点的入度,入度为0的点加入队列Q • (2) 从Q中取出一个点p,输出 • (3) 所有与p相邻的点的入度减1。如果新得到了入度为0的点,则加入队列Q。 • (4) 转步骤(2), 直到所有点都输出完毕 • 如果在执行过程中发现找不到入度为0的点, 说明图中存在环
1 6 4 3 5 拓扑排序 • 1 3 2 4 5 6 7 8 (不唯一) • 如何生成最小字典序的拓扑序列? • 可以使用最小堆维护当前入度为0的节点 2 7 8
强连通分量 • 有向图的强连通分量(SCC)指:对于强连通分量里面的任意两个节点u和v,都存在u到v和v到u的路
强连通分量 • 思路: • 对原图G进行DFS,记录各点的结束时间,把原图G的所有边反向为GT,在GT以各点结束时间递减的顺序DFS,则每棵树都是一个强连通分量 • 即第一遍DFS生成拓扑序,以拓扑序做第二次DFS
强连通分量 • 缩点操作 • 生成一个新的有向图Gscc,将每个强连通分量作为新图的一个点,原图连通分量内部的边删除,连通分量之间的边保留 • 新图必定有拓扑序,即不会出现有向环(DAG) • Gscc称为原图的核心DAG
强连通分量 • 例题: • 队长要向所有人通知某件事情,因为人数众多,他想出了一个省力的方法:他只通知队中的某些人,然后让这些人去通知所有他们认识的人,这些新被通知的人又去通知更多的人……直到最后队中的所有人都被通知到。 • 给定队长通知每个人所需的花费,现在他想求出一种方案,使得花费最少,并且保证最终所有人都能被通知到。
强连通分量 • Sample Input • 4 3 • 30 20 10 40 • 1 2 • 2 1 • 2 3 • Sample Output • 60
强连通分量 • 解题: • 同一个强连通分量里,只要有一人被通知即可 • 缩点后得到的DAG中,如果一个点被通知,则它的所有后继结点都会被通知。故只需通知入度为0的点 • 在入度为0的每个点所表示的连通分量中,通知花费最少的那个人,即为最优解
强连通分量 • 例题: • 给一个有向图G,添加最少的边数使得G中各点能两两互通(即每对点a和b,a能到b,b也能到a),求最少的边数
强连通分量 • 解题: • 对原图求强连通分量,形成新的DAG图Gscc • 对Gscc计算入度为0的点的个数为x • 对Gscc计算出度为0的点的个数为y • 答案为max (x, y),即从出度为0的点向入度为0的点连max (x, y)条边
欧拉路 • 欧拉路:经过图中每条边恰好一次的路 • 欧拉回路:起点和终点相同的欧拉路
欧拉路 • 无向图存在欧拉路的条件 • 如果图连通,且所有的点都是偶数度,则有欧拉回路。 • 如果图连通,且恰有两点是奇数度,则有欧拉路。且欧拉路的起止点为这两个奇数度点。 • 对重边、自环的情况仍适用。
欧拉路 • 有向图存在欧拉路的条件 • 如果图连通,且每个点的入度等于出度,则存在欧拉回路。 • 如果图连通,且恰有一点u的出度比入度大1,另有一点v的出度比入度小1,其余的出度等于出度,则存在欧拉路,起点为u,终点为v。 • 对重边、自环的情况仍适用。
欧拉路 • 套圈算法 • void Euler(int start) { • for (int i = 1; i <= E; i ++) { • if (!visit[i] && from == start) { • visit[i] = 1; • Euler(to); • path[++ top] = i; • } • } • } • 倒序path[]为欧拉路
欧拉路 • 例题: • 给定一组单词,问这些单词能否连成一串,使得前面一个单词的最后一个字母和后面一个单词的第一个字母相同。
Sample Input 6 aloha arachnid dog gopher rat tiger 3 oak maple elm 欧拉路 • Sample Output • aloha.arachnid.dog.gopher.rat.tiger • ***
欧拉路 • 解法: • 先判断连通性 • 每个单词相当于在首字母和尾字母之间连一条边 • 得到一个最多26个点的有向图 • 求欧拉路
汉密尔顿路 • 汉密尔顿路:经过图中每个点恰好一次的路 • 汉密尔顿回路:起点和终点相同的汉密尔顿路 • 常见方法:状态压缩DP
汉密尔顿路 • 对于点数较少的情况,可以用状态压缩的DP来做。比如用opt[mask][k]表示已经访问过mask表示的结点集合,并且最后位于点k的最优解。 • 状态转移方程为: • opt[mask][k] = min(opt[mask-{k}][i] + cost[i][k]) • i是所有与k相邻的点,mask-{k}指从mask除去k
最短路径 • 单源最短路径 • Dijkstra • Bellman-Ford • SPFA • 任意两点间 • Floyd
最短路径 • 复杂度: • Dijkstra: O(V2)或O(E+VlgV) • Bellman-Ford: O(EV) • Floyd: O(V3) • SPFA: 期望复杂度O(E)
最短路径 • 算法选择: • 编程复杂度:Bellman-Ford<Floyd<Dijkstra • 时间复杂度:Dijkstra<Bellman-Ford • 使用范围:有负权边需要使用Bellman-Ford • 如果是稀疏图,即使是求任意两点间最短路径,也最好选用堆优化的Dijkstra
最短路径 • 单源最短路径的核心 • 松弛操作 • If (d[j] > d[i] + cost[i][j]) d[j] = d[i] + cost[i][j];
最短路径 • Dijkstra
最短路径 • Bellman-Ford主体思想: • 对图中每条边做|V| - 1次松弛操作,即可确定最短路径,当有负权值时,再对所有边做一次松弛操作,若dis值仍有变化则表明存在负环
最短路径 • bool Bellman(int n , int m) • { /*bellman , return false when there is a nagetive circle*/ • int u , v , w , i , j; • bool flag ; • for(i = 1 ; i <= n ; i ++) { • flag = 0; • for(j = 0 ; j < m ; j ++) { • u = edge[j].from , v = edge[j].to , w = edge[j].value; • if(dis[v] > dis[u] + w) { • dis[v] = dis[u] + w; • flag = 1; • } • } • if(!flag) break; • if(i == n && flag)return false; • } • return true; • }