第八章 图
This presentation is the property of its rightful owner.
Sponsored Links
1 / 128

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


  • 99 Views
  • Uploaded on
  • Presentation posted in: General

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

Download Presentation

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

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


第八章 图

  • 图的存储表示

  • 图的遍历

  • 无向图的连通分量和生成树

  • 最短路径

  • 拓扑排序


一、图

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

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

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

点之间的关系是任意的。

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

v1

v3

v2

v1

v5

v3

v4

v4

v2

无向图

有向图


图的基本概念

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


图的基本概念

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

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

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

回路 环 首尾相接的路径,

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

复的路径,

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

连通图 任意两点都连通,

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

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


v2

v2

v1

v1

v4

v4

v3

v3

v5

v5

弱连通

强连通


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

B

A

ABC

D

EFG

H

I

D

C

F

E

H

G

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.

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


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

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


有向图的邻接矩阵

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


网的邻接矩阵

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


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


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. 邻接表

A

B

E

C

D

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


邻接表表示的图的定义

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

};


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


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


图的输入格式

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


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

把邻接矩阵分成四块

pos

第I块中边不动

第II块中边列左移

第III块中边行上移

第IV块中边

列左移行上移

I

II

pos

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

A

F

B

C

E

H

D

I


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;

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;

}


//深度优先搜索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


//深度优先搜索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);


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}


广度优先搜索

用队列

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


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


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

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

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

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

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

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


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


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


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

B

ABC

D

EFG

H

I

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

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

A

D

C

F

E

H

G

I


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∪{(u1,v1)}

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

得到 T就是最小生成树。

T中共有n-1条边


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


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}


A

定义数组 closeEdge[n]

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

U中点距离为0,

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

5

6

1

B

D

5

5

C

4

6

2

3

E

F

6


template<class T>struct MiniCostEdgeInfo { Tadjvex; 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];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; }


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的所有顶点而无边的非连通图。

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

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

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

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

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

    T 就是最小生成树


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


Kruskal 克鲁斯卡尔算法

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

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

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

问题:

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

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

A

B

D

C

E

F

并查集MFS方法


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

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

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

D

B

A

C

F

G

E

H


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

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

B

A

D

B

A

D

C

E

C

F

G

E

F

G

H

H


双亲表示法

用数组存储树的结点

每个结点中附设一个字段

指示其父结点的位置

D

A

B

C

E

F

G

H


要将AE,CG相连

只要将E的双亲改为0,

G的双亲改为2

D

A

B

C

E

F

G

H


现在要加入边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


双亲表示法结点定义

#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(Titem); int FindRoot(int i); int FindRoot(Titem); void Merge(int root1,int root2);};

并查集类的定义


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


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


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


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


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


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


图上两点间最短距离

4

4

E

A

2

B

4

6

12

6

6

10

8

12

F

D

C

20

14


五、最短路径

两点间边数最少的路径

可用作交通自动咨询系统

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

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

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


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

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

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


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

从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算法

按路径长度递增逐步产生最短路径

设集合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的最短路径的长度。


第一步

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}


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


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}


递归过程:重复第二步

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


迪克斯特拉Dijkstra算法

v5

60

100

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

30

v4

v0

10

20

10

时间复杂性O(n2)

v3

v1

5

50

v2


令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{TstartV, 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);Tsv, 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( );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 }


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


6

v0

v1

4

11

2

3

v2


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


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

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

工作顺序:

ABCDEF

ACDEBF

B

A

F

C

必须无环

构成偏序

D

E


拓扑排序

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

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

算法原理:

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

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

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

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


拓扑排序算法

  • 用一个数组纪录图G的每个顶点的入度

  • 找出第一个入度为0的点输出

  • 找出与之相连的所有顶点,将他们的入度都减1。

  • 重复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

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

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

从源点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


相关定义

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)


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


关键路径的算法

修改拓扑排序算法,增加一个纪录拓扑排序求得的序列。用来逆向计算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; Tu,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;}

}


  • Login