基础图论知识总结
来源:互联网 发布:战舰世界凤凰数据 编辑:程序博客网 时间:2024/05/20 11:48
1. 最短路
何为最短路?
给定两个顶点,在以这两个点为起点和终点的路径中,边的权值和最小的路径即为最短路
何为单源最短路?何为两点之间的最短路?
固定一个起点,求它到其他所有点的最短路的问题,终点也固定的问题叫做两点之间的最短路问题
Bellman−Ford 算法
记从起点
其中
对于给定的初始化起点
时间复杂度为
const int INF = 0x3f3f3f3f;const int MAXN = 1e3 + 5;struct edge{ int from, to, cost;}edge es[MAXN];//边int d[MAXN];//最短距离int V, E;//V是顶点数,E是边数//求解顶点s到所有点的最短距离void shortest_path(int s){ memset(d, 0x3f, sizeof(d)); d[s] = 0;//初始位置最短距离即为0 while(true){ bool update = false; for(int i = 0;i < E;i ++){ edge e = es[i]; if(d[e.from] != INF && d[e.to] > d[e.from] + e.cost){ d[e.to] = d[e.from] + e.cost; update = true; } } if(!update) break; }}
如果要检查负环,则要对
时间复杂度为
//返回true则存在负环bool find_negative_loop(){ memset(d, 0, sizeof(d)); for(int i = 0;i < V;i ++){ for(int j = 0;j < E;j ++){ edge e = es[j]; if(d[e.to] > d[e.from] + e.cost){ d[e.to] = d[e.from] + e.cost; //如果第n次仍然更新了,则存在负环 if(i == V - 1) return true; } } } return false;}
Dijkstra 算法
基于
找到最短距离已经确定的顶点,从它出发更新相邻顶点的最短距离
此后不需要关系
1 中”最短距离已经确定的顶点”
用邻接矩阵实现的
时间复杂度为
const int MAXN = 1e3 + 5;const int INF = 0x3f3f3f3f;struct edge{ int to, cost;}typedef pair<int, int >PII;//first是最短距离,second是顶点的编号int V;vector<edge>G[MAXN];int d[MAXN];void dijkstra(int s){ //通过制定greater<PII>参数,堆按照first从小到大的顺序取出值 priority_queue<PII, vector<PII>, greater<PII> >que; memset(d, 0x3f, sizeof(d)); d[s] = 0; que.push(PII(0, s)); while(!que.empty()){ PII e = que.top(); que.pop(); int v = e.second; if(d[v] < e.first) continue;//如果取出的不是最短距离则继续取 for(int i = 0;i < G[v].size();i ++){ edge e = G[v][i]; if(d[e.to] > d[v] + e.cost){ d[e.to] = d[v] + e.cost; que.push(PII(d[e.to], e.to)); } } }}
如果存在负边,则还需使用
Floyd−Warshall 算法
作用:任意两点之间的最短路问题
列出状态转移方程:
可以处理负数即负边,而判断图中是否有负环,只需检查是否存在
int d[MAXN][MAXN];//d[v][u]表示边e=(u,v)的权值(不存在时舍为INF, 不过d[i][i]=0)int V;//顶点数void warshall_floyd(){ for(int k = 0;k < V;k ++){ for(int i = 0;i < V;i ++){ for(int j = 0;j < V;j ++){ d[i][j] = min(d[i][j], d[i][k] + d[k][j]); } } }}
路径还原
在求解最短距离时,满足
此外,如果用
代码:
vector<int> get_path(int t){ vector<int> path; for(; t != -1; t = prev[t]){ path.push_back(t); } reverse(path.begin(), path.end()); return path;}
1.1 多源多汇最短路
何为多源多汇最短路?
即起点有多个,终点也有多个的最短路
相关知识点连接
对于多源多汇最短路,大家也比较熟悉了,
原理分析
而现在要讲的是针对小数目的求解多源多汇最短路的方法-构造超级源点和超级终点(专业用语称为汇点)
针对每一个起点,我们构造一个超级源点,这个超级源点连接每一个需要进行求解的起点,边的权值为
一般超级源点为
接下直接求超级起点
2. 最小生成树
何为最小生成树?
给定一个无向图,如果它的某个子图中任意两个顶点都互相连通并且是一棵树,那么这个数叫做生成树,如果边上有权值,那么使得权值和最小的生成树叫做最小生成树
Prim 算法
原理解析
我们假设有一颗只包含一个顶点
首先,我们设任意两顶点之间的最小距离为
其中
简单一点的理解思路:
首先是从一个无向图中随便找一个顶点当作一个起始点,原因很简单,便是对于一个最小生成树而言,他一定包括所有的点,那么以任意一个点为起点对最终的结果是没有什么影响的。
如此,我们接下来就是如何去构造出一个最小生成树出来了。
一个最简单明了的思路,也是与后续即将讲到的
如果单纯的使用
没有优化的
时间复杂度:
using namespace std;const int MAXN = 1e3 + 5;/***************************/int Cost[MAXN][MAXN];//已经在主函数中读入数据(读入前必须进行初始化设置)int mincost[MAXN];//即为当前我已经选择了的点离其他点的最小距离bool vis[MAXN];//是否已经加入了我选择的顶点中int V;//顶点的个数int prim(){ memset(mincost, 0x3f, sizeof(mincost)); memset(vis, false, sizeof(vis)); mincost[0] = 0;//随便找一个点作为起点,此处可以是mincost[2] = 0,mincost[4] = 0,当然最好是mincost[0] = 0,原因大家思考 int res = 0;//用于计算最终的最小生成树的权值和 while(true){ int v = -1;//用于存储我要将哪个顶点加入到我的最小生成树中来 for(int u = 0; u < V;u ++){//这个循环的目的是为了得到离我现在已经构成的生成树的最小距离的顶点 if(!vis[u] && (v == -1 || mincost[u] < mincost[v])) v = u; } if(v == -1) {//没有找到,证明已经得到最小生成树 break; } res += mincost[v]; vis[v] = true; for(int u = 0;u < V;u ++){//更新当前没有加入最小生成树的点离我架构的最小生成树的最小距离,不断更新 mincost[u] = min(mincost[u], Cost[u][v]); } } return res;}
用优先队列进行优化的
时间复杂度:
const int INF = 0x3f3f3f3f;const int MAXN = 1e3 + 5;struct edge{ int to, from;}typedef pair<int,int>PII;edge es[MAXN];int V;vector<edge>G[MAXN];int mincost[MAXN];bool vis[MAXN];int Prim(void){ priority_queue<PII, vector<PII>, greater<PII> >que; memset(mincost, 0x3f, sizeof(mincost)); memset(vis, false, sizeof(vis)); mincost[0] = 0;//随便找一个点作为起点,此处可以是mincost[2] = 0,mincost[4] = 0,当然最好是mincost[0] = 0,原因大家思考 int res = 0;//用于计算最终的最小生成树的权值和 que.push(PII(0,0)); while(!que.empty()){ PII p = que.top(); que.pop(); int v = p.second; vis[v] = true;//记录当前这个顶点已经加入到了生成树中 res += p.first;//将从前驱顶点到这个顶点的距离加入权值和中 for(int i = 0;i < G[v].size();i ++){ edge &e = G[v][i]; if(vis[e.to]) continue;//如果已经加入到了最小生成树则不进行处理 if(mincost[e.to] > e.cost){//如果当前没有加入生成树的顶点是当前的最小距离的话 mincost[e.to] = e.cost; que.push(PII(e.to, e.cost)); } } } return res;}
3. 树的直径
何为树的直径?
树的直径指的是一颗生成树中最长路
算法原理分析
求解一颗树的直径的方法有若干种,这里介绍一种最常见的,两个BFS即可解决问题
设
u 为s−t 路径上的一点,结论显然成立,否则设搜到的最远点为T 则d(u,T)>d(u,s) 且d(u,T)>d(u,t) 则最长路不是s−t 了,与假设矛盾.设
u 不为s−t 路径上的点,首先明确,假如u 走到了s−t 路径上的一点,那么接下来的路径肯定都在s−t 上了,而且终点为s 或t ,在1 中已经证明过了
所以现在又有两种情况了:u 走到了s−t 路径上的某点,假设为k ,最后肯定走到某个端点,假设是t ,则路径总长度为d(u,k)+d(k,t) .u 走到最远点的路径u−T 与s−t 无交点,则d(u−T)>d(u,k)+d(k,t) 显然,如果这个式子成立,则d(u,T)+d(s,k)+d(u,k)>d(s,k)+d(k,t)=d(s,t) 与最长路不是s−t 矛盾
所以我们只要以任意一顶点为起点,求解出他的最长路的一个端点
代码:
时间复杂度:
struct o { int to, cost, nxt;} E[MAXN << 1];int Head[MAXN], d[MAXN], tot;bool vis[MAXN];void init_edge() { memset(Head, -1, sizeof(Head)); tot = 0;}void add_edge(int u, int v, int cost) { E[tot].to = v; E[tot].cost = cost; E[tot].nxt = Head[u]; Head[u] = tot ++;}int BFS(int s) { int sum = 0, dot = -1; priority_queue<PII>que; mem(d, -1); mem(vis, false); d[s] = 0; que.push(PII(0, s)); while(!que.empty()) { PII p = que.top(); que.pop(); int u = p.second; if(d[u] > p.first || d[u] == -1) continue; if(vis[u]) continue; vis[u] = true; if(p.first > sum) { sum = p.first; dot = u; } for(int v = Head[u]; ~v; v = E[v].nxt) { o &e = E[v]; if(d[e.to] < d[u] + e.cost) { d[e.to] = d[u] + e.cost; que.push(PII(d[e.to], e.to)); } } } return dot;}int main() {#ifndef ONLINE_JUDGE //FIN; //FOUT;#endif int n, m; IO_Init(); while(~scanf("%d%d", &n , &m)) { int x, y, c; char S[2]; init_edge(); for(int i = 0; i < m; i ++) { scanf("%d%d%d%s", &x, &y, &c, S); add_edge(x, y, c); add_edge(y, x, c); } int v = BFS(1); int u = BFS(v); printf("%d\n", d[u]); } return 0;}
4. 树的重心
何为树的重心
以这个点为根,那么所有的子树(不算整个树自身)的大小都不超过整个树大小的一半.
另一种解释,即一个点的所有子树的最大值最小,这个点即为树的重心.
原理分析
从任一点出发,求解出每一个点下的所有子树的最大值,然后用变量
代码:
代码复杂度:
int dp[MAXN], son[MAXN];int Head[MAXN], tot, res, n;struct o{ int to, cost, nxt;}E[MAXN << 1];void init_edge(){ mem(Head, -1); tot = 0; res = INF;}void add_edge(int u, int v, int cost){ E[tot].to = v; E[tot].cost = cost; E[tot].nxt = Head[u]; Head[u] = tot ++;}void dfs(int u, int f){ son[u] = 1; int nson = 0; for(int v = Head[u]; ~v; v = E[v].nxt){ o &e = E[v]; if(e.to == f) continue; dfs(e.to, u); son[u] += son[e.to]; nson = max(nson, son[e.to]); } dp[u] = max(nson, n - son[u]); res = min(dp[u], res);}int main() {#ifndef ONLINE_JUDGE // FIN; // FOUT;#endif int T; int x, y; IO_Init(); scanf("%d", &T); while(T --){ init_edge(); scanf("%d", &n); for(int i = 1;i < n;i ++){ scanf("%d%d", &x, &y); add_edge(x, y, 1); add_edge(y, x, 1); } dfs(1, 0); for(int i = 1;i <= n;i ++){ if(dp[i] == res){ printf("%d %d\n", i, res); break; } } } return 0;}
知识点未完,敬请期待!
- 基础图论知识总结
- Spring 知识基础总结
- 【JavaScript】知识总结---基础
- Java知识总结-基础
- 基础数论知识总结
- JAVA基础总结知识
- 图论知识总结
- java 基础小知识 总结
- Java基础之知识总结
- 数据库基础应用知识总结
- 基础正则表达式知识学习总结
- SQL数据库开发知识总结:基础篇
- 黑马程序员-----java基础语法知识总结*
- androidの常用基础应用知识总结
- c++基础小知识归纳总结
- Android基础和进阶知识总结
- oc总结 --oc基础语法相关知识
- 【Java基础】continue用法知识总结
- 我的jquery之路(二)
- 文章标题
- sed工具以及awk工具的介绍及使用
- iOS开发 非常全的三方库、插件、大牛博客等等
- netbeans c++环境配置
- 基础图论知识总结
- jdbc查询大量数据内存溢出的解决方法
- 初次写博客,留作纪念
- 如何删除表中重复的wid
- js 判断数组中元素是否重复
- 使用v7下的AlertDialog遇到的问题
- 通俗的来讲RPC是什么?
- android 性能优化
- AlexNet论文学习笔记