图的算法
1 图的遍历
图的遍历就是从图中某个顶点出发,按某种方法对图中所有顶点访问且仅访问一次。
遍历算法是求解图的连通性问题、拓扑排序和求关键路径等算法的基础。
2 深度优先遍历
从图中某个顶点V 出发,访问此顶点,然后依次从V的各个未被访问的邻接点出发
深度优先搜索遍历图,直至图中所有和V有路径相通的顶点都被访问到。 若此时图中
善有顶点未被访问,则另选图中一个未被访问的顶点作为起始点,重复上述过程,直至
图中所有顶点都被访问到为止。
对于从某个顶点v出发的深度优先遍历过程其实是一个递归的遍历过程。
2.1 递归遍历
//对图进行深度优先遍历
public Iterator DFSTraverse(Vertex v) {
LinkedList traverseSeq = new LinkedListDLNode();//需返回的遍历结果
resetVexStatus();//重置所有顶点状态为未访问
DFSRecursion(v, traverseSeq);//从v点出发深度优先搜索
Iterator it = getVertex();//从图的所有顶点
for(it.first(); !it.isDone(); it.next()){
Vertex u = (Vertex)it.currentItem();
//如果u未被访问,从u点出发深度优先搜索
if (!u.isVisited()) DFSRecursion(u, traverseSeq);
}
//返回访问结果
return traverseSeq.elements();
}
//深度优先的递归算法
private void DFSRecursion(Vertex v, LinkedList list){
v.setToVisited();//设置顶点访问状态为 已访问
list.insertLast(v);//将顶点加入到访问结果集中
Iterator it = adjVertexs(v);//取得顶点v的所有邻接点
for(it.first(); !it.isDone(); it.next()){
Vertex u = (Vertex)it.currentItem();
//如果u未被访问,从u点出发深度优先搜索
if (!u.isVisited()) DFSRecursion(u,list);
}
}
2.2 非递归遍历
//对图进行深度优先遍历
public Iterator DFSTraverse(Vertex v) {
LinkedList traverseSeq = new LinkedListDLNode();//需返回的遍历结果
resetVexStatus();//重置所有顶点状态为未访问
DFS(v, traverseSeq);//从v点出发深度优先搜索
Iterator it = getVertex();//从图的所有顶点
for(it.first(); !it.isDone(); it.next()){
Vertex u = (Vertex)it.currentItem();
//如果u未被访问,从u点出发深度优先搜索
if (!u.isVisited()) DFS(u, traverseSeq);
}
//返回访问结果
return traverseSeq.elements();
}
//深度优先的非递归算法
private void DFS(Vertex v, LinkedList list){
Stack s = new StackSLinked();
s.push(v);
while (!s.isEmpty()){
Vertex u = (Vertex)s.pop();//从未访问顶点栈中弹出一个顶点
if (!u.isVisited()){
u.setToVisited();
list.insertLast(u);
Iterator it = adjVertexs(u);//取得顶点v的所有邻接点
for(it.first(); !it.isDone(); it.next()){
Vertex adj = (Vertex)it.currentItem();
if (!adj.isVisited()) s.push(adj);//将为访问的顶点放入栈中 等待处理
}
}//if
}//while
}
3 广度优先遍历
从图中的某个顶点V出发,并在访问此顶点之后依次访问V的所有未被访问过的邻接点,
之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V有路径相
通的顶点都被访问到。
若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上
述过程,直至图中所有顶点都被访问到为止。
//对图进行广度优先遍历
public Iterator BFSTraverse(Vertex v) {
LinkedList traverseSeq = new LinkedListDLNode();//需返回的遍历结果
resetVexStatus();//重置所有顶点状态为未访问
BFS(v, traverseSeq);//从v点出发广度优先搜索
Iterator it = getVertex();//获取图中所有顶点
for(it.first(); !it.isDone(); it.next()){
Vertex u = (Vertex)it.currentItem();
if (!u.isVisited()) BFS(u, traverseSeq);
}
return traverseSeq.elements();
}
//广度优先遍历算法
private void BFS(Vertex v, LinkedList list){
//按这些顶点被访问的先后次序依次访问它们的邻接点,因而采用队列
Queue q = new QueueSLinked();
v.setToVisited();
list.insertLast(v);//将顶点加入到访问结果集中
q.enqueue(v);//入队
while (!q.isEmpty()){
Vertex u = (Vertex)q.dequeue();//出队
Iterator it = adjVertexs(u);//取得顶点v的所有邻接点
for(it.first(); !it.isDone(); it.next()){
Vertex adj = (Vertex)it.currentItem();
if (!adj.isVisited()){
adj.setToVisited();
list.insertLast(adj);
q.enqueue(adj);
}//if
}//for
}//while
}
4 连通图的最小生成树
假设要在 n 个城市之间建立通讯联络网,则连通 n 个城市只需要修建 n-1条线路,
如何在最节省经费的前提下建立这个通讯网?
4.1 算法一:Prim(普里姆算法)
思路:取图中任意一个顶点 v 作为生成树的根,之后往生成树上添加新的顶点 w。在添加的顶点
w 和已经在生成树上的顶点v 之间必定存在一条边,并且该边的权值在所有连通顶点 v 和 w 之间的边
中取值最小。之后继续往生成树上添加顶点,直至生成树上含有 n-1 个顶点为止。
思路的另一种表示方式:从只含有一个顶点的集合开始,寻找集合外面的顶点到这个集合里的顶点最近的一条边,然后将这个顶点加入集合,修改因为这个顶点的加入而使得集合外面的顶点到集合里的顶点的最短距离产生变化的分量。因为需要对每个顶点扫描,邻接矩阵储存的图是最合适Prim算法的。
一般情况下所添加的顶点应满足下列条件:
在生成树的构造过程中,图中 n 个顶点分属两个集合:已落在生成树上的顶点集 U 和尚未
落在生成树上的顶点集V-U ,则应在所有连通U中顶点和V-U中顶点的边中选取权值最小的边。
具体的实现中:S到(V-S)中各顶点的轻边 由该点的成员变量application表示,此时
application指向Edge类的对象,最小生成树的表示--采用设置图中 边的类型 来完成。
//获取到达顶点v的最小边(轻边)的权值
protected int getCrossWeight(Vertex v){
if (getCrossEdge(v)!=null) return getCrossEdge(v).getWeight();
else return Integer.MAX_VALUE;//表示无穷大,不相邻
}
//获取轻边(权最小的边)
protected Edge getCrossEdge(Vertex v){ return (Edge)v.getAppObj();}
//设置轻边
protected void setCrossEdge(Vertex v, Edge e){ v.setAppObj(e);}
//求无向图的最小生成树,如果是有向图不支持此操作
//前提是无向图是连通图,算法不判断图的连通性
public void generateMST(){
resetVexStatus();//重置图中各顶点的状态未未访问
resetEdgeType();//重置图中各边的类型为normal
Iterator it = getVertex();//获取所有的顶点集合
Vertex v = (Vertex)it.currentItem();//选第一个顶点作为起点
v.setToVisited();//顶点v进入集合S,以visited=true表示属于S,否则不属于S
//初始化顶点集合S到V-S各顶点的边
for(it.first(); !it.isDone(); it.next()){
Vertex u = (Vertex)it.currentItem();
Edge e = edgeFromTo(v,u);//返回从v指向u的边
setCrossEdge(u,e); //设置到达V-S中顶点u的边
}
for (int t=1;t<getVexNum();t++){ //进行|V|-1次循环找到|V|-1条边
Vertex k = selectMinVertex(it);//中间顶点k
k.setToVisited(); //顶点k加入S
Edge mst = getCrossEdge(k); //割(S , V - S) 的轻边
if (mst!=null) mst.setToMST(); //将边加入MST
//以k为中间顶点修改S到V-S中顶点的最短横切边
Iterator adjIt = adjVertexs(k); //取出k的所有邻接点
for(adjIt.first(); !adjIt.isDone(); adjIt.next()){
Vertex adjV = (Vertex)adjIt.currentItem();
Edge e = this.edgeFromTo(k,adjV);
if (e.getWeight()<getCrossWeight(adjV))//发现到达adjV更短的横切边
setCrossEdge(adjV,e);
}//for
}//for(int t=1...
}
//查找轻边在V-S中的顶点
protected Vertex selectMinVertex(Iterator it){
Vertex min = null;
for(it.first(); !it.isDone(); it.next()){
Vertex v = (Vertex)it.currentItem();
if(!v.isVisited()){ min = v; break;}
}
for(; !it.isDone(); it.next()){
Vertex v = (Vertex)it.currentItem();
if(!v.isVisited()&&getCrossWeight(v)<getCrossWeight(min))
min = v;
}
return min;
}
4.2 Kruskal算法
最小生成树直白的讲就是,挑选N-1条不产生回路最短的边。Kruskal算法算是最直接的表达了这个思想:在剩余边中挑选一条最短的边,看是否产生回路,是放弃,不是选定然后重复这个步骤。