600 likes | 738 Views
第 12 章 图. 主要内容. 图的定义 图的遍历 拓扑排序 贪心算法 : 最短路径 最小生成树. 图 : 定义. 图 G 由集合 v 个 V 的中不同顶点对的集合 E 组成 ,V 的成员称为图 G 的顶点 这些顶点对称为 G 的边 . 如果 e=(v,w) 是顶点 v 和 w 组成的边 , 则说 v 和 w 依赖于 e, 而说 e 关联于 v 和 w. 如果顶点对是无序的 , 则称 G 为无向图 . 如果此顶点对是有序的 , 则称 G 为有向图 . 有向图这个术语在英文中表示经常简化为 digraph, 当不带限制条件时 , 图这个术语通常指无向图.
E N D
主要内容 • 图的定义 • 图的遍历 • 拓扑排序 • 贪心算法:最短路径 • 最小生成树
图:定义 • 图G由集合v个V的中不同顶点对的集合E组成,V的成员称为图G的顶点 • 这些顶点对称为G的边. • 如果e=(v,w) 是顶点v和w组成的边,则说v和w依赖于e,而说e关联于v和w. • 如果顶点对是无序的,则称G为无向图. • 如果此顶点对是有序的,则称G为有向图.有向图这个术语在英文中表示经常简化为digraph,当不带限制条件时,图这个术语通常指无向图.
图:定义 • 对无向图中的两个顶点,如果从一个顶点到另一个顶点存在一条边,则称这两个顶点是邻接的. • 路径是由不同顶点构造的一个序列,序列中的每个顶点都与下一个顶点邻接. • 环是一条至少包含3个顶点的路径,路径上最后一个顶点与第一个顶点邻接. • 如果从任一个顶点到任一个其他顶点之间存在一条路径,则称这个图是连通的.
图:定义 • 自由树定义为没有环的连通无向图. • 在有向图中,要求所有的边或环具有同样的方向,因此沿着一条边或环意味着总是沿着箭头指示的方向移动.这样的路径称为有向路径(环). • 如果从任一个顶点到另一个其他顶点之间存在一条有向路径,则称有向图是强连通的.如果不考虑边的方向且结果得到的无向图是连通的,则称有向图是弱连通的. • 顶点的度是连接到此顶点的边的个数, 也是其邻接顶点的个数.
图:定义 • 有向图的集合表示 • 定义 :有向图G 由集合 V 和对所有v2V,V的子集A,其中V称为G的顶点集,Av称为与v邻接的顶点集. • 集合作为布尔数组: template <int max_set> struct Set { bool is_element[max_set]; };
图:定义 • 有向图的集合表示 • 作为数组的集合 template <int max_size> class Digraph { int count; //number of vertices, at most max_size Set<max_size> neighbors[max_size]; }; • 作为邻接表格 template <int max_size> class Digraph { int count; //number of vertices, at most max_size bool adjacency[max_size][max_size]; };
图:定义 • 基于表的实现 typedef int Vertex; template <int max_size> class Digraph { int count; //number of vertices, at most max_size List<Vertex> neighbors[max_size]; };
图:定义 • 基于表的实现 class Edge; //forward declaration(向前声明) class Vertex { Edge *first_edge; // start of the adjacency list Vertex *next_vertex; // next vertex on the linked list }; class Edge { Vertex *end_point; // vertex to which the edge points Edge *next_edge; // next edge on the adjacency list }; class Digraph { Vertex *first_vertex; // header for the list of vertices };
图遍历 • 图的深度有序遍历大致类似于有序树的前序遍历.假设遍历刚刚访问过顶点v,设 w1;w2……wk 是与v邻接的顶点. 我们接下来将访问 w1 并使 w2……wk 等待. 在访问完 w1之后 , 我们在返回去遍历 w2; ……;wk 前,首先遍历所有与w1相邻的顶点. • 图的广度优先遍历大致类似于有序树的层次遍历.如果遍历刚刚访问过顶点v,则它接下来会访问所有与v邻接的顶点,并将与这些顶点邻接的顶点放入等待表中,在访问完所有与v邻接的顶点后再遍历它们.
图遍历 • 深度优先算法 template <int max_size> void Digraph<max_size> :: depth_first(void (*visit)(Vertex &)) const /* Post: The function *visit has been performed at each vertex of the Digraph in depth-first order. Uses: Method traverse to produce the recursive depth-first order. */ { bool visited[max_size]; Vertex v; for (all v in G) visited[v] = false; for (all v in G) if (!visited[v]) traverse(v, visited, visit); }
图遍历 • 深度优先算法 • The recursion is performed in an auxiliary function traverse. template <int max_size> void Digraph<max_size> :: traverse(Vertex &v, bool visited[], void (*visit)(Vertex &)) const /* Pre: v is a vertex of the Digraph . Post: The depth-first traversal, using function*visit , has been completed for v and for all vertices that can be reached from v . Uses: traverse recursively. */
图遍历 • 深度优先算法 { Vertex w; visited[v] = true; (*visit)(v); for (all w adjacent to v) if (!visited[w]) traverse(w, visited, visit); }
图遍历 • 广度优先算法 template <int max_size> void Digraph<max_size> :: breadth_first(void (*visit)(Vertex &)) const /* Post: The function *visit has been performed at each vertex of the Digraph in breadth-first order. Uses: Methods of class Queue . */ { Queue q; bool visited[max_size]; Vertex v, w, x; for (all v in G) visited[v] = false; for (all v in G) if (!visited[v]) { q.append(v);
图遍历 • 广度优先算法 while (!q.empty( )){ q.retrieve(w); if (!visited[w]) { visited[w] = true; (*visit)(w); for (all x adjacent to w) q.append(x); } q.serve( ); } } }
拓扑排序 • 如果G是一个无有向环的有向图,则G的拓扑排序是G中所有顶点的一个顺序排列,使得对所有的顶点v,w 2 G,如果有一条从v到w的边,则v在顺序排列中先于w.
拓扑排序 typedef int Vertex; template <int graph_size> class Digraph { public: Digraph( ); void read( ); void write( ); // methods to do a topological sort void depth_sort(List<Vertex> &topological_order); void breadth_sort(List<Vertex> &topological_order); private: int count; List <Vertex> neighbors[graph_size]; void recursive_depth_sort(Vertex v, bool visited[], List<Vertex> &topological_order); };
拓扑排序 • 深度优先算法 template <int graph_size> void Digraph<graph_size> ::depth_sort(List<Vertex> &topological_order) /* Post: The vertices of the Digraph are placed into List topological_order with adepth-first traversal of those vertices that do not belong to a cycle. Uses: Methods of class List , and function recursive_depth_sort to perform depth-first traversal. */
拓扑排序 { bool visited[graph_size]; Vertex v; for (v = 0; v < count; v++) visited[v] = false; Topological_order.clear( ); for (v = 0; v < count; v++) if (!visited[v]) // Add v and its successors into topological order. recursive_depth_sort(v, visited, topological_order); }
拓扑排序 template <int graph_size> void Digraph<graph_size> :: recursive_depth_sort(Vertex v, bool *visited, List<Vertex> &topological_order) /* Pre: Vertex v of the Digraph does not belong to the partially completed List topological_order . Post: All the successors of v and finally v itself are added to topological order with a depth-first search. Uses: Methods of class List and the function recursive_depth_sort . */
拓扑排序 { visited[v] = true; int degree = neighbors[v].size( ); for (int i = 0; i < degree; i++) { Vertex w; //A (neighboring) successor of v neighbors[v].retrieve(i, w); if (!visited[w]) // Order the successors of w. recursive_depth_sort(w, visited, topological_order); } topological_order.insert(0, v); // Put v into topological_order . }
拓扑排序 • 广度优先算法 template <int graph_size> void Digraph<graph_size> :: breadth_sort(List<Vertex> &topological_order) /* Post: The vertices of the Digraph are arranged into the List topological_order which is found with a breadth-first traversal of those vertices that do not belong to a cycle. Uses: Methods of classes Queue and List . */
拓扑排序 { topological_order.clear( ); Vertex v, w; int predecessor_count[graph size]; for (v = 0; v < count; v++) predecessor_count[v] = 0; for (v = 0; v < count; v++) for (int i = 0; i < neighbors[v].size( ); i++) { neighbors[v].retrieve(i, w); // Loop over all edges v-w. predecessor_count[w]++; } Queue ready_to_process; for (v = 0; v < count; v++) if (predecessor_count[v] == 0) ready_to_process.append(v);
拓扑排序 while (!ready_to_process.empty( )) { ready_to_process.retrieve(v); topological_order.insert(topological_order.size( ), v); for (int j = 0; j < neighbors[v].size( ); j++) { neighbors[v].retrieve(j, w); // Traverse successors of v . predecessor_count[w]--; if (predecessor_count[w] == 0) ready_to_process.append(w); } ready_to_process.serve( ); } }
贪心算法: 最短路径 • 最短路径的问题 • 给定有向图G,它的每条边附有一个非负的权,找到一个从顶点v到另一个顶点w的路径,使得路径上的权的和尽可能的小.
贪心算法: 最短路径 • 方法 • 算法运行时将维持从原点出发且最短距离已知的顶点集合S.最初S中仅有一个源点,接下来每一步,都会从剩下的顶点中找到一个顶点,使得从源点出发到该点的路径最短,然后将它加到S中.这个问题要决定的是每一步将哪个顶点加到S中.我们认为S中的顶点都标上某种颜色,且认为组成从源点出发到这些顶点的最短路径上的边也已着色.
贪心算法: 最短路径 • 寻找最短路径 • 我们选择记录在表格distance中的具有最小距离的顶点v时运用了贪心准则.对这个顶点v,必须证明记录在diatance中的距离确实是从源点到v的最短路径的长度.假设从源点到v有一条更改的路径,这条路径先是从S出发到某个顶点x,然后继续到v.如果这条路径比到v的着色路径更短,则从源点到x的初始路径也更短,于是有 distance[x] < distance[v].也因此贪心策略将选择x而不是v作为下一个加入S的顶点.
贪心算法: 最短路径 • 当将c加到S时,我们认为v现在已被着色,同时从源点到v的最短路径也已着色.接下来必须对不在S中的每个顶点w检查通过v然后直接到w的一条路径是否比先前记录到w的距离更短,从而更新整个distance.
贪心算法: 最短路径 • 例子
贪心算法: 最短路径 • 例子
贪心算法: 最短路径 template <class Weight, int graph_size> class Digraph { public: // Add a constructor and methods for Digraph input and output. void set_distances(Vertex source, Weight distance[]) const; protected: int count; Weight adjacency[graph_size][graph_size]; };
贪心算法: 最短路径 template <class Weight, int graph size> void Digraph<Weight, graph_size> :: set_distances(Vertex source, Weight distance[]) const /* Post: The array distance gives the minimal path weight from vertex source to each vertex of the Digraph . */ { Vertex v, w; bool found[graph_size]; // Vertices found in S for (v = 0; v < count; v++) { found[v] = false; distance[v] = adjacency[source][v]; }
贪心算法: 最短路径 found[source] = true; // Initialize with vertex source alone in the set S. distance[source] = 0; for (int i = 0; i < count; i++) { // Add one vertex v to S on each pass. Weight min = infinity; for (w = 0; w < count; w++) if (!found[w]) if (distance[w] < min) { v = w; min = distance[w]; } found[v] = true; for (w = 0; w < count; w++) if (!found[w]) if (min C adjacency[v][w] < distance[w]) distance[w] = min + adjacency[v][w]; } }
最小生成树 • 问题 • 最短路径的算法的应用没有改变网络和图
最小生成树 • 问题 • 如果初始网络基于连通图G,则从特定源点出发的最短路径就将这个源点和图G中所有其他的顶点连接了起来.如果将计算过的最短路径合并到一起,我们得到一棵树,我们得到了一棵树,这棵树连接了G的所有顶点. • 定义:连通网络的最小生成树是其边的权值总和尽可能小的生成树.
最小生成树 • Prim 算法 • 我们选择一个称为源点的顶点开始,当我们用这个方法继续找出生成树时,我们保持了一个顶点集X,X中的顶点到源点的路径可以在我们正在建立的最小生成树中找到.我们需要跟踪边集Y,Y中的边连接了正在构造的树中的X的顶点.因此经过一定的时间,可以想象X中的顶点和Y中的边构成了一棵小树,它生长成最终的生成树. • 最初,源点是X中唯一的顶点,边集Y为空.在每一步,我们都将另一个顶点加入X;这个顶点的选择要回答X的一条边有尽可能小的权值,这条回到X的最小边要加入Y.
最小生成树 • Prim算法 • 当我们开始实现prim算法时,我们将维持一个属于X的顶点表,将它们作为布尔数组component中的元素.我们可以方便的像存储图的边那样去存储y的边,那个图将通过我们的程序生长成一棵输出树.
最小生成树 • Prim算法 的实现 template <class Weight, int graph_size> class Network: public Digraph<Weight, graph_size> { public: Network( ); void read( ); //overridden method to enter a Network void make_empty(int size = 0); void add_edge(Vertex v, Vertex w, Weight x); void minimal_spanning(Vertex source, Network<Weight, graph_size> &tree) const; };