1 / 129

第八章 图

第八章 图. 赵建华 南京大学计算机系. 内容. 图的基本概念 图的存储表示 图的遍历与连通性 最小生成树 最短路径 活动网络. 图的基本概念. 定义 : 图是一个二元组: Graph = ( V , E ) 顶点集合 V = { x | x  某个数据对象 } 是有穷非空集合; E = {( x , y ) | x , y  V } 是边 (edge ) 的集合。. 有向图 与无向图. 有向图 顶点对 <x, y> 是有序的。 x 称为 始点 , y 为 终点 。

macey-eaton
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. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第八章 图 赵建华 南京大学计算机系

  2. 内容 • 图的基本概念 • 图的存储表示 • 图的遍历与连通性 • 最小生成树 • 最短路径 • 活动网络

  3. 图的基本概念 • 定义:图是一个二元组: Graph=( V, E ) • 顶点集合V = { x | x 某个数据对象}是有穷非空集合; • E = {(x, y) | x, y V }是边 (edge)的集合。

  4. 有向图与无向图 • 有向图 • 顶点对 <x, y> 是有序的。x称为始点,y为终点。 • <x,y>和<y,x>是不同的边(if x!=y)。 • 无向图 • 顶点对(x, y)是无序的。 • (x, y)和(y,x)是同一条边。

  5. 完全图 • 完全图 • 有n 个顶点的无向图有 n(n-1)/2条边, 则此图为完全无向图。任意两个结点之间有一条边。 • 有n 个顶点的有向图有n(n-1)条边, 则此图为完全有向图。任意两个结点对之间有一条边。 0 0 0 0 1 1 1 2 2 1 4 3 3 5 6 2 2

  6. 0 0 0 0 1 2 1 1 2 2 3 3 3 3 • 邻接顶点 • 如果(u, v) 是 E(G) 中的一条边,则称 u 与 v 互为邻接顶点。 • 子图 • 设有两个图G=(V, E) 和G'=(V', E')。若V ' V 且E'E, 则称图G'是图G的子图。 • 权 • 某些图的边具有与它相关的数, 称之为权。这种带权图叫做网络。 子图

  7. 顶点的度 • 一个顶点v的度是与它相关联的边的条数,记作TD(v)。 • 在有向图中, 顶点的度等于该顶点的入度与出度之和。 • 顶点 v 的入度/出度 • 在有向图中,以v为终点/始点的有向边的条数。记作ID(v)/OD(v)。 • 路径 • 在图 G=(V, E) 中, 若从顶点vi 出发, 沿一些边经过一些顶点vp1,vp2, …,vpm,到达顶点vj。则称顶点序列(vi vp1 vp2 ... vpm vj)为从顶点vi 到顶点 vj 的路径。 • 要求 (vi, vp1)、(vp1, vp2)、...、(vpm, vj) 应是属于E的边。

  8. 路径长度 • 非带权图的路径长度是指此路径上边的条数。 • 带权图的路径长度是指路径上各边的权之和。 • 简单路径 • 若路径上各顶点 v1, v2, ..., vm均不 互相重复, 则称这样的路径为简单路径。 • 回路 • 若路径上第一个顶点 v1 与最后一个顶点vm 重合, 则称这样的路径为回路或环。 0 0 0 1 2 1 2 1 2 3 3 3

  9. 连通图与连通分量 • 在无向图中, 若从顶点v1到顶点v2有路径, 则称顶点v1与v2是连通的。 • 如果图中任意一对顶点都是连通的, 则称此图是连通图。 • 非连通图的极大连通子图叫做连通分量。 • 强连通图与强连通分量 • 在有向图中, 若对于每一对顶点vi和vj, 都存在一条从vi到vj和从vj到vi的路径, 则称此图是强连通图。 • 非强连通图的极大强连通子图叫做强连通分量。 • 生成树 • 一个连通图的生成树是其极小连通子图,在 n 个顶点的情形下,有n-1条边。

  10. 图的抽象数据类型 class Graph { //对象: 由一个顶点的非空集合和一个边集合构成 //每条边由一个顶点对来表示。 public: Graph(); //建立一个空的图 void insertVertex (const T& vertex); //插入一个顶点vertex, 该顶点暂时没有边 void insertEdge (int v1, int v2, int weight); //在图中插入一条边(v1, v2, w) void removeVertex (int v); //在图中删除顶点v,以及所有关联到它的边

  11. void removeEdge (int v1, int v2); //在图中删去边(v1,v2) bool IsEmpty(); //若图中没有顶点, 则返回true, 否则返回false TgetWeight (int v1, int v2); //函数返回边(v1,v2) 的权值 int getFirstNeighbor (int v); //给出顶点 v 第一个邻接顶点的位置 int getNextNeighbor (int v, int w); //给出顶点 v 的某邻接顶点 w 的下一个邻接顶点 };

  12. 图的存储表示 • 图的模板基类 • 模板的数据类型参数表 <class T, class E> , • T是顶点数据的类型,E是边上所附数据的类型。 • 可以表示最复杂的情况:即带权无向图 • 处理非带权图时,边不附带数据,因此数据类型参数表改为 <class T>。 • 如果使用的是有向图,也可以对程序做相应的改动。

  13. 图的模板基类 const int maxWeight = ……; //表示的权值 const int DefaultVertices = 30; //最大顶点数(=n) template <class T, class E> class Graph { //图的类定义 protected: int maxVertices; //图中最大顶点数 int numEdges; //当前边数 int numVertices; //当前顶点数 int getVertexPos (T vertex); //给出顶点vertex在图中位置 public:

  14. … … … //构造函数析构函数 bool GraphEmpty () const //判图空否 int NumberOfVertices () ; //返回当前顶点数 int NumberOfEdges (); //返回当前边数 virtual TgetValue (int i); //取第i个顶点的值 virtual EgetWeight (int v1, int v2); //取边上权值 //取顶点 v 的第一个邻接顶点以及下一个邻接结点 virtual int getFirstNeighbor (int v); virtual int getNextNeighbor (int v, int w); //结点和边的操作 virtual bool insertVertex (const T vertex); virtual bool insertEdge (int v1, int v2, E cost); virtual bool removeVertex (int v); virtual bool removeEdge (int v1, int v2); };

  15. 邻接矩阵 (Adjacency Matrix) • 图的邻接矩阵表示包含 • 一个记录各个顶点信息的顶点表(表示集合V) • 一个表示各个顶点之间关系的邻接矩阵(表示集合E) • 设图 A = (V, E) 是一个有 n个顶点的图, 图的邻接矩阵是一个二维数组 A.edge[n][n]: 注意:edge可以看作是二维的bitSet表示方法 如果图是无向的,那么必然是对称矩阵

  16. 0 1 2 3 0 1 2 • 无向图的邻接矩阵是对称的; • 有向图的邻接矩阵可能是不对称的。

  17. 有向图:第i行中1的个数就是顶点 i的出度,第 j 列中1的个数就是顶点 j的入度。 • 无向图:第 i行 (列)中1的个数就是顶点i的度。 (注意,无向图的邻接矩阵是对称的)

  18. 8 2 3 6 9 3 2 5 4 0 1 1 网络(带权图)的邻接矩阵 例如:

  19. 用邻接矩阵表示的图的类定义 template <class T, class E> class Graphmtx : public Graph<T, E> { … … … … //输入输出友元 private: T * VerticesList; //顶点表 E **Edge; //邻接矩阵 int getVertexPos (T vertex); //给出顶点vertex在图中的位置

  20. public: … … … … //构造函数与析构函数的声明 T*getValue (int i); //获取第i个顶点的值, //i 不合理则返回NULL; EgetWeight (int v1, int v2); //取边(v1,v2)上权值 //取顶点 v 的第一个/下一个邻接顶点,用于便利邻接顶点 int getFirstNeighbor (int v); int getNextNeighbor (int v, int w); //插入/删除结点和边 bool insertVertex (const T vertex); bool insertEdge (int v1, int v2, E cost); bool removeVertex (int v); bool removeEdge (int v1, int v2); };

  21. template <class T, class E> Graphmtx<T, E>::Graphmtx (int sz) { //构造函数 maxVertices = sz; numVertices = 0; numEdges = 0; int i, j; VerticesList = new T[maxVertices]; //创建顶点表 //申请邻接矩阵的空间,已经默认E为int Edge = (int **) new int *[maxVertices]; for (i = 0; i < maxVertices; i++) Edge[i] = new int[maxVertices]; //矩阵初始化,按照带权图的方式初始化,不存在任何边 for (i = 0; i < maxVertices; i++) for (j = 0; j < maxVertices; j++) Edge[i][j] = (i == j)?0 : maxWeight; };

  22. template <class T, class E> int Graphmtx<T, E>::getFirstNeighbor (int v) { //给出顶点位置为v的第一个邻接顶点的位置, //如果找不到, 则函数返回-1 if (v != -1) { for (int col = 0; col < numVertices; col++) if (Edge[v][col] && Edge[v][col] < maxWeight) return col; } return -1; };

  23. template <class T, class E> int Graphmtx<T, E>::getNextNeighbor (int v, int w) { //给出顶点 v 的某邻接顶点 w 的下一个邻接顶点 if (v != -1 && w != -1) { for (int col = w+1; col < numVertices; col++) if (Edge[v][col] && Edge[v][col] < maxWeight) return col; } return -1; }; 每次调用时,使用上一次的位置w作为参数

  24. 邻接表 (Adjacency List) • 邻接矩阵的改进形式 • 把邻接矩阵的各行分别组织为一个单链表。 • 同一个顶点发出的边链接在同一个边链表中; • 每一个链结点代表一条边(边结点)。结点中有另一顶点的下标dest和指针link。 • 对于带权图,边结点中还保存权值cost。 • 顶点表: • 第 i 个顶点中保存该顶点的数据,以及它对应边链表的头指针adj。 边集E按照始结点,分为n个集合;每个集合用单链表表示

  25. data adj dest link dest link  A 0 1 2 3 A B C D 1 3  0 2 B C  1 D  0 无向图的邻接表 • 统计某顶点对应边链表中结点个数,可得该顶点的度。 • 某条边(vi, vj)在邻接表中有两个边结点,分别在第 i 个顶点和第 j 个顶点对应的边链表中。

  26. A data adj dest link  0 1 2 A B C 1 dest link  B 0 2  邻接表 (出边表) C data adj dest link  0 1 2 1 A B C  0  1 逆邻接表 (入边表) 有向图的邻接表和逆邻接表

  27. 6 A D data adj dest cost link 9  0 1 2 3 15 36 A B C D 5 2  28 B C 8  32  19 (出边表) (顶点表) 网络 (带权图) 的邻接表 • 统计出边表中结点个数,得到该顶点的出度; • 统计入边表中结点个数,得到该顶点的入度。

  28. 在邻接表的边链表中,各个边结点的链入顺序任意,视边结点输入次序而定。在邻接表的边链表中,各个边结点的链入顺序任意,视边结点输入次序而定。 • 设图中有 n 个顶点,e 条边, • 表示无向图时需要 n 个顶点结点,2e 个边结点; • 表示有向图时,(不考虑逆邻接表)需要 n 个顶点结点,e 个边结点。 • 当 e远远小于 n2 时,可以节省大量的存储空间。 • 把同一个顶点的所有边链接在一个单链表中,也使得某些操作更为便捷。(firstNeighbor,nextNeighbor)

  29. 用邻接表表示的图的类定义 template <class T, class E> struct Edge { //边链表结点的定义 intdest; //边的另一顶点位置 Ecost; //边上的权值 Edge<T, E> *link; //下一条边链指针 Edge () {} //构造函数 Edge (int num, E cost)//构造函数 : dest (num), weight (cost), link (NULL) { } bool operator != (Edge<T, E>& R) const { return dest != R.dest; } //判边等否 };

  30. template <class T, class E> struct Vertex { //顶点的定义 T data; //顶点的名字 Edge<T, E> *adj; //边链表的头指针 };

  31. template <class T, class E> class Graphlnk : public Graph<T, E> { //图的类定义 friend istream& operator >> (istream& in, Graphlnk<T, E>& G); //输入 friend ostream& operator << (ostream& out, Graphlnk<T, E>& G); //输出 private: Vertex<T, E> *NodeTable; //顶点表 (各边链表的头结点) //顶点的个数存放在numVertices,从基类继承 int getVertexPos (const T vertx) { //给出顶点vertex在图中的位置 for (int i = 0; i < numVertices; i++) if (NodeTable[i].data == vertx) return i; return -1; }

  32. public: Graphlnk (int sz = DefaultVertices); //构造函数 ~Graphlnk(); //析构函数 //获取信息的接口 TgetValue (int i);//取第i个顶点的值; EgetWeight (int v1, int v2); //取边(v1,v2)权值 //结点和边的操作接口 bool insertVertex (const T& vertex); bool removeVertex (int v); boolinsertEdge (int v1, int v2, E cost); bool removeEdge (int v1, int v2); //遍历邻接结点的接口 int getFirstNeighbor (int v); int getNextNeighbor (int v, int w); };

  33. template <class T, class E> Graphlnk<T, E>::Graphlnk (int sz) { //建立一个空的邻接表 maxVertices = sz; numVertices = 0; numEdges = 0; //创建顶点表数组 NodeTable = new Vertex<T, E>[maxVertices]; if (NodeTable == NULL) { cerr << "存储分配错!" << endl; exit(1); } //初始化边集合(空集) for (int i = 0; i < maxVertices; i++) NodeTable[i].adj = NULL; };

  34. template <class T, class E> Graphlnk<T, E>::~Graphlnk() { //析构函数:删除一个邻接表 //删除所有顶点的边 for (int i = 0; i < numVertices; i++ ) { Edge<T, E> *p = NodeTable[i].adj; //删除第i个顶点的边链表中的所有结点(边的信息) while (p != NULL) { NodeTable[i].adj = p->link; delete p; p = NodeTable[i].adj; } } //删除顶点表数组 delete [ ]NodeTable; };

  35. template <class T, class E> int Graphlnk<T, E>::getFirstNeighbor (int v) { //给出顶点位置为v 的第一个邻接顶点的位置, //如果找不到, 则函数返回-1 //if(v>=0 && v<numVertice) //应该使用这个判断 if (v != -1) { //顶点v存在 Edge<T, E> *p = NodeTable[v].adj; //对应边链表第一个边结点 if (p != NULL) return p->dest; //存在, 返回第一个邻接顶点 } return -1; //第一个邻接顶点不存在 };

  36. template <class T, class E> int Graphlnk<T, E>::getNextNeighbor (int v, int w) { //给出顶点v的邻接顶点w的下一个邻接顶点的位置, //若没有下一个邻接顶点, 则函数返回-1 if (v != -1) { //顶点v存在 Edge<T, E> *p =NodeTable[v].adj; //寻找当前的边;比较低效 while (p != NULL && p->dest !=w) p = p->link; //p==NULL表示w不是v的邻接顶点 //p->link == NULL表示w是最后一个邻接顶点 if (p != NULL && p->link != NULL) return p->link->dest; //返回下一个邻接顶点 } return -1; //下一邻接顶点不存在 }; 在遍历一个顶点的所有边时,可以保存指向当前链表结点的指针。这样寻找下一个邻接顶点时效率更高一些。

  37. template <class T, class E> E graphlnk<T,E>::getWeight(int v1, int v2) { //返回边(v1,v2)上的权值;无边则返回0; if(v1 != -1 && v2 != -1) { //寻找从v1到v2的边; Edge<T,E> *p = NodeTable[v1].adj; //第一条边 while(p!=NULL && p->dest !=v2) p = p->link; //下一条边 if(p!=NULL) return p->cost; } return 0; }

  38. template <class T, class E> E graphlnk<T,E>::insertVertex(const T& vertex) { //插入顶点vertex。成功返回true,失败返回false if (numVertice == maxVertices) return false;//满 //增加一个顶点:插入到最后,计数器加一。 NodeTable[numVertices].data = vertex; numVertices ++; return true; }

  39. template <class T, class E> E graphlnk<T,E>::removeVertex(int v) //删除第v个顶点。成功则返回true,否则返回false; if(v<0 || v>= numVertices) return false; //不合法的顶点号; //下面要做两件事情 //1、删除所有进入v的边;如果是无向图,那么 // 是记录在其它边链表中的所有和v相连的边 //2、删除所有从v出发的边; //3、消除结点的记录,并填补结点的空缺。 // 并修改边中的信息。

  40. 对于有向图,应该分两步删除和v相连的边: 1、删除从v出发的边;2、删除到达v的边; 第一步遍历顶点v的链表,第二步遍历所有链表 //删除和v相连的边 Edge<T,E> *p, *s, *t; int i,k; while(NodeTable[v].adj != NULL) { //删除第v个边链表中所有的边; p = NodeTable[v].adj; k=p->dest; s = NodeTable[k].adj; t = NULL; //寻找和p所指结点对称的边;t总是指向s的前一个结点 while(s!=NULL && s->dest != v) { t=s; s=s->link;} if(s != NULL){ //找到了对称边,删除 if(t==NULL)NodeTable[k].adj=s->link; //对称边是首结点; else t->link = s->link; delete s; } NodeTable[v].adj = p->link; //清除顶点v的边链表结点; delete p; numEdges--; }

  41. numVertices --; //删除顶点,将最后的顶点移动到v处 NodeTable[v].data = NodelTable[numVertices].data; p = NodeTable[v].adj = NodeTable[numVertices].adj; while(p!=NULL){ s = NodeTable[p->dest].adj; //原最后一个结点的边; while(s!=NULL) //原来指向最后一个结点的边现在应该指向v; if(s->dest == numVertices){s->dest = v; break;} else s=s->link; } return true; };//end of method; 这里处理的同样是无向图。所以我们可以只修改p的邻接顶点上的边的信息。 处理有向图时需要遍历所有的边链表。 需要调整的原因是:在边里面用下标索引指示结点。现在因为结点的移动导致结点的下标出现变化

  42. mark vertex1 vertex2 path1 path2 邻接多重表 (Adjacency Multilist) • 邻接多重表可以方便地处理图的边 • 每一条边只有一个边结点。结点的结构如下: • Mark:标记域; • vertex1,vertex2存放顶点; • path1,path2:分别指向对应于vertex1和vertex2的边链表; • 还可以增加字段,存放其它和边相关的信息。

  43. data Firstout 无向图的邻接多重表表示 • 顶点的数据结构 • data 存放与该顶点相关的信息,Firstout是指示第一条依附该顶点的边的指针。 • 顶点的数据可以存放在一个顺序表中。 • 和每个顶点相连的边组成一个链表 • Firstout指向第一条边 • 顶点v的边curEdge的下一条边是: (v==curEdge->vertext1)? path1 : path2;

  44. data Fout mark vtx1 vtx2 path1 path2 0 A A e1 e1 0 1 e3 1 B B D e2 e2   0 2 2 C C e3   1 3 3 D 邻接多重表的例子

  45. mark vertex1 vertex2 path1 path2 有向图的邻接多重表 • 邻接多重表(十字链表)可以同时表示邻接表和逆邻接表(即从一个顶点离开的边和到达一个顶点的边)。 • 边结点的结构 • mark:处理标记; • vertex1和vertex2:始顶点和终顶点 • path1指向始顶点与该边相同的下一条边的指针; • path2是指向终顶点与该边相同的下一条边的指针。

  46. data Firstin Firstout • 顶点结点的结构 • 顶点的数据可以存放在一个顺序表中。 • data:存放与该顶点相关的信息; • Firstin 指示以该顶点为始顶点的出边表的第一条边, • Firstout 指示以该顶点为终顶点的入边表的第一条边。

  47. data Fin Fout mark vtx1 vtx2 path1 path2 e6 e1  0 1 0 A E A e2  0 3 e5 1 e2 B e1 D 2   C 1 2 e4 e3 e3 B C 3 e4 D   2 3 4 E e5   3 4 e6   4 0 邻接多重表的结构

  48. 图的遍历与连通性 • 从给定连通图中某一顶点出发,沿着一些边访遍图中所有的顶点,且使每个顶点仅被访问一次,就叫做图的遍历 (Graph Traversal)。 • 图中可能存在回路,因此可能沿着某些边多次到达一个顶点。 • 为避免重复访问,设置一个辅助数组visited [ ] • visited[i]==0表示i尚未被访问; • visited[i]==1表示i已经被访问; • 遍历时,只访问visited[i]==0的顶点;访问顶点 i后立即将visited[i]设置为 1。

  49. 图遍历的种类 • 深度优先搜索DFS (Depth First Search) • 从起始结点开始,不断选择当前结点的相邻结点向前搜索。如果某个结点的所有相邻结点都被探索过,就回溯。 • 广度优先搜索BFS (Breadth First Search) • 从起始结点开始,首先访问当前结点的所有相邻结点;然后访问这些相邻结点的相邻结点;… • 在深度/广度优先搜索的过程中访问到的顶点和边组成深度/广度优先生成树。

  50. 1 2 1 2 3 3 A A B B E E 7 7 5 4 5 4 G G D C D C 6 6 H I H I F F 8 9 8 9 深度优先搜索DFS示例 前进 回退 深度优先搜索过程 深度优先生成树

More Related