一:起因
(1)关于图的算法一般是比较复杂的,自己在这方面也是比较弱的,首先是图的存储问题 和 遍历问题:
存储分为两种,邻接矩阵 和 临街表;遍历分为DFS 和 BFS两种,非常类似于二叉树的先跟遍历和层次遍历。
(2)图在实际应用中是非常广泛的,这与万物归一,万物相连的理论是一致的,两个物体之间有着千丝万缕的联系,我们成这种联系建立的网络为图(带权图);联系的强弱为边的权重。
(3)图的一些复杂的算法,也是建立在两种遍历图的思想的基础上进行的,所以我们先从简单的谈起。
二:代码示例
(1)最简单的图的遍历问题
Lake Counting
Sample Input
10 12
W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.
Sample Output
3
要求输出图中连通分支的个数,最简单的图遍历问题。
图的常见遍历方式有两种:深度优先遍历和广度优先遍历,他们的作用是将图中的每个点都访问一遍,只不过是顺序不同。
如果把图中的每条边长都相等(比如都是1)的话,深度优先遍历过程就是尽可能深的去访问节点,具体过程为:
1.任意选定一个点p0作为遍历的起点
2.当访问到某一个节点p1时,如果p1下面有未被遍历的子节点,我们就接着访问p1的某一个未被遍历子节点,并标记该子节点被访问过,
3.如果p1不存在未被访问的子节点,我们就退回到p1的父节点,并执行第2步
4.执行第2、3步,直到所有节点被访问过。
大家也看出来了,深度优先遍历的是一个递归的过程,因此很容易想到要用到栈这种数据结构,具体实现的时候可以用递归,也可以用栈。看个人习惯了。
广度优先遍历实现就要用到队列了。以下是poj2386不同实现方式的代码
#include <iostream> #include <string> using namespace std; const int MAX_SIZE = 102; string strs[MAX_SIZE]; int n,m; int dir[8][2] = {{-1,0},{-1,-1},{0,-1},{1,-1},{1,0},{1,1},{0,1},{-1,1}}; inline bool isBound(int x,int y) { return (x<0 || x>=m || y<0 || y>=n); } void dfs(int xindex,int yindex) { strs[xindex][yindex] = '.'; int i,x,y; for(i=0; i<8; ++i) { x = xindex+dir[i][0]; y = yindex+dir[i][1]; if(!isBound(x,y) && strs[x][y]=='W') { dfs(x,y); } } } int main() { int i,j; cin >> m >> n; getline(cin,strs[0]);//¿ÕÐйýÂË for(i=0; i<m; ++i) { getline(cin,strs[i]); //cout << strs[i] << " i= " << i << endl; } int ans = 0; for(i=0; i<m; ++i) { for(j=0; j<n; ++j) { if(strs[i][j]=='W') { //cout << strs[i][j]; ans ++; dfs(i,j); } } } cout << ans << endl; cin >> n; return 0; }
(2)最小生成树
题意:北极有一些村庄,现需要在这些村庄间建立起通讯,有s个卫星频道,任何两个拥有卫星频道的村庄都可以直接通过卫星进行通讯而无视距离,没有卫星的村庄通过无线电进行通讯,并且这两个村庄的距离不能超过D,D值取决于无线电收发器的功率,功率越大,D值越大,但价格也越高,出于购买费用和维护费用的考虑,所有村庄的无线电收发器都相同,即D值相同,现要求在保证任意两个村庄间都能直接或间接通讯,并且D值最小,输出这个最小值。就是输出最小生成树最大边的权值,但是卫星频道是不确定因素。这道题有个很好的解法及证明。
其实题目意思是将一棵最小生成树转化成一个森林,森林里有S棵树,每棵树配一个卫星频道,并且使得森林里所有边中最长的边的长度最小
其实意思就是可以删除最小生成树中的S-1条边,问剩下的边中最长的是多少
由于建图时每两个点之间都有边,是稠密图,故用Prim法比较好 ---- 有的题目也稍微复杂一点,首先得判断是否联通,再求最小生成树
#include <iostream> #include<cmath> #include<algorithm> using namespace std; const int INF = 1000000; const int MAX_SIZE = 501; double map[MAX_SIZE][MAX_SIZE]; double path[MAX_SIZE]; struct node { int x; int y; }point[MAX_SIZE]; //记录从顶点集U到V-U的代价最小的边的辅助数组定义 struct { int adjvex; double lowcost; }closedge[MAX_SIZE]; bool cmp(double a,double b)//从大到小偏序 { return a>b; } // 求距离 inline double CalDist(struct node &a, struct node &b) { double xx = (a.x-b.x); double yy = (a.y-b.y); return sqrt(xx*xx + yy*yy); } //用普里姆算法从第k个顶点出发构造网G的最小生产树T,N个顶点的图 void prim(int k,int n) { int i,j; for(j=1;j<=n;j++)//辅助数组初始化 { if(j!=k) { closedge[j].adjvex=k; closedge[j].lowcost=map[k][j]; } } closedge[k].lowcost=0; //初始,U={u},这里将0作为访问过的标志 int l=0; for(i=1;i<n;i++)//选择其余n-1个顶点,这个i不无任何实际意义,仅仅是选取n-1个点,记录次数而已 { double min=INF; for(j=1;j<=n;j++)//求出T的下一个结点:第k顶点,最小的,不成环(即未访问过) { if(closedge[j].lowcost!=0&&min>closedge[j].lowcost) { k=j; min=closedge[j].lowcost; } } closedge[k].lowcost=0; //第k顶点并入U集 path[l++]=map[k][closedge[k].adjvex]; //保存该边,要是求最小生成树的成本,就得改成加和了,要是求最小生成树的最大边权值,就得比较最大值了 for(j=1;j<=n;j++) //新顶点并入U后重新选择最小边 { if(map[k][j]<closedge[j].lowcost) { closedge[j].adjvex=k; closedge[j].lowcost=map[k][j]; } }// end of for }// end of for } int main() { int t,m,n; int i,j; cin>>t; while(t--) { cin>>m>>n; // input for(i=1;i<=n;i++) { cin>>point[i].x>>point[i].y; } // init dist for(i=1;i<=n;i++) //求出毎两个顶点之间的距离 { for(j=1;j<i;j++) { map[i][j]=map[j][i]=CalDist(point[i],point[j]); //cout << map[i][j] << endl; } } for(i=1;i<=n;i++) { map[i][i]=INF; } // prime prim(1,n); sort(path,path+n-1,cmp); //把构成最小生成树的n-1条边从大到小排序 cout.setf(ios::fixed);//保留两位小数 cout.precision(2); cout<<path[m-1]<<endl;//数组d从下标0开始存储,即第m条边 } return 0; }
(3)