数据结构-4. 图
4. 图
4.1 术语和表示法
1) 图可以用 G=(V , E) 来表示,包括一个顶点集合 V 和一个边集合 V。
2) 边数较少的图称为稀疏图,边数较多的图称为密集图,包括所有可能边的图称为完全图。
3) 如果图的边限定从一个顶点指向另一个顶点,则称这个图为有向图,反之则称为无向图。
4) 一条边所连接的两个顶点称为是相邻的,这两个顶点互称邻接点。
5) 如果顶点序列 v1,v2,…,vn 从 vi 到 vi+1(1≤i≤n) 的边均存在,则称顶点序列 v1,v2,…,vn 构成一条长度为 n-1 的路径。如果路径上的各个顶点都不同,则称这个路径为简单路径。
6) 如果一条路径将某个顶点连接到它本身,且其长度大于等于 3,则称此路径为回路。如果构成回路的路径是简单路径,则称此回路为简单回路。
7) 子图 S 是通过从图 G 中选出其顶点集的一个子集 Vs,以及与 Vs 中顶点相关联的一些边构成的子集 Es 所形成的。
8) 如果一个无向图中任意一个顶点到其它任意顶点都至少存在一条路径,则称此无向图为连通的。无向图的最大连通子图称为连通分量。
9) 不带回路的图称为无环图。
4.2 图的实现
1 class Graph{ 2 public: 3 virtual int n()=0; 4 virtual int e()=0; 5 virtual int first(int)=0; 6 virtual int next(int,int)=0; 7 virtual void setEdge(int,int,int)=0; 8 virtual void delEdge(int,int)=0; 9 virtual int weight(int,int)=0; 10 virtual int getMark(int)=0; 11 virtual void setMark(int,int)=0; 12 }; 13 14 class Edge{ 15 public: 16 int vertex,weight; 17 Edge(){ vertex=-1; weight=-1; } 18 Edge(int v,int w){ 19 vertex=v; 20 weight=w; 21 } 22 }; 23 24 class Graphm:public Graph{ 25 private: 26 int numVertex,numEdge; 27 int **matrix; 28 int *mark; 29 public: 30 Graphm(int numVert){ 31 int i,j; 32 numVertex=numVert; 33 numEdge=0; 34 mark=new int[numVert]; 35 for(i=0;i<numVertex;i++) 36 mark[i]=UNVISITED; 37 matrix=(int**)new int*[numVert]; 38 for(i=0;i<numVert;i++) 39 matrix[i]=new int[numVert]; 40 for(i=0;i<numVert;i++) 41 for(j=0;j<numVert;j++) 42 matrix[i][j]=0; 43 } 44 45 ~Graphm(){ 46 delete [] mark; 47 for(int i=0;i<numVertex;i++) 48 delete [] matrix[i]; 49 delete [] matrix; 50 } 51 52 int n(){ return numVertex; } 53 54 int e(){ return numEdge; } 55 56 int first(int v){ 57 int i; 58 for(i=0;i<numVertex;i++) 59 if(matrix[v][i]!=0) 60 return i; 61 return i; 62 } 63 64 int next(int v1,int v2){ 65 int i; 66 for(i=v2+1;i<numVertex;i++) 67 if(matrix[v1][i]!=0) 68 return i; 69 return i; 70 } 71 72 void setEdge(int v1,int v2,int wgt){ 73 //Assert(wgt>0,"Illegal weight value"); 74 if(matrix[v1][v2]==0) 75 numEdge++; 76 matrix[v1][v2]=wgt; 77 } 78 79 void delEdge(int v1,int v2){ 80 if(matrix[v1][v2]!=0) 81 numEdge--; 82 matrix[v1][v2]=0; 83 } 84 85 int weight(int v1,int v2){ return matrix[v1][v2]; } 86 int getMark(int v){return mark[v]; } 87 void setMark(int v,int val){ mark[v]=val; } 88 };
4.3 图的周游
4.3.1深度优先搜索(DFS)
沿着图的某一分支搜索至末端,然后回溯,沿着另一分支搜索,以此类推。
1 void DFS(Graph *G,int v){ 2 G->setMark(v,VISITED); 3 for(int w=G->first(v);w<G->n();w=G->next(v,w)) 4 if(G->getMark(w)==UNVISITED) 5 DFS(G,w); 6 cout<<v<<" "; 7 }
4.3.2 广度优先搜索(BFS)
BFS在进一步深入访问其它顶点前,检查起点的所有邻接点。
1 void BFS(Graph *G,int start,LinkQueue<int> *Q){ 2 int v,w; 3 Q->enqueue(start); 4 G->setMark(start,VISITED); 5 while(Q->length()!=0){ 6 Q->dequeue(v); 7 for(w=G->first(v);w<G->n();w=G->next(v,w)) 8 if(G->getMark(w)==UNVISITED){ 9 G->setMark(w,VISITED); 10 Q->enqueue(w); 11 } 12 cout<<v<<" "; 13 } 14 }
4.3.3 拓扑排序
可以通过深度优先搜索算法来寻找拓扑序列。
1 void tophelp(Graph *G,int v){ 2 G->setMark(v,VISITED); 3 for(int w=G->first(v);w<G->n();w=G->next(v,w)) 4 if(G->getMark(w)==UNVISITED) 5 tophelp(G,w); 6 cout<<v<<" "; 7 } 8 9 void topsort(Graph *G){ 10 int i; 11 for(i=1;i<G->n();i++) 12 G->setMark(i,UNVISITED); 13 for(i=1;i<G->n();i++) 14 if(G->getMark(i)==UNVISITED) 15 tophelp(G,i); 16 }
4.4 最短路径问题
4.4.1 单源最短路径
Dijkstra算法:
1 int minVertex(Graph *G,int *D){ 2 int i,v; 3 for(i=0;i<G->n();i++) 4 if(G->getMark(i)==UNVISITED){ 5 v=i; 6 break; 7 } 8 for(i++;i<G->n();i++) 9 if((G->getMark(i)==UNVISITED)&&(D[i]<D[v])) 10 v=i; 11 return v; 12 } 13 14 void Dijkstra(Graph *G,int *D){ 15 int i,v,w; 16 for(i=0;i<G->n();i++){ 17 v=minVertex(G,D); 18 cout<<"v:"<<v<<endl; 19 if(D[v]==INFINITY) 20 return ; 21 G->setMark(v,VISITED); 22 for(w=G->first(v);w<G->n();w=G->next(v,w)) 23 if(D[w]>(D[v]+G->weight(v,w))) 24 D[w]=D[v]+G->weight(v,w); 25 } 26 }
4.4.2 每对顶点间的最短路径
Floyd算法:
1 void Floyd(Graph* G){ 2 int D[G->n()][G->n()]; 3 for(int i=0;i<G->n();i++) 4 for(int j=0;j<G->n();j++) 5 D[i][j]=G->weight(i,j); 6 for(int k=0;k<G->n();k++) 7 for(int i=0;i<G->n();i++) 8 for(int j=0;j<G->n();j++) 9 if(D[i][j]>(D[i][k]+D[k][j])) 10 D[i][j]=D[i][k]+D[k][j]; 11 }
4.5 最小支撑树(MST)
1) Prim 算法:
从图中任意一个顶点 N 开始,初始化 MST 为 N,选出与 N 相关联的边中权最小的一条边,设其连接顶点 N 与另一个顶点 M,把顶点 M 和边 (N,M) 加入 MST 中。接下来,选出与顶点 N 或顶点 M 相关联的边中权最小的一条边,设其连接另一个新顶点,将这条边和新顶点添加到 MST 中,反复进行这样的处理,每一步都选出一条边来扩展 MST,这条边是连接当前已在 MST 中的某个顶点与一个不在 MST 中的顶点的所有边中代价最小的。
1 int minVertex(Graph *G,int *D){ 2 int i,v; 3 for(i=0;i<G->n();i++) 4 if(G->getMark(i)==UNVISITED){ 5 v=i; 6 break; 7 } 8 for(i++;i<G->n();i++) 9 if((G->getMark(i)==UNVISITED)&&(D[i]<D[v])) 10 v=i; 11 return v; 12 } 13 14 void Prim(Graph *G,int *D){ 15 int V[G->n()]; 16 int i,w; 17 for(i=0;i<G->n();i++){ 18 int v=minVertex(G,D); 19 G->setMark(v,VISITED); 20 V[i]=v; 21 if(D[v]==INFINITY) 22 return; 23 for(w=G->first(v);w<G->n();w=G->next(v,w)) 24 if(D[w]>G->weight(v,w)){ 25 D[w]=G->weight(v,w); 26 V[w]=v; 27 } 28 } 29 cout<<"VVVVVV"<<endl; 30 for(int i=0;i<6;i++) 31 cout<<V[i]<<" "; 32 cout<<endl; 33 }
2) Kruskal 算法:
将顶点集分为 |V| 个等价类,每个等价类中包括一个顶点,然后按照权的大小顺序处理每条边,如果一条边连接属于两个不同等价类的顶点,就把这条边添加到 MST 中,并把这两个等价类合并为一个,反复执行这个过程知道只剩下一个等价类。