图的概念和关于图的几个算法

来源:互联网 发布:淘宝大c香港站是正品吗 编辑:程序博客网 时间:2024/05/01 12:00

一、图的概念

图是算法中是树的拓展,树是从上向下的数据结构,结点都有一个父结点(根结点除外),从上向下排列。而图没有了父子结点的概念,图中的结点都是平等关系,结果更加复杂。

图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 treedo find a edge(u,v) that is safe for AA=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){//选取一个顶点vVertex v;key[v]=0;G=G-{v};//集合中减去vforeach u∈Gdo 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)}





0 0
原创粉丝点击