foxspecial

导航

java数据结构_笔记(5)_图的算法

图的算法

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中顶点的边中选取权值最小的边。

java数据结构_笔记(5)_图的算法

具体的实现中: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算法算是最直接的表达了这个思想:在剩余边中挑选一条最短的边,看是否产生回路,是放弃,不是选定然后重复这个步骤。

5 最短距离
在许多应用领域,带权图都被用来描述某个网络,比如通信网络、交通网络。这种情况下,各边的权重就对应于两点之间通信的成本或交通费用。此时,一类典型的问题就是:在任意指定的两点之间如果存在通路,那么最小的消费是多少。这类问题实际上就是带权图中两点之间最短路径的问题。
对于求解最短路径问题:A)有时应当是有向图:如同一信道两个方向的信息流量不同,会造成信息从终端A到B和从终端B到A所需延时不同。B)有时应当是无向图:如从城市A到B和从城市B到A的公路长度都一样。

定理:最短路径的子路径也是最短路径。

5.1 求从某个源点到其余各点的最短路径
迪克斯特拉Dijkstra算法执行规程:S 、V - S 是图的2个顶点集合;S是已求出的最短路径的终点集合;V - S 是尚未求出最短路径的终点集合。

1) 初始化:

S = {s}; distance(s) = 0; distancer(ui) = w(s, ui)或∞ ,(ui ∈ V-S);

2) 选择distance(uk) = min{distance(ui)|ui ∈ V-S }, uk为下一条最短路径的终点;

3) S = S ∪ { uk }

4) 以uk 为”中转”,修正V-S中各个顶点distance;

distancer(ui) = min{ distance(ui), distance(ui) + w(uk , ui)} (ui ∈ V-S)

5) 重复2) -4)步|V| -1 次

Path对象:

public class Path {
private int distance;  //起点与终点的距离
private Vertex start;  //起点信息
private Vertex end;  //终点信息
private LinkedList pathInfo;//起点到终点的完整路径
public Path() {
this(Integer.MAX_VALUE,null,null);
}
public Path(int distance, Vertex start, Vertex end) {
this.distance = distance;
this.start = start;
this.end = end;
pathInfo = new LinkedListDLNode();
}
//判断起点与终点之间是否存在路径
public boolean hasPath() {
return distance!=Integer.MAX_VALUE&&start!=null&&end!=null;
}
//求路径长度
public int pathLength(){
if (!hasPath()) return -1;
else if (start==end) return 0;
else return pathInfo.getSize()+1;
}

//get&set methods
public void setDistance(int dis){ distance = dis;}
public void setStart(Vertex v){ start = v;}
public void setEnd(Vertex v){ end = v;}
public int getDistance(){ return distance;}
public Vertex getStart(){ return start;}
public Vertex getEnd(){ return end;}
public Iterator getPathInfo(){
return pathInfo.elements();
}

//清空路经信息
public void clearPathInfo(){
pathInfo = new LinkedListDLNode();
}

//添加路径信息
public void addPathInfo(Object info){
pathInfo.insertLast(info);
}
}

关键算法 代码片段:

//求顶点v到其他顶点的最短路径
public Iterator shortestPath(Vertex v) {
LinkedList sPath = new LinkedListDLNode();
resetVexStatus();//重置图中各顶点的状态信息
Iterator it = getVertex();//初始化,将v到各顶点的最短距离初始化为由v直接可达的距离
for(it.first(); !it.isDone(); it.next()){
Vertex u = (Vertex)it.currentItem();
int weight = Integer.MAX_VALUE;
Edge e = edgeFromTo(v,u);
if (e!=null)
weight = e.getWeight();
if(u==v) weight = 0;
Path p = new Path(weight,v,u);
setPath(u, p);
}
v.setToVisited();//顶点v进入集合S,以visited=true表示属于S,否则不属于S
sPath.insertLast(getPath(v));//求得的最短路径进入链接表
for (int t=1;t<getVexNum();t++){//进行n-1次循环找到n-1条最短路径
Vertex k = selectMin(it);//中间顶点k。可能选出无穷大距离的点,但不会为空
k.setToVisited();  //顶点k加入S
sPath.insertLast(getPath(k));  //求得的最短路径进入链接表
int distK = getDistance(k);  //以k为中间顶点修改v到V-S中顶点的当前最短路径
Iterator adjIt = adjVertexs(k);  //取出k的所有邻接点
for(adjIt.first(); !adjIt.isDone(); adjIt.next()){
Vertex adjV = (Vertex)adjIt.currentItem();
Edge e = edgeFromTo(k,adjV);
if ((long)distK+(long)e.getWeight()<(long)getDistance(adjV)){//发现更短的路径
setDistance(adjV, distK+e.getWeight());
amendPathInfo(k,adjV);  //以k的路径信息修改adjV的路径信息
}
}//for
}//for(int t=1...
return sPath.elements();
}

//在顶点集合中选择路径距离最小的
protected Vertex selectMin(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()&&getDistance(v)<getDistance(min))
min = v;
}
return min;
}

//修改到终点的路径信息
protected void amendPathInfo(Vertex mid, Vertex end){
Iterator it = getPath(mid).getPathInfo();
getPath(end).clearPathInfo();
for(it.first(); !it.isDone(); it.next()){
getPath(end).addPathInfo(it.currentItem());
}
getPath(end).addPathInfo(mid.getInfo());
}

5.2 求每一对顶点之间的最短路径

Floyd弗洛伊德算法, 基本思想是:

1) 从 vi 到 vj 的所有可能存在的路径中,选出一条长度最短的路径。

2) 若< vi, vj >存在,则存在路径{ vi, vj }// 路径中不含其它顶点

3) 若< vi,v1>,< v1, vj >存在,则存在路径{ vi, v1, vj }// 路径中所含顶点序号不大于1

4) 若{ vi,…,v2}, { v2,…, vj }存在,则存在一条路径{ vi, …, v2, …vj }// 路径中所含顶点序号不大于2

5) 依次类推,则 vi 至 vj 的最短路径应是上述这些路径中,路径长度最小者。

posted on 2013-09-14 10:53  foxspecial  阅读(409)  评论(0编辑  收藏  举报