图论的两个经典问题。
1、先介绍树的概念:
树的概念挺简单的,一个祖先。一个儿子仅仅能有一个父亲节点,不能形成环。
n个节点仅仅能有n-1条边,要不然会形成环。(易得知)
2、再来讲讲我用来存图的两种方式:
1、邻接矩阵(使用于稠密图)
map[b][a] = map[a][b] = c;代表的意思是a到b的距离为c.
如图(网上找的图):
2、邻接表(适用于稀疏图)
struct Edge {///数组的下标代表边的还有一个端点 int v; //边端点。还有一端点已知 int w; //边权值 Edge(int v_ = 0, int w_ = INFINITE): v(v_), w(w_) { } };
vector< vector <Edge> > G(110); //图的邻接表如图(网上找的图):
開始进入正题。(有些题目我会用两种存图方式写,有些仅仅能用当中一种)
一、最小生成树
1、概念:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包括原图中的全部 n 个结点,而且有保持图连通的最少的边。
我们先介绍prim算法,然后再介绍kruskal算法。
prim算法的思路非常easy。就是从一个起点開始进行连通始终寻找没有訪问过且最小的边来进行连通。
POJ:1258 http://poj.org/problem?
题意非常easy就是依据n个点求一颗最小生成树。
prim代码例如以下:
#include<cstdio> #include<cstring> #include<iostream> #define maxn 100 + 15 #define inf 200000005 int dis[maxn], vis[maxn]; int map[maxn][maxn]; int n, sum; using namespace std; void prim() { memset(vis, 0, sizeof(vis)); for(int i = 0; i < n; ++i) dis[i] = map[i][0]; vis[0] = 1; dis[0] = 0; for(int i = 0; i < n - 1; ++i) { ///n-1条边 int k, temp = inf; ///temp用来找最小的边,k存储最小边的相应的点 for(int j = 0; j < n; ++j) { if(!vis[j] && dis[j] < temp) temp = dis[j], k = j; } vis[k] = 1; ///标记找到的点 sum += dis[k]; ///最小边增加到最小生成树里面 for(int j = 0;j <n;++j) if(!vis[j]&&dis[j] > map[k][j]) dis[j] = map[k][j]; ///存储小的 } cout << sum << endl; } int main() { while(scanf("%d", &n) != EOF) { for(int i = 0; i < n; ++i) for(int j = 0; j < n; ++j) { int value; scanf("%d", &value); map[i][j] = value; ///邻接矩阵存图 } sum = 0; prim(); } return 0; }
kruskal算法的思路:把边按从小到大排序,然后运用并查集的知识依照边的两个端点进行合并,推断是不是同一个连通分量假设不是进行合并,假设是的话则跳过。读到n-1条边合并之后则是最小生成树。
代码例如以下:(POJ1258)
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<vector> #define maxn 100 + 15 int father[maxn]; using namespace std; ///邻接矩阵存图 struct Edge { int s, e, v; Edge(int ss, int ee, int vv): s(ss), e(ee), v(vv) {} Edge() {} }; ///按边从大到小排序 bool cmp(Edge a,Edge b) { return a.v < b.v; } vector<Edge>vp; ///并查集 int Find(int x) { if(x == father[x]) return x; else return father[x] = Find(father[x]); } void Union(int a, int b) { int aa = Find(a); int bb = Find(b); if(aa == bb) return ; else father[bb] = aa; } void Clear(int n) { for(int i = 0; i < n; ++i) father[i] = i; } int main() { int n; while(scanf("%d", &n) != EOF) { Clear(n); vp.clear(); for(int i = 0; i < n; ++i) for(int j = 0; j < n; ++j) { int value; scanf("%d", &value); vp.push_back(Edge(i,j,value)); } sort(vp.begin(),vp.end(),cmp); ///按边排序 int sum = 0; int num = 0; for(int i = 0; i < vp.size(); ++i) { int a = vp[i].s, b = vp[i].e, c = vp[i].v; if(Find(a) != Find(b)) { ///是不是同一个祖先 num++; sum += c; Union(a, b); ///合并成统一个连通分量 } if(num == n - 1) break; ///找到n-1条边,数已经生成,退出。 } cout << sum << endl; } return 0; }
二、最短路
1、概念:若网络中的每条边都有一个数值(长度、成本、时间等)。则找出两节点(一般是源节点和阱节点)之间总权和最小的路径就是最短路问题。
这里我介绍两种个算法。floyd,dijkstra
HDU2544 http://acm.hdu.edu.cn/showproblem.php?pid=2544
裸裸的最短路。
2、dijkstra代码:
#include<cstdio> #include<iostream> #include<cstring> #define MIN(a,b) a>b?3、floyd算法(更新了全部点的最短路时间复杂度为O(n^3)):b:a #define maxn 201 #define INF 200000005 int map[maxn][maxn]; int dis[maxn]; bool vis[maxn]; using namespace std; int main() { int n, m; while(scanf("%d%d", &n, &m) != EOF) { if(n == 0 && m == 0) break; memset(vis, 0, sizeof(vis)); for(int i = 1; i <= n; i++) { for(int j = 1; j <= n; j++) if(i == j) map[i][j] = 0; ///自己到自己的距离为0 else map[i][j] = INF; ///初始到其它点的距离为无穷大 } for(int i = 1; i <= m; i++) { int a, b, c; scanf("%d%d%d", &a, &b, &c); map[a][b] = map[b][a] = c; ///双向距离相等 } for(int i = 1; i <= n; i++) dis[i] = map[1][i]; vis[1] = true; for(int i = 1; i < n; i++) { int temp = INF; int k; for(int j = 1; j <= n; j++) { if(vis[j]) continue; if(temp > dis[j]) { temp = dis[j]; k = j; } } vis[k] = true; for(int j = 1; j <= n; j++) { if(vis[j]) continue; dis[j] = MIN(dis[j], dis[k] + map[k][j]); } } cout << dis[n] << endl; } return 0; }
///HDU 2544. #include<cstdio> #include<cstring> #include<algorithm> #define maxn 100 + 15 #define inf 100000005 int a[maxn][maxn], dis[maxn], vis[maxn]; int n, m; using namespace std; int main() { while(scanf("%d%d", &n, &m) != EOF && (n && m)) { for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) { a[i][j] = 0; if(i != j) a[i][j] = inf; } for(int i = 1; i <= m; i++) { int x, y, z; scanf("%d%d%d", &x, &y, &z); ///更新最小值 if(a[x][y] > z) a[x][y] = a[y][x] = z; } ///更新了全部点的最短路 for(int k = 1; k <= n; k++) for(int i = 1; i <= n; i++) for(int j = 1; j <= n; ++j) { a[i][j] = min(a[i][j], a[i][k] + a[k][j]); } printf("%d ", a[1][n]); } return 0; }
4、dijkstra(邻接表存图):
POJ3159
代码:
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<queue> #include<vector> #define maxn 30000 + 5 #define inf 200000005 int vis[maxn]; int n, m; using namespace std; ///邻接表存图 struct edge { int e, cc; ///本身自带一个节点,e代表还有一个节点 v代表权值 }; bool operator < (const edge &a, const edge &b) { return a.cc > b.cc; } ///优先队列 priority_queue<edge>pq; ///邻接表 vector< vector<edge> >v; int main() { while(scanf("%d%d", &n, &m) != EOF) { edge d, z; v.clear(); v.resize(n + 1); for(int i = 0; i < m; ++i) { int a, b, c; scanf("%d%d%d", &a, &b, &c); d.e = b; d.cc = c; v[a].push_back(d);///邻接表存图 } memset(vis,0,sizeof(vis)); d.e = 1; d.cc = 0; pq.push(d); while(!pq.empty()) { d = pq.top();///不能使用pq.front(); pq.pop(); if(vis[d.e]) continue; ///表示已经走过 vis[d.e] = 1; if(d.e == n) break; ///找到该点 for(int i = 0 ; i < v[d.e].size(); ++i) { z.e = v[d.e][i].e; if(vis[z.e]) continue; ///邻接表里的点有没有訪问过 z.cc = v[d.e][i].cc + d.cc; pq.push(z); } } cout << d.cc << endl; } return 0; }