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

Loading in 2 Seconds...

play fullscreen
1 / 128

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


  • 146 Views
  • Uploaded on

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

loader
I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
capcha
Download Presentation

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


An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript
slide1

第八章 图

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

图 应用最广泛的数据结构。

不同于树的另一种非线性结构

每个顶点可以与多个其他顶点相关联,各顶

点之间的关系是任意的。

简单图 没有自身环,两点间至多一条边

v1

v3

v2

v1

v5

v3

v4

v4

v2

无向图

有向图

slide3
图的基本概念

G=<V, E>

V={v1,v2,······,vn} 顶点集

E={ (vi, vj) | vi,vj∈V, vi≠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 为整个图的总度,出度,入度数。

slide4
图的基本概念

路径 vi······vj, 以vi为起点vj为终点的顶点序列。

路径的长 路径上边的数目,

简单路径 顶点都不重复的路径,

回路 环 首尾相接的路径,

简单回路 除第一个和最后一个顶点以外都不重

复的路径,

vivj连通 有路径 vi······vj,

连通图 任意两点都连通,

有向图 vivj强连通 vivj连通 vjvi也连通,

强连通图 任意两点都强连通。

slide5

v2

v2

v1

v1

v4

v4

v3

v3

v5

v5

弱连通

强连通

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

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

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

稀疏图 |E|<nlog n

稠密图 |E|<nlog n

带权边 具有边长的边

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

网络 有权图

slide8
子图

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

如果 V’ V, E’ E ,

就称 G’是G的子图。

连通分量 一个图的极大连通子图。

强连通分量 一个图的极大强连通子图。

连通图的生成树 含有所有顶点的极小 连通图

n个顶点尽可能少边的连通图有n-1条边。

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

slide9
有向树

有向图连通图恰有一个顶点的入度为0,

其余顶点的入度都是1。

有向图的生成森林:

有向图的一个子图,含有所有顶点,构成若干互不相交的有向树,叫做生成森林。

slide10
二、图的存储结构

1.邻接矩阵

用矩阵表示图的顶点之间的相邻关系。

A[i,j]=1 (vi,vj)∈E

=0 o.w.

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

v1

v2

v5

v3

v4

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

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的度

v1

v2

v5

v3

v4

n

j=1

n

n

|E|=1/2 ΣΣA[i,j] 全部数字的和等于边数*2

i=1

j=1

slide12
有向图的邻接矩阵

A[i,j]=1 <vi,vj>∈E

=0 o.w.

0 1 1 0

0 0 0 1

1 0 0 0

1 0 0 0

v3

v1

v4

v2

n

OD(vi)=ΣA[i,j] i行数字的和等于vi的出度

j=1

n

n

|E|= ΣΣA[i,j] 全部数字的和等于边数

i=1

j=1

slide13
网的邻接矩阵

A[i,j]=wi (vi,vj)∈E 权为wi

=∞ o.w.

∞ 5 ∞ 7 ∞ ∞

∞ ∞ 4 ∞ ∞ ∞

8 ∞ ∞ ∞ ∞ 9

∞ ∞ 5 ∞ ∞ 6

∞ ∞ ∞ 5 ∞ ∞

3 ∞ ∞ ∞ 1 ∞

5

v2

v1

4

8

v3

7

3

9

v6

6

1

5

v5

v4

5

slide14

#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 intMaxGraphSize= 25;

slide15

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

slide16

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

slide17

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

slide18

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

slide19
2. 邻接表

A

B

E

C

D

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

slide20
邻接表表示的图的定义

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

};

slide21

template<class T>

struct VNode

{ Tvertex;

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

slide22

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

slide23

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

slide24

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

slide25

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

slide26
图的输入格式

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

A

B

顶点数

顶点序列

边数

边序列

E

C

D

5

A B C D E

5

A B 1

A C 1

B D 1

B E 1

C E 1

Graph<char>G;

G.ReadGraph(“graph.dat”);

slide27

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>::NumberOfVertices(void) const{ return graphsize;}

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

slide29

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

slide30

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

slide31

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

slide32

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

slide33

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

slide34

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>

检查顶点vertex1,vertex2是否在图的

顶点表中,有一个不在图中就给出

错误信息返回。

在图中,则确定位置pos1,pos2,

设置边(pos1,pos2)的权值。

slide36

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

slide37
删除一个顶点

如果顶点不在表中给出错误信息返回。

如果在表中,确定位置pos

把邻接矩阵分成四块

pos

第I块中边不动

第II块中边列左移

第III块中边行上移

第IV块中边

列左移行上移

I

II

pos

III

IV

slide38

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

slide39

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

slide40

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 >class VertexIterator: public SeqListIterator< T >{ public:VertexIterator(Graph< T >& G);};

template <class T >VertexIterator<T>::VertexIterator(Graph<T>& G): SeqListIterator< T > (G.vertexList){}

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

三、图的遍历

输出顶点并标记

循环:{

递归计算第一个邻接点

(如 未标记)

下一个邻接点

}

ABCDEIHF

A

F

B

C

E

H

D

I

slide43

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;

}

slide44

template <class T>

SeqList<T>& Graph<T>::DFS(const int v, int *visited)

{ SeqList<T>*L;

Tvertex=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;

}

slide45
//深度优先搜索2 不用递归用栈

L

F

B

A

S

SeqList<T> L; //输出顶点

Stack<T> S; //存储待算顶点

HE

B

AF

IE

B

A

AFH

F

B

D

B

AFHIE

C

E

H

C

AFHIEDB

D

I

AFHIEDBC

slide46

//深度优先搜索2 不用递归用栈

template <class T>SeqList<T> & Graph<T>::DepthFirstSearch(const T& beginVertex){ Stack<T> S; SeqList<T> *L, adjL; SeqListIterator<T> iteradjL(adjL); Tvertex; L = new SeqList<T>; S.Push(beginVertex);

slide47

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}

slide48

广度优先搜索

用队列

ABFCHEDI

Queue<T> Q;

SeqList<T> L,adjL;

L

Q

BF

A

AB

FCH

A

ABF

CHE

F

B

HED

ABFC

C

E

H

EDI

ABFCH

D

I

ABFCHEDI

slide49

template <class T>SeqList< T >& Graph< T >::BreadthFirstSearch( const T & beginVertex){ Queue< T > Q; SeqList< T > *L, adjL; SeqListIterator< T > iteradjL(adjL);Tvertex; 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; }

slide50
四、无向图的连通分量和生成树

一个图中互相连通的点的极大子集叫连通分量。

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

从连通图的任意一点出发,深度优先搜索到的子图也就是图的生成树,也叫深度优先生成树;广度优先搜索到的生成树,也叫广度优先生成树;

非连通图遍历得到的是生成森林:

从一点出发深度优先搜索并标记,得到一棵树,再继续得到另一棵树,直到取遍所有顶点。

slide51

template <class T>void PrintList(SeqList<T> &L){ SeqListIterator<T> liter(L);for (liter.Reset( ); !liter.EndOfList( ); liter.Next( )) cout << liter.Data( ) << " ";}

slide52

template <class T>int PathConnect (Graph<T> &G, Tv, Tw){ SeqList<T> L;// find vertices connected to vL = G.DepthFirstSearch(v);// is w is in the list, return TRUE if (L.Find(w)) return 1; else return 0;}

slide53

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

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

B

ABC

D

EFG

H

I

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

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

A

D

C

F

E

H

G

I

slide55

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

slide56

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
最小生成树 Minimum-cost Spanning Tree

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

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

slide58
Prim 普里姆算法

设 G=<V,E>,

1. 令 U={v0}, T={ }.

2. 对任意u∈U, v∈V-U, (u,v)∈E,

找到权最小的边(u1,v1),

令U=U∪{v1}, T=T∪{(u1,v1)}

3. 重复2,直至U=V.

得到 T就是最小生成树。

T中共有n-1条边

slide59

A

28

F

16

10

14

B

D

H

24

18

25

12

C

E

22

A

28

F

16

10

14

B

D

H

24

18

25

12

C

E

22

slide60

A

A

5

5

6

6

1

1

B

D

B

D

5

5

5

5

C

C

4

4

6

2

6

2

3

3

E

F

E

F

6

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}

slide61

A

定义数组 closeEdge[n]

纪录每点到U的最短距离 (点,距离)

U中点距离为0,

每加入一个新点, 数组更新一次

5

6

1

B

D

5

5

C

4

6

2

3

E

F

6

slide62

template<class T>struct MiniCostEdgeInfo { Tadjvex; int lowcost; };template <class T>int operator<(MiniCostEdgeInfo<T> a, MiniCostEdgeInfo<T> b){ return a.lowcost<b.lowcost;}

slide63

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

slide64

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

slide65

template<class T >void MiniSpanTreePrim(Graph< T > G){ int j,k,l,n=G.NumberOfVertices( ); MiniCostEdgeInfo< T > * closeEdge;closeEdge =new MiniCostEdgeInfo< T >[n];Ts,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; }

slide66

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
void main( ){ Graph<char> G;G.ReadGraph("sctest.dat");ConnectedComponent(G);MiniSpanTreePrim(G);}
kruskal
Kruskal 克鲁斯卡尔算法

G=(V,E) 连通图

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

  • 选择E中权值最小的边,

若该边连接T的两个连通分量,将它加入T,

这时T的连通分量减少1;

否则选下一条权值最小的边。

  • 重复1 n-1次直到T连通。

T 就是最小生成树

slide69

A

A

5

5

6

6

1

1

1

B

D

5

5

B

D

5

5

5

C

C

4

6

2

4

4

3

6

2

2

3

3

E

F

E

F

6

6

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

A

B

D

C

E

F

kruskal1
Kruskal 克鲁斯卡尔算法

把所有边都放进优先队列。

重复以下步骤,直至T连通:

取出最小边,判断这条边的两个端点,如果属于T的不同的连通分支,加入T, 把两分支联成一个。否则放弃。

问题:

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

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

A

B

D

C

E

F

并查集MFS方法

slide71
问题:1.如何判断两点是否在同一 分支?

用双亲表示法表示结点,每个结点都有一个数据和一个指向双亲的地址的指针。根结点的双亲为-1。

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

D

B

A

C

F

G

E

H

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

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

B

A

D

B

A

D

C

E

C

F

G

E

F

G

H

H

slide73
双亲表示法

用数组存储树的结点

每个结点中附设一个字段

指示其父结点的位置

D

A

B

C

E

F

G

H

slide74

要将AE,CG相连

只要将E的双亲改为0,

G的双亲改为2

D

A

B

C

E

F

G

H

slide75

现在要加入边EG使连通度减小:

先查E所在分支的根,E的双亲是0,即A点,A的双亲是-1,A是根。

同样查到G所在分支的根是2,C点。

0≠2,根不同,分支不同。

将C连到A,即C点的双亲改为0,就可以。

A

B

C

D

E

F

G

H

slide76
双亲表示法结点定义

#define MAX_TREE_SIZE 100

template <class T>

struct PNode

{ T data;

int Parent;

}

slide77
树的双亲表示法定义

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

}

slide78

#include"ptree.h"template<class T>class MFSet:public PTree< T >{ public:MFSet( ){ } int Find(Titem); int FindRoot(int i); int FindRoot(Titem); void Merge(int root1,int root2);};

并查集类的定义

slide79

template <class T >int MFSet< T >::Find(Titem){ 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; }

slide80

template <class T >int MFSet< T >::FindRoot(Titem){ 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;}

slide81

#include<graph.h>#include<conncomp.h>#define maxint 32767template <class T>struct EdgeInfo{ TbeginVex, endVex; int cost;};

slide82

//Kruskal算法#define MaxInt 32767#include"APQueue.h"#include"Graph.h"#include"edgeinfo.h"#include"mfset.h"#include"PTree.h"typedefEdgeInfo<char> EI;

slide83

template <class T>void MiniSpanTreeKruskal(Graph< T > G){ int i, j, l, n=G.NumberOfVertices( );Titem,u,v; EI edge; MFSet< T > MFS; PQueue<EI> L; for(i=0;i<n;i++) { item=GetVertex(G,i); MFS.PTreeInsert(item,-1); }

slide84

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

slide85

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
void main( ){ Graph<char> G;G.ReadGraph("sctest.dat"); cout<<endl; cout<<endl; cout<<endl;MiniSpanTreeKruskal(G);}
slide87
图上两点间最短距离

4

4

E

A

2

B

4

6

12

6

6

10

8

12

F

D

C

20

14

slide88
五、最短路径

两点间边数最少的路径

可用作交通自动咨询系统

两点间边权重的和最小的路径

用来计算两城市间路程最短,

时间最快,费用最省的路径

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

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

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

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

从v0到其余各点的最短路径

v5

60

100

30

v4

v0

(v0,v2) 10

10

20

(v0,v4,v3) 50

10

(v0,v4) 30

v3

v1

(v0,v4,v3,v5) 60

5

50

v2

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

slide92
第一步

L={v0}

v5

60

j1=2

D[2]=10

是v0到v2的最短路径的长度

100

30

v4

v0

10

20

10

v3

v1

5

50

v2

L={v0,v2}

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

slide94

L={v0,v2}

第二步

v5

60

100

30

j2=4

D[4]=30

是v0到v4的最短路径的长度

v4

v0

10

20

10

v3

v1

5

50

v2

L={v0,v2,v4}

slide95
递归过程:重复第二步

设已经有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的最短路径的长.

dijkstra2
迪克斯特拉Dijkstra算法

v5

60

100

L={v0,v2,v4,v3,v5}

30

v4

v0

10

20

10

时间复杂性O(n2)

v3

v1

5

50

v2

slide97
令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 > struct PathInfo{TstartV, endV; int cost;};

template <class T >int operator <= (const PathInfo< T >& a,

const PathInfo< T >& b){ return a.cost <= b.cost;}

slide99

//用优先序列实现最短路径算法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);Tsv, ev; int mincost;

slide100

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

slide101

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

slide102

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

slide103

template<class T>void ShortestPathDijkstra(Graph<T> G, int v0,int *D,int**P){ int i, j,k,l,min, n=G.NumberOfVertices( );Tu,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 }

slide104

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

slide105

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

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

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

弗洛伊德Floyd算法更直接

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不变。

slide108

6

v0

v1

4

11

2

3

v2

floyd1
弗洛伊德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 32767 typedef int distancematrix typedef int pathmatrix
#include"graph.h"#define MaxInt 32767typedef int** DistanceMatrix;typedef int** PathMatrix;
slide111

template <class T>void ShortestPathFloyd( Graph<T> G,PathMatrix *&P, DistanceMatrix& D){ int n=G.NumberOfVertices( ); int i,j,k,l,t; Tu,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;} }

slide112

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

slide113

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
有向无环图 directed acycline graph

有向无环图可以用来描述一项工程的流程

也叫施工流程图,生产流程图,学习流程图等等。

AOV网Activity on Vertex Network

顶点表示一项工作,有向边表示前一项工作完成后才能开始后一项工作。

工作顺序:

ABCDEF

ACDEBF

B

A

F

C

必须无环

构成偏序

D

E

slide116
拓扑排序

为一个AOV网建立一个全序序列,

网中原有先后次序保持不变。

算法原理:

1. 选择一个没有前驱的顶点输出,

2. 去掉这个顶点以及从这点出发的所有边。

重夫1.2.直到所有顶点都输出完毕

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

slide117
拓扑排序算法
  • 用一个数组纪录图G的每个顶点的入度
  • 找出第一个入度为0的点输出
  • 找出与之相连的所有顶点,将他们的入度都减1。
  • 重复2.3.直至没有入度为0的点

用出度可以计算拓扑逆序

slide118
//入度算法函数

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

}

slide120
AOE网 工程进度表示

AOE网 Active on Edges

顶点表示事件,边表示活动以及所需时间

v2

v7

a1=6

a4=1

a7=9

a10=2

v5

v1

v9

a5=1

a2=4

a8=7

a11=4

v8

v3

a3=5

a9=4

a6=2

v4

v6

aoe critical path
AOE网的关键路径critical path

从源点v1到汇点v9的最长路径叫关键路径

(起点) (终点)

入度0唯一 出度0唯一

v2

v7

a1=6

a4=1

a7=9

a10=2

v5

v1

v9

a5=1

a2=4

a8=7

a11=4

v8

v3

a9=4

a3=5

a6=2

v4

v6

slide122
相关定义

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

slide124

v2

v7

a1=6

a4=1

a7=9

a10=2

v5

v1

v9

a5=1

a2=4

a8=7

a11=4

v8

v3

a3=5

a9=4

a6=2

v4

v6

slide125
关键路径的算法

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

template class t void criticalpath graph t g
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; Tu,v;

InDegree(G,indegree);//纪录顶点的入度

for(i=0;i<n;i++) if(indegree[i]==0)S.Push(i);

int count=0; //入度为0的点进栈

slide127

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]

slide128

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

}