图的概念和关于图的几个算法
2014-07-19 17:07
295 查看
一、图的概念
图是算法中是树的拓展,树是从上向下的数据结构,结点都有一个父结点(根结点除外),从上向下排列。而图没有了父子结点的概念,图中的结点都是平等关系,结果更加复杂。图G=(V, E),其中V代表顶点Vertex,E代表边edge,一条边就是一个定点对(u,v),其中(u,v)∈V。
无向图
有向图
图分有向图和无向图。在无向图中,如果(u,v) (表示u到v的路径)联通,那么(v,u)也联通,例如“1”到“2”联通,“2”到“1”也联通。但是在又向图中“1”到“2”联通,但是“2”到“1”是不联通的。
在图的概念中,除了顶点和边的概念外,经常还涉及到权值,表示一个顶点到另一个顶点的的“代价”,如果顶点不联通,可以认为权值无限大。如果不涉及权值,那么可以认为联通的顶点权值都为1.
在数据结构中,经常用邻接表和邻接矩阵表示图。
1、邻接表
上图即为有向图的邻接表,表中的一个结点对应图中的一个结点,结点后面的链表是与这个结点联通的结点。
//图的结点 typedef struct Node{ char value;// 结点 Node *next;//指向联通结点 }; //邻接表 Node Adj[Num];//Num为图结点个数
邻接表常用语表示稀疏图,即结点的边数|E|远小于|V|^2。
对于有向图,邻接表存储所占空间为|V|+|E|,对于无向图|V|+2|E|,因为每条表在邻接表中出现两次。在存储上占优势,但是在判断两个结点(u,v)是否联通时,要首先在邻接表中找到u,遍历u后面的链表才能判断。
2、邻接矩阵
上图是无向图的邻接矩阵表示。邻接矩阵是一个|V|x|V|的矩阵GMatr,如果(u,v)联通,那么GMatr[u][v]=1。
如果图是加权的话,GMatr[u][v]=权值。
bool GMatr[Num][Num];//Num为图结点个数
可以看出,邻接矩阵的表示方法占得空间为O(V^2),但是在判断两个结点是否联通时,只需O(1)。
当图比较小时更多采用邻接矩阵,因为它更明了。如果图没有加权时,可以用一个二进制位来表示两个图是否联通。
二、图的算法
1、拓扑排序
拓扑排序是应用于有向无环图的一种排序。例如在图中存在u到v的通路(未必联通)那么排序后,u出现在v前面。拓扑排序常常用在有关系的工程之间的排序。例如在一个解决方案中,工程A完工后才可进行工程B工程C,而工程D又依赖于工程B和工程C。
那么排序后可能为A B C D,也可能为A C B D。
首先定义“入度(indgree)”这一概念,v的入度为所有(u,v)联通结点的个数,即可以从多少个结点直接到v。
那么拓扑排序步骤如下:
1、从图找找出任一一个入度为零的结点,依序放到排序队列。
2、在图中删除该结点,并且删除从该结点出发的边。
3、更新其他结点的入度。
重复1-3,直到所有结点都已排序。
代码:
void Topsort() { for(int i=0;i<Num;i++) { Vertex v=findIndegreeZero();//找到任一一个入度为零的点 putVerter(v);//把结点放到排序队列 for each Vertex u adjanct to v//v到w结点联通 w.indgree--;//入度减一 } }
2、图的广度优先遍历
图的广度优先遍历有点像树的层次遍历,是一个分层搜索的过程。假设从v0结点开始遍历,首先遍历与v0结点联通的点w1,w2……,再遍历与w1联通的点u1,u2……,与w2联通的点q1……。在遍历过程中,要注意不要重复遍历一个结点,往往在遍历过一个结点后就对这个结点做标记。
广度优先遍历常常借助队列。步骤如下:
1、把结点v放入队列。标记v
2、若队列为空则结束,否则取出队列头结点u。
3、找出与u联通的结点w1,w2……,若未遍历则遍历,然后标记、入队。转到2。
void BFS() { Vertex v;//最先遍历的结点 v.visit=true;//标记 Q.push(v);//入队 while(!Q.empty()) { Vertex u=pop(); foreach(u的每一个临界点 w) { w.visit=true; Q.push(w); } } }
3、图的深度优先遍历
深度优先遍历是尽可能“深"的遍历图。假设从结点v0开始遍历,遍历与v0联通的且未必遍历过的结点v1,再遍历与v1联通的且未被遍历过的结点v2……。如果遍历到vn后无结点可以遍历,那么退回的哦v(n-1)再去找结点遍历,依次类推。直到图中所有结点都被遍历过。可以看出图的深度优先遍历可以借助堆栈。
1、把结点v放入堆栈。标记v
2、若堆栈为空则结束,否则取出栈顶结点u。
3、找出与u联通的且未被标记的结点w1,w2……,并入栈。转到2。
void DFS() { Vertex v; v.visit=true; S.push(v); while(!S.empty()) { Vertex u=S.pop(); u.visit=true; for(u的每一个邻接点 w and w.visit=false) S.push(w); } }
4、最小生成树
最小生成树应用于无向联通图。生成树是指把图中所有的结点连接起来,任一两个顶点之间有通路。最小是指把所有顶点连接起来的路径的权值的和最小。
构建最小生成树是通过贪心算法来构建,通过局部最优来达到整体最优。
G(V,E)是一个无向联通图,其权值函数为w。
A是最小生成树的子集,初始为空;通过循环迭代,每次往A中加入一条边,且确保加入边后,A仍是最小生成树的子集,那么加入的这条边就叫做“安全边(safe edge)”。直到把所有的结点都加入到A中,循环结束。
Greec-MST(G,w) { A=∅;//空集合 while A don't for a spanning tree do find a edge(u,v) that is safe for A A=A∪{(u,v)}; return A; }在集合A和安全边的选择上,不同的方法形成了两个最小生成树算法:1、Kruskal。2、Prim。
这两个算法都使用二叉堆的话,算法复杂度为O(ElogV),但是如果Prim使用斐波那契堆的话,其算法复杂度可以达到O(E+VlogV)。
1、Kruskal算法
在Kruskal算法中,A是一颗森林,把权值排序选取权值最小的边,若选取的边不形成回路,则为安全边,把它添加的正在生长的森林中。MST-KRUSKAL(G,w) { A=∅;//空集 for each v in V do make-set(v);//把v做成集合 sort the edges of E into nondecreasing order by weight w//安装权值升序排列 for each (u,v) in E, taken in nondecreasing order by weight if Find-Set(u) != Find-Set(v) //Find-Set(v)为找出v所在集合的代表元素 A = A U {(u,v)} Union(u,v) //合并两个集合 return A; }
2、Prim算法
在Prim算法中,A中的边形成单树,每次循环向A中添加一个结点(权值最小的边连接的结点)。在算法实现中用到一个最小优先级队列,不在树中的结点多放在基于权值key的的最小优先级队列Q中,对于结点v来说,key[v]的值是与树A中某一顶点连接的某一条边的最小权值,如果不连接,那么key[v]=∞。MST-PRIM(G,w) { //选取一个顶点v Vertex v; key[v]=0; G=G-{v};//集合中减去v foreach u∈G do key[u]=∞; Q=G;//构造优先级队列 while(Q!=?) do u=EXTRACT-MIN(Q) foreach u∈Adj[v] do if u∈Q and w(v,u)<key[u] key[u]=w(v,u) }
相关文章推荐
- 关于缓冲、缓存等几个易混淆的概念
- WCDMA缩略语 & 几个容易混淆的概念 & 关于掉话原因分析
- 管理会计中关于成本的几个概念
- ARCGIS中关于地图投影和坐标系的几个概念
- 关于J2EE Tranaction的几个基本概念
- 关于移植的几个概念
- 【摄影】关于景深的概念和算法!!!(转)
- 关于功率放大电路的几个基本概念
- 关于AJAX的几个重要概念
- 【摄影】关于景深的概念和算法!!!(转)
- 关于MRP的几个概念
- php5中关于OOP的几个关键概念(属性)
- 关于模块结构的几个重要概念
- 关于J2EE Tranaction的几个基本概念
- 关于MRP的几个概念
- Apache源代码分析——关于模块结构的几个重要概念
- 关于MRP的几个概念
- 关于UML的几个视图的概念!
- 关于图像归一化中用到几个基本概念
- 【摄影】关于景深的概念和算法!!!(转)