图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径 拓扑排序
DESCRIPTION
第八章 图. 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径 拓扑排序. 一、图. 图 应用最广泛的数据结构。 不同于树的另一种 非线性结构 每个顶点可以与 多个 其他顶点相关联,各顶 点之间的关系是 任意 的。 简单图 没有自身环,两点间至多一条边. v 1. v 3. v 2. v 1. v 5. v 3. v 4. v 4. v 2. 无向图. 有向图. 图的基本概念. G= - PowerPoint PPT PresentationTRANSCRIPT
图图 图的存储表示图的存储表示 图的遍历图的遍历 无向图的连通分量和生成树无向图的连通分量和生成树 最短路径 最短路径 拓扑排序拓扑排序
一、图 图 应用最广泛的数据结构。 不同于树的另一种非线性结构 每个顶点可以与多个其他顶点相关联,各顶 点之间的关系是任意的。简单图 没有自身环,两点间至多一条边
v5
v1 v1
v3
v2v3
v4 v4v2
无向图 有向图
图的基本概念G=<V, E>
V={v1,v2,······,vn} 顶点集 E={ (vi, vj) | vi,vj V, v∈ i≠vj} 边集 无向图 E={<vi, vj>|vi , vj V}∈ 有向边集 有向图 有向边 <vi, vj> , vi 起点弧尾, vj 终点弧头
TD(vi): 一个顶点的度 , 以 vi 为端点的边的数目。 OD(vi): 出度 , 以 vi 为起点的边的数目。 ID(vi): 入度,以 vi 为终点的边的数目。 TD(vi)= OD(vi)+ ID(vi)
OD=ID, TD=2|E| , |E| =1/2*TD
TD OD ID 为整个图的总度 , 出度 , 入度数。
图的基本概念路径 vi······vj, 以 vi 为起点 vj 为终点的顶点序列。路径的长 路径上边的数目,简单路径 顶点都不重复的路径,回路 环 首尾相接的路径,简单回路 除第一个和最后一个顶点以外都不重 复的路径,vivj 连通 有路径 vi······vj ,连通图 任意两点都连通,有向图 vivj 强连通 vivj 连通 vjvi 也连通,强连通图 任意两点都强连通。
v5
v1
v3
v2
v4
v5
v1
v3
v2
v4
弱连通 强连通
强连通分量:彼此强连通的顶点的子集
A
C
B
D
I
E
G
F
H
ABC
D
EFG
H
I
完全图 任意两点间都有边相关联的图。
无向完全图 共有边 1/2(n*(n-1)) 条 ,
有向完全图 共有边 n(n-1) 条。
稀疏图 |E|<nlog n
稠密图 |E|<nlog n
带权边 具有边长的边
有权图 图的所有边都是带权边。
网络 有权图
子图G=(V, E), G’=(V’, E’)
如果 V’ V, E’ E ,
就称 G’ 是 G 的子图。∩ ∩
连通分量 一个图的极大连通子图。强连通分量 一个图的极大强连通子图。连通图的生成树 含有所有顶点的极小 连通图 n 个顶点尽可能少边的连通图有 n-1 条边。
非连通图的生成森林:所有 k 个连通分支的生成树组成生成森林,共有 n-k 条边。
有向树有向图连通图恰有一个顶点的入度为 0 ,其余顶点的入度都是 1 。
有向图的生成森林:有向图的一个子图,含有所有顶点,构成
若干互不相交的有向树,叫做生成森林。
二、图的存储结构
1. 邻接矩阵 用矩阵表示图的顶点之间的相邻关系。 A[i,j]=1 (vi,vj) E∈ =0 o.w.
v5
v1
v3
v2
v4
0 1 1 0 0
1 0 0 1 1
1 0 0 0 1
0 1 0 0 0
0 1 1 0 0
无向图的邻接矩阵是对称矩阵
TD(vi)=ΣA[i,j] i 行数字的和等于 vi 的度
v5
v1
v3
v2
v4
0 1 1 0 0
1 0 0 1 1
1 0 0 0 1
0 1 0 0 0
0 1 1 0 0
j=1
n
|E|=1/2 ΣΣA[i,j] 全部数字的和等于边数 *2i=1
n
j=1
n
有向图的邻接矩阵 A[i,j]=1 <vi,vj> E∈ =0 o.w.
v1v3
v4v2
0 1 1 0
0 0 0 1
1 0 0 0
1 0 0 0
OD(vi)=ΣA[i,j] i 行数字的和等于 vi 的出度j=1
n
|E|= ΣΣA[i,j] 全部数字的和等于边数i=1
n
j=1
n
网的邻接矩阵
A[i,j]=wi (vi,vj) E ∈ 权为 wi
=∞ o.w.
v5
v1
v6
v2
v4
v3
∞ 5 ∞ 7 ∞ ∞
∞ ∞ 4 ∞ ∞ ∞
8 ∞ ∞ ∞ ∞ 9
∞ ∞ 5 ∞ ∞ 6
∞ ∞ ∞ 5 ∞ ∞
3 ∞ ∞ ∞ 1 ∞
54
8
9
5
73
1
5
6
#ifndef GRAPH_CLASS#define GRAPH_CLASS
#include <iostream.h>#include <fstream.h>
#include "stack.h"#include "pqueue.h"#include "queue.h"#include "seqlist2.h"
const int MaxGraphSize = 25;
template <class T>class VertexIterator;
template <class T> class Graph{ SeqList<T> vertexList; int edge [MaxGraphSize][MaxGraphSize]; int graphsize; int FindVertex(SeqList<T> &L, const T& vertex);
int GetVertexPos(const T& vertex);
public: Graph(void); int GraphEmpty(void) const; int GraphFull(void) const; int NumberOfVertices(void) const; int GetWeight(const T& vertex1, const T& vertex2);
SeqList<T>& GetNeighbors(const T& vertex); int GetFirstNeighbor(const int v); int GetNextNeighbor(const int v1, const int v2);
// graph modification methods void InsertVertex(const T& vertex); void InsertEdge(const T& vertex1, const T& vertex2, int weight); void DeleteVertex(const T& vertex); void DeleteEdge(const T& vertex1, const T& vertex2);
// utility methods void ReadGraph(char *filename); SeqList<T>& DFS( ); SeqList<T>& DFS(const int v, int *visited); SeqList<T>& DepthFirstSearch(const T& beginVertex); SeqList<T>& BreadthFirstSearch(const T& beginVertex);
int MinimumPath(const T& sVertex, const T& eVertex);
// iterator used to scan the vertices friend class VertexIterator<T>;};
2. 邻接表
0
1
2
3
4
A
B
C
D
E
1 2
2 3
0 3
1E
A
C
B
D
4
4
2
有向图可以有正反两种邻接表
邻接表表示的图的定义
Const int MaxGraphSize=25;
template <class T>struct Edge // 边的类 第一个顶点是隐式的{ int adjvex; // 第二个顶点的编号 int weight; Edge<T> *next; // 指向下一条边的指针 Edge( ):adjvex(0),weight(0),next(0){ } Edge(int v,int w):ajvex(v),weight(w),next(0){ } ~Edge( ){delete next;} };
adj w
template<class T>
struct VNode
{ T vertex;
Edge<T> *firstedge;
}
template<class T>
class ALGraph
{ VNode vertexArry[MaxGraphSize] ;
int vexNum, edgeNum;
int FindVertex(SeqList<T> &L, const T& vertex); int GetVertexPos(const T& vertex);
A
B
public: ALGraph(void); int GraphEmpty(void) const; int GraphFull(void) const; int NumberOfVertices(void) const; int GetWeight(const T& vertex1, const T& vertex2);
SeqList<T>& GetNeighbors(const T& vertex); int GetFirstNeighbor(const int v); int GetNextNeighbor(const int v1, const int v2);
// graph modification methods void InsertVertex(const T& vertex); void InsertEdge(const T& vertex1, const T& vertex2, int weight); void DeleteVertex(const T& vertex); void DeleteEdge(const T& vertex1, const T& vertex2);
// utility methods void ReadGraph(char *filename); SeqList<T>& DFS( ); SeqList<T>& DFS(const int v, int *visited); SeqList<T>& DepthFirstSearch(const T& beginVertex); SeqList<T>& BreadthFirstSearch(const T& beginVertex);
int MinimumPath(const T& sVertex, const T& eVertex);
};
// 邻接矩阵表示的图的实现// constructor initialize entries in the adjacency matrix// to 0 and sets the graphsize to 0template <class T>Graph<T>::Graph(void){ for (int i = 0; i < MaxGraphSize; i++) for (int j = 0; j < MaxGraphSize; j++) edge[i][j] = 0; graphsize = 0;}
图的输入格式
顶点数顶点序列边数边序列
A
C
B
D
0 1 1 0 0
1 0 0 1 1
1 0 0 0 1
0 1 0 0 0
0 1 1 0 0
E
5A B C D E5A B 1A C 1B D 1B E 1C E 1
Graph<char>G;
G.ReadGraph(“graph.dat”);
template <class T >void Graph< T >::ReadGraph(char *filename){ int i, nvertices, nedges; T S1, S2; int weight; ifstream f; f.open(filename, ios::in | ios::nocreate); if(!f){ cerr << "Cannot open " << filename << endl;
exit(1); } f >> nvertices; for (i = 0; i < nvertices; i++) { f >> S1; InsertVertex(S1); } f >> nedges; for (i = 0; i < nedges; i++) { f >> S1; f >> S2; f >> weight; InsertEdge(S1,S2, weight); } f.close( ); }
template <class T>int Graph<T>::NumberOfVertices(void) const{ return graphsize;}
template <class T>int Graph<T>::GraphEmpty(void) const{ return graphsize == 0;}
template <class T>int Graph<T>::GetVertexPos(const T& vertex){ SeqListIterator<T> liter(vertexList); int pos = 0; while(!liter.EndOfList( ) && liter.Data( ) != vertex)
{ pos++; liter.Next( ); } if (liter.EndOfList( )) { cerr << "GetVertex: the vertex is not in the graph."
<< endl; pos = -1; } return pos;}
template <class T>int Graph<T>::GetWeight(const T& vertex1, const T& vertex2){ int pos1=GetVertexPos(vertex1), pos2=GetVertexPos(vertex2); if (pos1 == -1 || pos2 == -1) { cerr << "GetWeight: a vertex is not in the graph." << endl; return -1; } return edge[pos1][pos2];}
template <class T>SeqList<T>& Graph<T>::GetNeighbors(const T& vertex){ SeqList<T> *L; SeqListIterator<T> viter(vertexList); L = new SeqList<T>; int pos = GetVertexPos(vertex); if (pos == -1) { cerr << "GetNeighbors: the vertex is not in the graph."
<< endl; return *L; } for (int i = 0; i < graphsize; i++) { if (edge[pos][i] > 0) L->Insert(viter.Data( )); viter.Next( ); } return *L;}
template <class T>int Graph<T>::GetFirstNeighbor(const int v)
{if (v <0||v>graphsize) { cerr << “The vertex is not in the graph." << endl; return -1; } for(int i = 0; i < graphsize; i++) if(edge[v][i] >0) return i; return -1;}
template <class T>int Graph<T>::GetNextNeighbor(const int v, const int v1){if (v <0||v>graphsize ||v1 <0||v1>graphsize) { cerr << “The vertex is not in the graph." << endl; return -1; } for(int i = v1+1; i < graphsize; i++ ) if(edge[v][i] >0) return i; return -1;}
template <class T>void Graph<T>::InsertVertex(const T& vertex){ if (graphsize+1 > MaxGraphSize) { cerr << "Graph is full" << endl; exit (1); } vertexList.Insert(vertex); graphsize++;}
插入一条边 <vertex1,vertex2>
检查顶点 vertex1,vertex2 是否在图的顶点表中,有一个不在图中就给出错误信息返回。
在图中,则确定位置 pos1,pos2 ,设置边( pos1,pos2) 的权值。
template <class T>void Graph<T>::InsertEdge(const T& vertex1, const T& vertex2, int weight){ int pos1=GetVertexPos(vertex1), pos2=GetVertexPos(vertex2); if (pos1 == -1 || pos2 == -1) { cerr << "InsertEdge: a vertex is not in the graph." << endl; return; } edge[pos1][pos2] = weight;}
删除一个顶点如果顶点不在表中给出错误信息返回。如果在表中,确定位置 pos
把邻接矩阵分成四块
I II
III IV
pos
pos
第 I 块中边不动
第 II 块中边列左移
第 III 块中边行上移
第 IV 块中边
列左移行上移
template <class T>void Graph<T>::DeleteVertex(const T& vertex){ int pos = GetVertexPos(vertex); int row, col; if (pos == -1) { cerr << "DeleteVertex: a vertex is not in the graph." << endl; return; } vertexList.Delete(vertex); graphsize--; for (row = 0; row < pos; row++) for (col = pos + 1;col < graphsize;col++) edge[row][col-1] = edge[row][col]; for (row = pos + 1;row < graphsize;row++) for (col = pos + 1;col < graphsize;col++) edge[row-1][col-1] = edge[row][col]; for (row = pos + 1;row < graphsize;row++) for (col = 0; col < pos; col++) edge[row-1][col] = edge[row][col]; }
template <class T>void Graph<T>::DeleteEdge(const T& vertex1, const T& vertex2){ int pos1=GetVertexPos(vertex1), pos2=GetVertexPos(vertex2); if (pos1 == -1 || pos2 == -1) { cerr << "DeleteEdge: a vertex is not in the graph."
<< endl; return; } edge[pos1][pos2] = 0;}
template <class T>int Graph<T>::FindVertex(SeqList<T> &L, const T& vertex){ SeqListIterator<T> iter(L); int ret = 0; while(!iter.EndOfList( )) { if (iter.Data( ) == vertex) { ret = 1; break; } iter.Next( ); } return ret;}
template <class T >class VertexIterator: public SeqListIterator< T >{ public: VertexIterator(Graph< T >& G);};
template <class T >VertexIterator<T>::VertexIterator(Graph<T>& G): SeqListIterator< T > (G.vertexList){}
深度优先搜索用递归算法ABCDEIHF
D
B
C
A
E
I
F
H
三、图的遍历输出顶点并标记循环: {
递归计算第一个邻接点 (如 未标记) 下一个邻接点 }
template <class T>
SeqList<T>& Graph<T>::DFS( )
{ int *visited=new int[graphsize];
for(int i=0;i<graphsize;i++)
visited[i]=0;
SeqList<T> *L=new SeqList<T>;
*L=DFS(0,visited);
delete[]visited;
return *L;
}
template <class T>SeqList<T>& Graph<T>::DFS(const int v, int *visited)
{ SeqList<T>*L; T vertex=vertexList.GetData(v); L=new SeqList<T>; visited[v]=1; L->Insert(vertex); int w=GetFirstNeighbor(v) while(w!=-1) {if(!visited[w])DFS(w,visited); w=GetNextNeihbor(v,w); } return *L ;}
SeqList<T> L; // 输出顶点Stack<T> S; // 存储待算顶点
// 深度优先搜索 2 不用递归用栈
D
B
C
A
E
I
F
H
FB AS
L
AF
HEBIEB AFH
DB
AFHIE
C
AFHIEDB
AFHIEDBC
template <class T>SeqList<T> & Graph<T>::DepthFirstSearch(const T& beginVertex){ Stack<T> S; SeqList<T> *L, adjL; SeqListIterator<T> iteradjL(adjL); T vertex; L = new SeqList<T>; S.Push(beginVertex);
// 深度优先搜索 2 不用递归用栈
while (!S.StackEmpty( )) {vertex = S.Pop( ); if (!FindVertex(*L,vertex)) { (*L).Insert(vertex); adjL = GetNeighbors(vertex); iteradjL.SetList(adjL);for(iteradjL.Reset( );!iteradjL.EndOfList( );iteradjL.Next( )) if (!FindVertex(*L,iteradjL.Data( ))) S.Push(iteradjL.Data( )); } } return *L; // return list}
D
B
C
A
E
I
F
H
广度优先搜索
用队列
ABFCHEDI
Queue<T> Q;
SeqList<T> L,adjL;
BF AQL
ABFCH
ABFCHE
ABFCHED
ABFCHEDI
ABFCHEDI
template <class T>SeqList< T >& Graph< T >::BreadthFirstSearch( const T & beginVertex){ Queue< T > Q; SeqList< T > *L, adjL; SeqListIterator< T > iteradjL(adjL); T vertex; L = new SeqList< T >; Q.QInsert(beginVertex); // initialize the queue while (!Q.QEmpty( )) { vertex = Q.QDelete( ); if (!FindVertex(*L,vertex)) { (*L).Insert(vertex); adjL = GetNeighbors(vertex); iteradjL.SetList(adjL); for(iteradjL.Reset( );!iteradjL.EndOfList( );iteradjL.Next( )) { if (!FindVertex(*L,iteradjL.Data( ))) Q.QInsert(iteradjL.Data( )); } } } return *L; }
四、无向图的连通分量和生成树一个图中互相连通的点的极大子集叫连通分
量。从一点出发,深度优先或广度优先搜索到的
子图就是连通分量。从连通图的任意一点出发,深度优先搜索到
的子图也就是图的生成树,也叫深度优先生成树;广度优先搜索到的生成树,也叫广度优先生成树;
非连通图遍历得到的是生成森林: 从一点出发深度优先搜索并标记,得到一
棵树,再继续得到另一棵树,直到取遍所有顶点。
template <class T>void PrintList(SeqList<T> &L){
SeqListIterator<T> liter(L);
for (liter.Reset( ); !liter.EndOfList( ); liter.Next( ))
cout << liter.Data( ) << " ";}
template <class T>int PathConnect (Graph<T> &G, T v, T w){ SeqList<T> L; // find vertices connected to v L = G.DepthFirstSearch(v); // is w is in the list, return TRUE if (L.Find(w)) return 1; else return 0;}
template <class T>void UConnectedComponent (Graph<T> &G){ VertexIterator<T> viter(G); SeqList<T> markedList, L; for (viter.Reset( ); !viter.EndOfList( ); viter.Next( ))
{ if (!markedList.Find(viter.Data( ))) { L.ClearList( ); L = G.DepthFirstSearch(viter.Data( )); SeqListIterator<T> liter(L); for(liter.Reset( );!liter.EndOfList( );liter.Next( )) markedList.Insert(liter.Data( )); PrintList(L); cout << endl; } } }
强连通分量:彼此强连通的顶点的子集
A
C
B
D
I
E
G
F
H
ABC
D
EFG
H
I
做法:从一点 v0 出发作深度优先搜索,得到一个顶点序列 L 。检查 L上的点到 v0 ,是否也连通,所有连通的点组成一个强连通分量。
重复着一过程,直到取遍所有顶点。
template <class T>
void ConnectedComponent (Graph<T> &G)
{ VertexIterator<T> viter(G);
SeqList<T> markedList, scList, L;
for (viter.Reset( ); !viter.EndOfList( ); viter.Next( ))
{ if (!markedList.Find(viter.Data( )))
{ scList.ClearList( );
L = G.DepthFirstSearch(viter.Data( ));
SeqListIterator<T> liter(L);
for (liter.Reset( );!liter.EndOfList( );liter.Next( )) if (PathConnect(G,liter.Data( ),viter.Data( ))) { scList.Insert(liter.Data( )); markedList.Insert(liter.Data( )); } PrintList(scList); cout << endl; } }}
最小生成树 Minimum-cost Spanning Tree
带权连通图(网络)中权值总和最小的生成树叫最小生成树
例连接所有 n 个点的通讯网络的最短线路。
Prim 普里姆算法设 G=<V,E>,
1. 令 U={v0}, T={ }. 2. 对任意 u U, v V-U, (u,v) E,∈ ∈ ∈ 找到权最小的边 (u1,v1),
令 U=U {v1}, T=T {(u∪ ∪ 1,v1)} 3. 重复 2 ,直至 U=V. 得到 T 就是最小生成树。 T 中共有 n-1 条边
DB
C
A
E
F
H
10
28
25
22
12
16
18
14
24
DB
C
A
E
F
H
10
28
25
22
12
16
18
14
24
CB
E
A
F
D
65
3
6
4
5
2
15
6
CB
E
A
F
D
65
3
6
4
5
2
15
6
U={A}, T={(A,C)}
U={A,C}, T={(A,C),(C,F)}
U={A,C,F}, T={(A,C),(C,F),(D,F)}
U={A,C,F,D}, T={(A,C),(C,F),(D,F),(B,C)} U={A,C,F,D,B}, T={(A,C),(C,F),(D,F),(B,C),(B,E)} U={A,C,F,D,B,E}
CB
E
A
F
D
65
3
6
4
5
2
15
6
A B C D E F U T
0 A 6 A 1 A 5 A (A,C)
C 5 0 C 6 C 4 C (C,F)
C 5 F 2 C 6 0 F (D,F)
0 D (B,C)
0 B 3 B (B,E)
0 E
定义数组 closeEdge[n]
纪录每点到 U 的最短距离 ( 点,距离 )
U 中点距离为 0 ,每加入一个新点, 数组更新一次
template<class T>struct MiniCostEdgeInfo { T adjvex; int lowcost; };
template <class T>int operator<(MiniCostEdgeInfo<T> a, MiniCostEdgeInfo<T> b){ return a.lowcost<b.lowcost;}
template <class T>
int minimum(MiniCostEdgeInfo<T> *a,int n)
{ for(int i=0;i<n;i++)
if(a[i].lowcost!=0) break;
int min=i;
for(i=min+1;i<n;i++)
if(a[i].lowcost!=0&&a[i]<a[min])
min=i ;
return min;
}
template<class T>T GetVertex(Graph<T> G,int pos){ int i, n=G.NumberOfVertices( ); if(pos<0||pos>=n) {cerr<<"There are not so many vertices!"; return 0; } VertexIterator<T> liter(G); i = 0; while(!liter.EndOfList( ) && i != pos) { i++;
liter.Next( ); } return liter.Data( );}
template<class T >
void MiniSpanTreePrim(Graph< T > G)
{ int j,k,l,n=G.NumberOfVertices( );
MiniCostEdgeInfo< T > * closeEdge;
closeEdge =new MiniCostEdgeInfo< T >[n];
T s,w, v=GetVertex(G,0);
closeEdge[0].lowcost=0;// 起始点 v0 加进 U
for(int i=1;i<n;i++)// 初始化 closeEdge 数组 { w=GetVertex(G,i); l=G.GetWeight(v,w);
closeEdge[i].adjvex=v;
if(l>0)closeEdge[i].lowcost=l;
else closeEdge[i].lowcost=maxint;
}
for( i=1;i<n;i++) //双重循环复杂度 O(n2) 与边数无关 { k=minimum(closeEdge,n); // 确定 closeEdge 中最小值
v=closeEdge[k].adjvex;// 取出这一边w=GetVertex(G,k); l=closeEdge[k].lowcost;
cout<<“\n”<<v<<“ ”<<w<<“ ”<<l<<endl;closeEdge[k].lowcost=0; //将 w 加进 U
for( j=0;j<n;j++) // 更新 closeEdge { v=GetVertex(G,j); l=G.GetWeight(w,v);
if(l>0&&l<closeEdge[j].lowcost) { closeEdge[j].lowcost=l; closeEdge[j].adjvex=w; } } }
}
void main( )
{
Graph<char> G;
G.ReadGraph("sctest.dat");
ConnectedComponent(G);
MiniSpanTreePrim(G);
}
Kruskal 克鲁斯卡尔算法
G=(V,E) 连通图令 T=(V,{ }) 是 G 的所有顶点而无边的非连通
图。1. 选择 E 中权值最小的边, 若该边连接 T 的两个连通分量,将它加入 T,
这时 T 的连通分量减少 1 ; 否则选下一条权值最小的边。2. 重复 1 n-1 次直到 T 连通。 T 就是最小生成树
CB
E
A
F
D
65
3
6
4
5
2
15
6C
B
E
A
F
D
65
3
6
4
5
2
15
6
1
23 4
5
CB
E
A
F
D
T={ (A,C), (D,F), (B,E) }
T={ (A,C), (D,F), (B,E), (C,F) }
T={ (A,C), (D,F), (B,E)
(C,F), (B,C) }
Kruskal 克鲁斯卡尔算法
把所有边都放进优先队列。重复以下步骤,直至 T 连通:取出最小边,判断这条边的两个端点,如果属于
T 的不同的连通分支,加入 T, 把两分支联成一个。否则放弃。
CB
E
A
F
D
问题:
1. 如何判断两端点是否在同一 分支?
2. 如何把两个分支连到一起?
并查集 MFS方法
问题: 1. 如何判断两点是否在同一 分支?用双亲表示法表示结点,每个结点都有一个数据
和一个指向双亲的地址的指针。根结点的双亲为 -1 。
同一分支的结点联成一棵树。从每个结点都可以向上找到这个分支的根。两个结点各自所在分支的根相同,则他们处在同一分支,根不同,则所在分支不同。
C
B
E
A
F
D
G
H
问题 2. 如何把两个分支连到一起?
将第二个分支与第三个分支连起来,只要让结点D 的双亲由 -1改为指向 A 。
C
B
E
A
F
D
G
H
C
B
E
A
F
D
GH
双亲表示法用数组存储树的结点每个结点中附设一个字段指示其父结点的位置
A B C D
E F G H
0
1
2
3
4
5
6
7
A -1
B -1
C -1
D -1
E -1
F -1
G -1
H -1
A B C D
E F G H
0
1
2
3
4
5
6
7
A -1
B -1
C -1
D -1
E -1
F -1
G -1
H -1
0
1
2
3
4
5
6
7
A -1
B -1
C -1
D -1
E 0
F -1
G 2
H -1
要将 AE , CG 相连
只要将 E 的双亲改为 0 ,
G 的双亲改为 2
A B C D
E F G H
0
1
2
3
4
5
6
7
A -1
B -1
C -1
D -1
E 0
F -1
G 2
H -1
0
1
2
3
4
5
6
7
A -1
B -1
C 0
D -1
E 0
F -1
G 2
H -1
现在要加入边 EG使连通度减小:
先查 E 所在分支的根, E的双亲是 0 ,即 A 点, A的双亲是 -1 , A 是根。
同样查到 G 所在分支的根是 2 , C 点。
0≠2 ,根不同,分支不同。
将 C 连到 A,即 C 点的双亲改为 0 ,就可以。
双亲表示法结点定义
#define MAX_TREE_SIZE 100
template <class T>
struct PNode
{ T data;
int Parent;
}
树的双亲表示法定义
template <class T>class PTree{ PNode<T> nodes[MAX_TREE_SIZE]; int n; //number of nodes public: PTree( int m=0); PNode<T> operator[ ](int i); int PTreeInsert(T item, int pr); T PTreeDelete(int i); int PTreeSize( ); }
#include"ptree.h"template<class T>class MFSet:public PTree< T >{ public: MFSet( ){ } int Find(T item); int FindRoot(int i); int FindRoot(T item); void Merge(int root1,int root2);};
并查集类的定义
template <class T >int MFSet< T >::Find(T item){ for(int i=0;i<n;i++) if(nodes[i].data==item) { return i; break;} return -1;}
template <class T >int MFSet< T >::FindRoot(int i) { if(i<0||i>=n)return -1; for(int j=i;nodes[j].parent>=0;j=nodes[j].parent) ;
return j; }
template <class T >int MFSet< T >::FindRoot(T item){ int i=Find(item); return FindRoot(i);}
template <class T >void MFSet< T >::Merge(int root1, int root2){ if(root1<0||root1>=n||root2<1||root2>=n) { cerr<<"Beyound the scope!"; } nodes[root2].parent=root1;}
#include<graph.h>
#include<conncomp.h>
#define maxint 32767
template <class T>
struct EdgeInfo
{ T beginVex, endVex;
int cost;
};
//Kruskal 算法#define MaxInt 32767#include"APQueue.h"#include"Graph.h"
#include"edgeinfo.h"#include"mfset.h"
#include"PTree.h"
typedef EdgeInfo<char> EI;
template <class T>
void MiniSpanTreeKruskal(Graph< T > G)
{ int i, j, l, n=G.NumberOfVertices( );
T item,u,v;
EI edge;
MFSet< T > MFS;
PQueue<EI> L;
for(i=0;i<n;i++)
{ item=GetVertex(G,i);
MFS.PTreeInsert(item,-1);
}
for(i=0;i<n;i++) { u=GetVertex(G,i); for(j=0;j<n;j++) { v=GetVertex(G,j);
l=G.GetWeight(u,v); if(l!=MaxInt&&l>0) { edge.beginVex=u; edge.endVex=v; edge.cost=l;
L.PQInsert(edge); }}
}
int count=1; while(count<n) { edge=L.PQDelete( ); i=MFS.FindRoot(edge.beginVex); j=MFS.FindRoot(edge.endVex); if(i!=j)
{ cout<<edge.beginVex<<" "<<edge.endVex <<" "<<edge.cost<<endl; MFS.Merge(i,j);
count++; }
}}
void main( ){ Graph<char> G; G.ReadGraph("sctest.dat"); cout<<endl; cout<<endl; cout<<endl; MiniSpanTreeKruskal(G);}
图上两点间最短距离
A
D
B
C
E
F
4
6
20 14
6612
8 12
24
104
五、最短路径
两点间边数最少的路径 可用作交通自动咨询系统两点间边权重的和最小的路径 用来计算两城市间路程最短, 时间最快,费用最省的路径
两点 A,B 之间边数最少的路径
从 A 点出发,对图做广度优先遍历。
从根 A 到 B 的路径就是边数最少的路径,也就是中转次数最少的路径。
单源点到其余各点权重和最小的路径
从 v0 到其余各点的最短路径
v3
v0
v5
v2
v4
50
v1
5
30
60100
201010
起点 终点
最短路径 长度
v0 v1
v2
v3
v4
v5
(v0,v4) 30
(v0,v2) 10
(v0,v4,v3) 50
(v0,v4,v3,v5) 60
迪克斯特拉 Dijkstra 算法
按路径长度递增逐步产生最短路径设集合 S 存放已经求出的最短路径的终点,开始,
S 中只有一个源点 v0 ,以后每求得的一条最短路径就将终点加入 S ,直到全部顶点都加入到 S.
定义一个数组 D[n]; n 是图的顶点数。D[i]= 从源点 v0 到顶点 vi 最短路经的长度。
第一步 取 D[i] 为 v0 到 vi 的边的权值,无边时取值∞,
取一个最小值 D[j1]=min{D[i], i<n}
D[j1] 是 v0 到 vj1 的最短路径的长度。
第一步
v3
v0
v5
v2
v4
50
v1
5
30
60100
1010
1 2 3
4 5 6
D[i] 10 30 100
j1=2
D[2]=10
是 v0 到 v2 的最短路径的长度
20
L={v0}
L={v0,v2}
迪克斯特拉 Dijkstra 算法
已经有 L={v0,v2} ,下一条最短路径 ( 终点 vj2),或者是 (v0 vj2), 或者是 (v0, vj1,vj2) 。
对每个顶点 vi, 比较 D[i] 与 D[j1]+arc[j1][i], 取其小
更新 D[i]=min{D[i], D[j1]+arc[j1][i]}
取 D[j2]=min{D[i], i<n,i≠j1 }
则 D[j2] 是 v0 到 vj2 的最短路径的长度。
第二步
v3
v0
v5
v2
v4
50
v1
5
30
60100
1010
1 2 3
4 5 6 j
D[I] 10 30 100 2
D[i] 60 4
j2=4
D[4]=30
是 v0 到 v4 的最短路径的长度
20
L={v0,v2}
L={v0,v2,v4}
递归过程:重复第二步设已经有 v0 到 vj1 ,vj2···,vjk 的最短路径 对每个顶点 vi, vi ≠ vj1 ,vj2···,vjk,
更新 D[i]=min{D[i], D[jk]+arc[jk][i]}
令 D[jk+1]=min{D[i], i<n,i≠ j1 ,j2···,jk }
则 D[jk+1] 是 v0 到 vjk+1 的最短路径的长 .
v3
v0
v5
v2
v4
50
v1
5
30
60100
1010
1 2 3 4 5 L
D[i] 0
10(v0,v2)
30(v0,v4)
100 (v0,v5)
2
D[i] 0 60 (v0,v2,v3)
4
D[i] 50(v0,v4,v3)
0 90 (v0,v4,v5)
3
D[i] 0 60 (v0,v4,v3,v5)
5
20
迪克斯特拉Dijkstra 算法
L={v0,v2,v4,v3,v5}
时间复杂性 O(n2)
令 L={vj1 ,vj2···,vjk-1} 是已经求得的从 v0 出发的最短路径的终点的集合,可以证明下一条最短路径 ( 终点 vjk), 是只通过 S 中顶点到达 vjk 的 。
否则设 v0 到 vjk 的路径中有一个不在 S 中出现的顶点 vp ,但是路径 v0···vp···vjk比 v0···vp 长
应当先有 v0···vp 的最短路径,以归纳假设 vp 应当已经出现于 L 中。
template <class T > struct PathInfo{ T startV, endV; int cost;};
template <class T >int operator <= (const PathInfo< T >& a,
const PathInfo< T >& b){ return a.cost <= b.cost;}
// 用优先序列实现最短路径算法template <class T >int Graph< T >::MinimumPath(const T & sVertex, const T & eVertex){ PQueue< PathInfo< T > > PQ(MaxGraphSize); PathInfo< T > pathData; SeqList< T > L, adjL; SeqListIterator< T > adjLiter(adjL); T sv, ev; int mincost;
pathData.startV = sVertex; pathData.endV = sVertex; pathData.cost = 0; PQ.PQInsert(pathData); while (!PQ.PQEmpty( )) { pathData = PQ.PQDelete( ); ev = pathData.endV; mincost = pathData.cost; if (ev == eVertex) break; if (!FindVertex(L,ev)) {L.Insert(ev); sv = ev; adjL = GetNeighbors(sv); adjLiter.SetList(adjL);
for(adjLiter.Reset( );!adjLiter.EndOfList( ); adjLiter.Next( )) { ev = adjLiter.Data( ); if (!FindVertex(L,ev)) { pathData.startV = sv; pathData.endV = ev; pathData.cost = mincost+GetWeight(sv,ev);
PQ.PQInsert(pathData); } } } }if (ev == eVertex) return mincost; else return -1;}
template<class T>T GetVertex(Graph<T> G,int pos){ int i, n=G.NumberOfVertices( ); if(pos<0||pos>=n) {cerr<<"There are not so many vertices!"; return 0; } VertexIterator<T> liter(G); i = 0; while(!liter.EndOfList( ) && i != pos) { i++;
liter.Next( ); } return liter.Data( );}
template<class T>void ShortestPathDijkstra(Graph<T> G, int v0,int *D,int**P){ int i, j,k,l,min, n=G.NumberOfVertices( ); T u,v,w; u=GetVertex(G,v0); int *final=new int[n]; for( i=0;i<n;i++) { final[i]=0; v=GetVertex(G,i); for(j=0;j<n;j++)P[i][j]=0;//initial P[i][j] D[i]=G.GetWeight(u,v); //initial D[i] if(D[i]<MaxInt){ P[i][v0]=1;P[i][i]=1;} // p[i][j]=1 iff vertex j is in the path from v0 to i
}
D[v0]=0; final[v0]=1; for(i=1;i<n;i++) { min=MaxInt; for(j=1;j<n;j++) //Get the minimum D[k] if(final[j]==0) //vertex j has not marked.
if(D[j]<min) { k=j; min=D[j];}
final[k]=1; //marked vertex k, v=GetVertex(G,k); //found the shortest path for(j=1;j<n;j++) { w=GetVertex(G,j); l=G.GetWeight(v,w)+min;
if(!final[j]&&(l<D[w])) { D[w]=l; //renew D[w] P[j]=P[k]; P[j][j]=1; } } } }
void main( ){ Graph<char> G; G.ReadGraph("sctest.dat"); int n=G.NumberOfVertices( ); int *D=new int[n]; int **P=new (int**[n])[n]; ShortestPathDijkstra(G,0,D,P); for(int i=0;i<n;i++) { cout<<"P["<<i<<"]={ "; cout<<P[i][0]; for(int j=1;j<n;j++) cout<<","<<P[i][j]; cout<<"}"<<endl; }}
每一对顶点之间的最短路径
可让每个顶点作起始点以用 Dijkstra 算法算一遍,共 n 遍,时间复杂性 O(n3).
弗洛伊德 Floyd 算法更直接
弗洛伊德 Floyd 算法定义 Dk(u,v) 为从 u 到 v 的长度最短的 k-path.
假设已知从 u 到 v 的最短 (k-1)-path ,则最短 k-path要么经过,要么不经过顶点 k 。如果经过顶点 k, 则最短 k-path 是从 u 到 k 的最短 (k-1)-path, 再连接从 k 到 v 的最短 (k-1)-path 。如果不经过顶点 k ,则最短路径保持 k-1-path 不变。
v0
v2
3
v1
2
46
11
D
D-1 D0 D1 D2
0 1 2 0 1 2 0 1 2 0 1 2
0 0 4 11 0 4 11 0 4 6 0 4 6
1 6 0 2 6 0 2 6 0 2 5 0 2
2 3 ∞ 0 3 7 0 3 7 0 3 7 0
P P-1 P0 P1 P2
0 1 2 0 1 2 0 1 2 0 1 2
0 v0v1 v0v2 v0v1 v0v2 v0v1v0v1
v2
v0v1v0v1
v2
1 v1v0 v1v2 v1v0 v1v2 v1v0 v1v2v1v2
v0
v1v2
2 v2v0 v2v0v2v0
v1
v2v0v2v0
v1
v2v0v2v0
v1
弗洛伊德 Floyd 算法
int **D=new (int**[n])[n];
int ***P= new ((int***[n])[n])[n];
D-1[i][j]=arc[i][j];
Dk[i][j]=min{Dk-1[i][j], Dk-1[i][k]+ Dk-1[k][j]}
#include"graph.h"#define MaxInt 32767typedef int** DistanceMatrix;typedef int** PathMatrix;
template <class T>void ShortestPathFloyd( Graph<T> G,PathMatrix *&P, DistanceMatrix& D){ int n=G.NumberOfVertices( ); int i,j,k,l,t; T u,v,w; for(i=0;i<n;i++) { u=GetVertex(G,i); for(j=0;j<n;j++) { v=GetVertex(G,j);
D[i][j]=MaxInt; l=G.GetWeight(u,v); if(l>0)D[i][j]=l; for(k=0;k<n;k++) P[i][j][k]=0; if(D[i][j]<MaxInt)
{P[i][j][i]=1;P[i][j][j]=1;} }
for(k=0;k<n;k++)for(i=0;i<n;i++) for(j=0;j<n;j++) if(D[i][k]+D[k][j]<D[i][j])
{ D[i][j]=D[i][k]+D[k][j]; for(t=0;t<n;t++) P[i][j][t]=P[i][k][t]||P[k][j][t]; }
}}
void main( ) { Graph<char> G; G.ReadGraph("sctest.dat"); int n=G.NumberOfVertices( ); DistanceMatrix D=new (int** [n])[n]; PathMatrix* P=new (int***)[n]; for(int i=0;i<n;i++) P[i]=new (int** [n])[n]; ShortestPathFloyd(G,P,D); for( i=0;i<n;i++) {cout<<endl; for(int j=0;j<n;j++) cout<<D[i][j]<< " "; } }
六、拓扑排序
有向无环图 directed acycline graph
有向无环图可以用来描述一项工程的流程也叫施工流程图,生产流程图,学习流程图等等。
AOV 网 Activity on Vertex Network 顶点表示一项工作,有向边表示前一项工作完
成后才能开始后一项工作。
D
A
C
B
E
F
工作顺序:
ABCDEF
ACDEBF
必须无环
构成偏序
拓扑排序为一个 AOV 网建立一个全序序列,网中原有先后次序保持不变。
算法原理: 1. 选择一个没有前驱的顶点输出, 2. 去掉这个顶点以及从这点出发的所有边。 重夫 1.2. 直到所有顶点都输出完毕
同样可以计算拓扑逆序,即由末尾向前递归
拓扑排序算法1. 用一个数组纪录图 G 的每个顶点的入
度2. 找出第一个入度为 0 的点输出3. 找出与之相连的所有顶点,将他们的入
度都减 1 。4. 重复 2.3. 直至没有入度为 0 的点
用出度可以计算拓扑逆序
// 入度算法函数
int n=G.NumberOfVertices( );
int indegree[n];
void InDegree(Graph<T> G,*indegree)
{ int i,j=0; T u,v;
for( i =0; i <n; i ++)indegree[i]=0;
for( i =0; i <n; i ++)
{u=GetVertex(G, i);
for( j =0; j <n; j ++)
v=GetVertex(G, j);
if(G.GetWeight(u,v))indegree[j]++;}
}
Template<class T>void TopologicalSort(Graph<T> G){ int n=G.NumberOfVertices; int *indegree=new int[n]; Stack<int> S; InDegree(G,indegree);// 纪录顶点的入度 for(int i=0;i<n;i++) if(indegree[i]==0)S.Push(i); int count=0; // 入度为 0 的点进栈 while(!S.StackEmpty( )) { i=S.Pop( ); // 取出一个入度 0 的点输出 cout<<GetVertex(G,i)<<“ ”; ++count; for(int j=G.GetFirstNeighber(i);j>0; j= G.GetNextNeighber(i,j)) if(--indegree[j]==0)S.Push(j); // 邻接点入度减 1} if(count<n)cout<<“There is a circle!”; }
AOE 网 工程进度表示AOE 网 Active on Edges
顶点表示事件,边表示活动以及所需时间
v3
v1
v2
v4
v5
a 1=6 a
4 =1
a 5=1
a6=2
a3 =5
a2=4
v8
v7
v6
v9
a10 =2
a 7=9
a8 =7
a 11=4
a 9=4
AOE 网的关键路径 critical path
从源点 v1 到汇点 v9 的最长路径叫关键路径(起点) (终点) 入度 0唯一 出度 0唯一
v3
v1
v2
v4
v5
a 1=6 a
4 =1
a 5=1
a6=2
a3 =5
a2=4
v8
v7
v6
v9
a10 =2
a 7=9
a8 =7
a 11=4
a 9=4
相关定义ve(i) vi 的最早开始时间 =v1 到 vi 的最长路径 ve(n)=工程完成时间vl(i) vi 的最迟开始时间 =vn-vi 到 vn 的最长路径
e(k) 活动 ak 的最早开始时间 若 ak=<vi,vj> 则 e(k)=ve(i)
l(k) 活动 ak 的最迟开始时间 若 ak=<vi,vj> 则 l(k)=vl(j)- ak
l(k)-e(k) 活动 ak 的松弛时间 slack time
若 l(k)=e(k) 则 ak 叫做关键活动
ve(i),vl(i) 的递归算法(以拓扑排序递归)ve(0)=0;
ve(j)=max{ ve(i)+dut<i,j>} <i,j> T∈
dut<i,j> = 边 ak=<vi,vj> 的长T 是所有以 vj 为终点的边的集合
vl(n-1)=ve(n-1)
vl(i)=min{vl(j)-dut<i,j>} <i,j> S∈
S 是所有以 vi 为起点的边的集合
由 ve(i),vl(i) 可以计算 l(k) , e(k)
v3
v1
v2
v4
v5
a 1=6 a
4 =1
a 5=1
a6=2
a3 =5
a2=4
v8
v7
v6
v9
a10 =2
a 7=9
a8 =7
a 11=4
a 9=4
Ve Vl
v1 0 0
v2 6 6
v3 4 6
v4 5 8
v5 7 7
v6 7 10
v7 16 16
v8 14 14
v9 18 18
a1 0 0 *
a2 0 2
a3 0 3
a4 6 6 *
a5 4 6
a6 5 8
a7 7 7 *
a8 7 7 *
a9 7 10
a10 16 16 *
a11 14 14 *
关键路径的算法
修改拓扑排序算法,增加一个纪录拓扑排序求得的序列。用来逆向计算 vl.
Template<class T>void CriticalPath(Graph<T> G)
{ int i,j, l,ee,el,tag,n=G.NumberOfVertices;
int *indegree=new int[n];
int *ve=new int[n];
int *vl=new int[n];
Stack<int> S1,S2; T u,v;
InDegree(G,indegree);// 纪录顶点的入度
for(i=0;i<n;i++) if(indegree[i]==0)S.Push(i);
int count=0; // 入度为 0 的点进栈
while(!S.StackEmpty( )) { i=S.Pop( ); // 取出一个入度 0 的点输出 T.Push(i); ++count; for(j=G.GetFirstNeighber(i); j>0; j= G.GetNextNeighber(i,j)) if(--indegree[j]==0)S.Push(j); // 邻接点入度减 1
u=GetVertex(G,i); v=GetVertex(G,j); l=ve[i]+G.GetWeight(u,v); if(l>ve[j])ve[j]=l; } if(count<n)cout<<“There is a circle!”; for( i=0;i<n;i++) vl[i]=ve[i]; // 初始化 vl[I]
while(!T.StackEmpty( )) for(i=T.Pop( ), j=G.GetFirstNeighber(i); j>0; j= G.GetNextNeighber(i,j)) { u=GetVertex(G,i); v=GetVertex(G,j); l=vl[j]-G.GetWeight(u,v); if(l<vl[i])vl[i]=l; }for( i=0;i<n;i++) for(j=G.GetFirstNeighber(i); j>0; j= G.GetNextNeighber(i,j)) { u=GetVertex(G,i); v=GetVertex(G,j); l=G.GetWeight(u,v); ee=ve[i]; el=vl[j]-l; tag= (ee==el)? ‘*’: ‘ ’ ; cout<<u<<“ ”<<v<<“ ” <<ee<<“ ”<<el<<“ ”<<tag<<endl;}}