图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径 拓扑排序

128
图图图图图图 图图图图图图 图图图图 图图图图 图图图图图图图图图图图图 图图图图图图图图图图图图 图图图图 图图图图 图图图图 图图图图

Upload: majed

Post on 22-Jan-2016

339 views

Category:

Documents


0 download

DESCRIPTION

第八章 图. 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径 拓扑排序. 一、图. 图 应用最广泛的数据结构。 不同于树的另一种 非线性结构 每个顶点可以与 多个 其他顶点相关联,各顶 点之间的关系是 任意 的。 简单图 没有自身环,两点间至多一条边. v 1. v 3. v 2. v 1. v 5. v 3. v 4. v 4. v 2. 无向图. 有向图. 图的基本概念. G= - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

图图 图的存储表示图的存储表示 图的遍历图的遍历 无向图的连通分量和生成树无向图的连通分量和生成树 最短路径 最短路径 拓扑排序拓扑排序

Page 2: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

一、图 图 应用最广泛的数据结构。 不同于树的另一种非线性结构 每个顶点可以与多个其他顶点相关联,各顶 点之间的关系是任意的。简单图 没有自身环,两点间至多一条边

v5

v1 v1

v3

v2v3

v4 v4v2

无向图 有向图

Page 3: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

图的基本概念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 为整个图的总度 , 出度 , 入度数。

Page 4: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

图的基本概念路径 vi······vj, 以 vi 为起点 vj 为终点的顶点序列。路径的长 路径上边的数目,简单路径 顶点都不重复的路径,回路 环 首尾相接的路径,简单回路 除第一个和最后一个顶点以外都不重 复的路径,vivj 连通 有路径 vi······vj ,连通图 任意两点都连通,有向图 vivj 强连通 vivj 连通 vjvi 也连通,强连通图 任意两点都强连通。

Page 5: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

v5

v1

v3

v2

v4

v5

v1

v3

v2

v4

弱连通 强连通

Page 6: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

强连通分量:彼此强连通的顶点的子集

A

C

B

D

I

E

G

F

H

ABC

D

EFG

H

I

Page 7: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

完全图 任意两点间都有边相关联的图。

无向完全图 共有边 1/2(n*(n-1)) 条 ,

有向完全图 共有边 n(n-1) 条。

稀疏图 |E|<nlog n

稠密图 |E|<nlog n

带权边 具有边长的边

有权图 图的所有边都是带权边。

网络 有权图

Page 8: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

子图G=(V, E), G’=(V’, E’)

如果 V’ V, E’ E ,

就称 G’ 是 G 的子图。∩ ∩

连通分量 一个图的极大连通子图。强连通分量 一个图的极大强连通子图。连通图的生成树 含有所有顶点的极小 连通图 n 个顶点尽可能少边的连通图有 n-1 条边。

非连通图的生成森林:所有 k 个连通分支的生成树组成生成森林,共有 n-k 条边。

Page 9: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

有向树有向图连通图恰有一个顶点的入度为 0 ,其余顶点的入度都是 1 。

有向图的生成森林:有向图的一个子图,含有所有顶点,构成

若干互不相交的有向树,叫做生成森林。

Page 10: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

二、图的存储结构

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

Page 11: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

无向图的邻接矩阵是对称矩阵

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

Page 12: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

有向图的邻接矩阵 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

Page 13: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

网的邻接矩阵

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

Page 14: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

#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;

Page 15: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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);

Page 16: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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);

Page 17: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

// 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);

Page 18: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

// 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>;};

Page 19: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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

有向图可以有正反两种邻接表

Page 20: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

邻接表表示的图的定义

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

Page 21: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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

Page 22: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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);

Page 23: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

// 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);

Page 24: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

// 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);

};

Page 25: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

// 邻接矩阵表示的图的实现// 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;}

Page 26: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

图的输入格式

顶点数顶点序列边数边序列

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”);

Page 27: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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( ); }

Page 28: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

template <class T>int Graph<T>::NumberOfVertices(void) const{ return graphsize;}

template <class T>int Graph<T>::GraphEmpty(void) const{ return graphsize == 0;}

Page 29: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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;}

Page 30: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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];}

Page 31: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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;}

Page 32: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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;}

Page 33: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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;}

Page 34: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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++;}

Page 35: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

插入一条边 <vertex1,vertex2>

检查顶点 vertex1,vertex2 是否在图的顶点表中,有一个不在图中就给出错误信息返回。

在图中,则确定位置 pos1,pos2 ,设置边( pos1,pos2) 的权值。

Page 36: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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;}

Page 37: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

删除一个顶点如果顶点不在表中给出错误信息返回。如果在表中,确定位置 pos

把邻接矩阵分成四块

I II

III IV

pos

pos

第 I 块中边不动

第 II 块中边列左移

第 III 块中边行上移

第 IV 块中边

列左移行上移

Page 38: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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]; }

Page 39: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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;}

Page 40: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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;}

Page 41: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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){}

Page 42: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

深度优先搜索用递归算法ABCDEIHF

D

B

C

A

E

I

F

H

三、图的遍历输出顶点并标记循环: {

递归计算第一个邻接点 (如 未标记) 下一个邻接点 }

Page 43: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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;

}

Page 44: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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 ;}

Page 45: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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

Page 46: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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 不用递归用栈

Page 47: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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}

Page 48: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

D

B

C

A

E

I

F

H

广度优先搜索

用队列

ABFCHEDI

Queue<T> Q;

SeqList<T> L,adjL;

BF AQL

ABFCH

ABFCHE

ABFCHED

ABFCHEDI

ABFCHEDI

Page 49: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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; }

Page 50: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

四、无向图的连通分量和生成树一个图中互相连通的点的极大子集叫连通分

量。从一点出发,深度优先或广度优先搜索到的

子图就是连通分量。从连通图的任意一点出发,深度优先搜索到

的子图也就是图的生成树,也叫深度优先生成树;广度优先搜索到的生成树,也叫广度优先生成树;

非连通图遍历得到的是生成森林: 从一点出发深度优先搜索并标记,得到一

棵树,再继续得到另一棵树,直到取遍所有顶点。

Page 51: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

template <class T>void PrintList(SeqList<T> &L){

SeqListIterator<T> liter(L);

for (liter.Reset( ); !liter.EndOfList( ); liter.Next( ))

cout << liter.Data( ) << " ";}

Page 52: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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;}

Page 53: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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; } } }

Page 54: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

强连通分量:彼此强连通的顶点的子集

A

C

B

D

I

E

G

F

H

ABC

D

EFG

H

I

做法:从一点 v0 出发作深度优先搜索,得到一个顶点序列 L 。检查 L上的点到 v0 ,是否也连通,所有连通的点组成一个强连通分量。

重复着一过程,直到取遍所有顶点。

Page 55: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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);

Page 56: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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; } }}

Page 57: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

最小生成树 Minimum-cost Spanning Tree

带权连通图(网络)中权值总和最小的生成树叫最小生成树

例连接所有 n 个点的通讯网络的最短线路。

Page 58: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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 条边

Page 59: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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

Page 60: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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}

Page 61: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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 ,每加入一个新点, 数组更新一次

Page 62: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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;}

Page 63: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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;

}

Page 64: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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( );}

Page 65: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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;

}

Page 66: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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; } } }

}

Page 67: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

void main( )

{

Graph<char> G;

G.ReadGraph("sctest.dat");

ConnectedComponent(G);

MiniSpanTreePrim(G);

}

Page 68: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

Kruskal 克鲁斯卡尔算法

G=(V,E) 连通图令 T=(V,{ }) 是 G 的所有顶点而无边的非连通

图。1. 选择 E 中权值最小的边, 若该边连接 T 的两个连通分量,将它加入 T,

这时 T 的连通分量减少 1 ; 否则选下一条权值最小的边。2. 重复 1 n-1 次直到 T 连通。 T 就是最小生成树

Page 69: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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) }

Page 70: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

Kruskal 克鲁斯卡尔算法

把所有边都放进优先队列。重复以下步骤,直至 T 连通:取出最小边,判断这条边的两个端点,如果属于

T 的不同的连通分支,加入 T, 把两分支联成一个。否则放弃。

CB

E

A

F

D

问题:

1. 如何判断两端点是否在同一 分支?

2. 如何把两个分支连到一起?

并查集 MFS方法

Page 71: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

问题: 1. 如何判断两点是否在同一 分支?用双亲表示法表示结点,每个结点都有一个数据

和一个指向双亲的地址的指针。根结点的双亲为 -1 。

同一分支的结点联成一棵树。从每个结点都可以向上找到这个分支的根。两个结点各自所在分支的根相同,则他们处在同一分支,根不同,则所在分支不同。

C

B

E

A

F

D

G

H

Page 72: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

问题 2. 如何把两个分支连到一起?

将第二个分支与第三个分支连起来,只要让结点D 的双亲由 -1改为指向 A 。

C

B

E

A

F

D

G

H

C

B

E

A

F

D

GH

Page 73: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

双亲表示法用数组存储树的结点每个结点中附设一个字段指示其父结点的位置

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

Page 74: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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

Page 75: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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 ,就可以。

Page 76: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

双亲表示法结点定义

#define MAX_TREE_SIZE 100

template <class T>

struct PNode

{ T data;

int Parent;

}

Page 77: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

树的双亲表示法定义

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( ); }

Page 78: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

#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);};

并查集类的定义

Page 79: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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; }

Page 80: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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;}

Page 81: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

#include<graph.h>

#include<conncomp.h>

#define maxint 32767

template <class T>

struct EdgeInfo

{ T beginVex, endVex;

int cost;

};

Page 82: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

//Kruskal 算法#define MaxInt 32767#include"APQueue.h"#include"Graph.h"

#include"edgeinfo.h"#include"mfset.h"

#include"PTree.h"

typedef EdgeInfo<char> EI;

Page 83: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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);

}

Page 84: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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); }}

}

Page 85: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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++; }

}}

Page 86: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

void main( ){ Graph<char> G; G.ReadGraph("sctest.dat"); cout<<endl; cout<<endl; cout<<endl; MiniSpanTreeKruskal(G);}

Page 87: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

图上两点间最短距离

A

D

B

C

E

F

4

6

20 14

6612

8 12

24

104

Page 88: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

五、最短路径

两点间边数最少的路径 可用作交通自动咨询系统两点间边权重的和最小的路径 用来计算两城市间路程最短, 时间最快,费用最省的路径

Page 89: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

两点 A,B 之间边数最少的路径

从 A 点出发,对图做广度优先遍历。

从根 A 到 B 的路径就是边数最少的路径,也就是中转次数最少的路径。

Page 90: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

单源点到其余各点权重和最小的路径

从 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

Page 91: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

迪克斯特拉 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 的最短路径的长度。

Page 92: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

第一步

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}

Page 93: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

迪克斯特拉 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 的最短路径的长度。

Page 94: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

第二步

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}

Page 95: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

递归过程:重复第二步设已经有 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 的最短路径的长 .

Page 96: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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)

Page 97: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

令 L={vj1 ,vj2···,vjk-1} 是已经求得的从 v0 出发的最短路径的终点的集合,可以证明下一条最短路径 ( 终点 vjk), 是只通过 S 中顶点到达 vjk 的 。

否则设 v0 到 vjk 的路径中有一个不在 S 中出现的顶点 vp ,但是路径 v0···vp···vjk比 v0···vp 长

应当先有 v0···vp 的最短路径,以归纳假设 vp 应当已经出现于 L 中。

Page 98: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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;}

Page 99: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

// 用优先序列实现最短路径算法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;

Page 100: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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);

Page 101: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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;}

Page 102: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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( );}

Page 103: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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

}

Page 104: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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; } } } }

Page 105: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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; }}

Page 106: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

每一对顶点之间的最短路径

可让每个顶点作起始点以用 Dijkstra 算法算一遍,共 n 遍,时间复杂性 O(n3).

弗洛伊德 Floyd 算法更直接

Page 107: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

弗洛伊德 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 不变。

Page 108: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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

Page 109: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

弗洛伊德 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]}

Page 110: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

#include"graph.h"#define MaxInt 32767typedef int** DistanceMatrix;typedef int** PathMatrix;

Page 111: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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;} }

Page 112: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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]; }

}}

Page 113: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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]<< " "; } }

Page 114: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

六、拓扑排序

Page 115: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

有向无环图 directed acycline graph

有向无环图可以用来描述一项工程的流程也叫施工流程图,生产流程图,学习流程图等等。

AOV 网 Activity on Vertex Network 顶点表示一项工作,有向边表示前一项工作完

成后才能开始后一项工作。

D

A

C

B

E

F

工作顺序:

ABCDEF

ACDEBF

必须无环

构成偏序

Page 116: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

拓扑排序为一个 AOV 网建立一个全序序列,网中原有先后次序保持不变。

算法原理: 1. 选择一个没有前驱的顶点输出, 2. 去掉这个顶点以及从这点出发的所有边。 重夫 1.2. 直到所有顶点都输出完毕

同样可以计算拓扑逆序,即由末尾向前递归

Page 117: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

拓扑排序算法1. 用一个数组纪录图 G 的每个顶点的入

度2. 找出第一个入度为 0 的点输出3. 找出与之相连的所有顶点,将他们的入

度都减 1 。4. 重复 2.3. 直至没有入度为 0 的点

用出度可以计算拓扑逆序

Page 118: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

// 入度算法函数

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]++;}

}

Page 119: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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!”; }

Page 120: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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

Page 121: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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

Page 122: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

相关定义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 叫做关键活动

Page 123: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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)

Page 124: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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 *

Page 125: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

关键路径的算法

修改拓扑排序算法,增加一个纪录拓扑排序求得的序列。用来逆向计算 vl.

Page 126: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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 的点进栈

Page 127: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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]

Page 128: 图 图的存储表示 图的遍历 无向图的连通分量和生成树 最短路径  拓扑排序

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;}}