深度优先、广度优先java实现

一 图的基本概念及存储结构
图G是由顶点的有穷集合,以及顶点之间的关系组成,顶点的集合记为V,顶点之间的关系构成边的集合E
G=(V,E).
说一条边从v1,连接到v2,那么有v1Ev2(E是V上的一个关系)《=》<v1,v2>∈E.
图有有向图,无向图之分,无向图的一条边相当于有向图的中两条边,即如果无向图的顶点v1和v2之间有一条边
,那么既有从v1连接到v2的边,也有从v2连接到v1的边,<v1,v2>∈E 并且<v2,v1>∈E,而有向图是严格区分方向的。

一般图的存储有两种方式
1)相邻矩阵,用一个矩阵来保持边的情况,<v1,v2>∈E则Matrix[v1][v2]=Weight.
2)邻接表,需要保存一个顺序存储的顶点表和每个顶点上的边的链接表。
这里的实现采用第二种方案,另外图又复杂图,简单图之分,复杂图可能在两点之间同一个方向有多条边,我们考虑的都是无环简单图,无环简单图是指顶点没有自己指向自己的边的简单图,即任取vi属于V => <vi,vi>不属于E并且没有重复边。

我们先给出图的ADT:

package algorithms.graph;


public interface Graph {

//mark

public static interface Edge{
public int
getWeight();
}

int
vertexesNum();

int
edgeNum();
boolean
isEdge(Edge edge);
void setEdge(
int
from,
int
to,
int
weight);
Edge firstEdge(
int
vertex);
Edge nextEdge(Edge edge);
int
toVertex(Edge edge);
int
fromVertex(Edge edge);
String getVertexLabel(
int
vertex);
void
assignLabels(String[] labels);
void
deepFirstTravel(GraphVisitor visitor);
void
breathFirstTravel(GraphVisitor visitor);



}

其中的方法大多数比较一目了然,
其中
1)Edge firstEdge(int vertex)返回指定节点的边的链表里存的第一条边
2)
Edge nextEdge(Edge edge),顺着边链表返回下一条边
3)fromVertex,toVertex很简单返回该边的起始顶点和终结顶点
4)
getVertexLabel返回该定点对应的标号,
assignLabels给所有顶点标上号

GraphVisitor是一个很简单的接口:

package algorithms.graph;


public interface GraphVisitor {

void visit(Graph g,
int
vertex);

}

OK,下面是该部分实现:

package algorithms.graph;

import
java.util.Arrays;



public class DefaultGraph implements Graph {


private static class _Edge
implements
Edge{


private static final _Edge NullEdge
=new
_Edge();

int
from;
int
to;
int
weight;

_Edge nextEdge;

private
_Edge()
{
weight
=
Integer.MAX_VALUE;
}

_Edge(
int from,
int
to,
int
weight)
{

this.from=
from;
this.to=
to;
this.weight=
weight;
}
public int
getWeight()
{
return
weight;
}


}

private static class
_EdgeStaticQueue
{
_Edge first;
_Edge last;
}

private int
numVertexes;
private
String[] labels;
private int
numEdges;


private
_EdgeStaticQueue[] edgeQueues;

//tag the specified vertex be visited or not

private boolean [] visitTags;


public DefaultGraph(int numVertexes) {
if(numVertexes<1
)
{
throw new
IllegalArgumentException();
}
this.numVertexes=
numVertexes;
this.visitTags=new boolean
[numVertexes];
this.labels=new
String[numVertexes];
for(int i
=0;i<numVertexes;i++
)
{
labels[i]
=i+""
;


}
this.edgeQueues=new
_EdgeStaticQueue[numVertexes];
for(int i
=0;i<numVertexes;i++
)
{
edgeQueues[i]
=new
_EdgeStaticQueue();
edgeQueues[i].first
=edgeQueues[i].last=
_Edge.NullEdge;

}
this.numEdges=0
;
}




@Override
public int edgeNum() {

return
numEdges;
}


@Override
public Edge firstEdge(int vertex) {
if(vertex>=numVertexes) throw new
IllegalArgumentException();
return
edgeQueues[vertex].first;

}


@Override
public boolean isEdge(Edge edge) {

return (edge
!=
_Edge.NullEdge);
}


@Override
public Edge nextEdge(Edge edge) {

return
((_Edge)edge).nextEdge;
}


@Override
public int vertexesNum() {

return
numVertexes;
}


@Override
public int
fromVertex(Edge edge) {

return
((_Edge)edge).from;
}

@Override
public void setEdge(
int
from,
int
to,
int
weight) {
//we don't allow ring exist

if(from<0||from>=numVertexes||to<0||to>=numVertexes||weight<0||from==to)throw new IllegalArgumentException();
_Edge edge
=new
_Edge(from,to,weight);
edge.nextEdge
=
_Edge.NullEdge;
if(edgeQueues[from].first==
_Edge.NullEdge)
edgeQueues[from].first
=
edge;
else

edgeQueues[from].last.nextEdge
=
edge;
edgeQueues[from].last
=
edge;

}

@Override
public int
toVertex(Edge edge) {

return
((_Edge)edge).to;
}

@Override
public String getVertexLabel(
int
vertex) {
return
labels[vertex];
}

@Override
public void
assignLabels(String[] labels) {
System.arraycopy(labels,
0, this.labels, 0
, labels.length);

}

//to be continue


}


二 深度优先周游
即从从某一点开始能继续往前就往前不能则回退到某一个还有边没访问的顶点,沿这条边看该边指向的点是否已访问,如果没有访问,那么从该指向的点继续操作。

那么什么时候结束呢,这里我们在图的ADT实现里加上一个标志数组。该数组记录某一顶点是否已访问,如果找不到不到能继续往前访问的未访问点,则结束。

你可能会问,如果指定图不是连通图(既有2个以上的连通分量)呢?
OK,解决这个问题,我们可以让每一个顶点都有机会从它开始周游。
下面看deepFirstTravel的实现:


@Override
public void deepFirstTravel(GraphVisitor visitor) {
Arrays.fill(visitTags,
false);//reset all visit tags

for(int i=0;i<numVertexes;i++ )
{
if(!
visitTags[i])do_DFS(i,visitor);
}

}


private final void do_DFS(
int
v, GraphVisitor visitor) {
//first visit this vertex

visitor.visit( this , v);
visitTags[v]
=true
;

//
for each edge from this vertex, we do one time
//and this for loop is very classical in all graph algorithms

for(Edge e=firstEdge(v);isEdge(e);e= nextEdge(e))
{
if(!
visitTags[toVertex(e)])
{
do_DFS(toVertex(e),visitor);
}
}


}


三 广度优先周游
广度优先周游从每个指定顶点开始,自顶向下一层一层的访问。上一层所有顶点访问完了才继续下一层的访问。可以把二叉树的广度优先周游看成图的广度优先周游的特例。
(二叉树是连通的无回路的有向图,也是一棵根树)
同样,广度优先也要借助与一个队列来存储待访问顶点

下面是breathFirstTravel的实现,为了减小Java库的影响,我自己实现一个很简单的队列。(你也可以使用
ArrayList,但是记住队列的定义,只能在头删除,在尾插入):

private static class _IntQueue
{
private static class
_IntQueueNode
{
_IntQueueNode next;
int
value;
}
_IntQueueNode first;
_IntQueueNode last;

//queue can only insert to the tail

void add(int i)
{
_IntQueueNode node
=new
_IntQueueNode();
node.value
=
i;
node.next
=null
;
if(first==null)first=
node;
else last.next=
node;
last
=
node;
}


boolean
isEmpty()
{
return first
==null
;
}
//queue can only remove from the head

int remove()
{
int val
=
first.value;
if(first==
last)
first
=last=null
;
else

first
= first.next;
return
val;
}

}

@Override
public void breathFirstTravel(GraphVisitor visitor) {
Arrays.fill(visitTags,
false);//reset all visit tags



for(int i=0;i<numVertexes;i++ )
{
if(!
visitTags[i])
{

do_BFS(i,visitor);
}
}

}

private void do_BFS(
int
v, GraphVisitor visitor) {
//
and BFS will use an queue to keep the unvisited vertexes
// we can also just use java.util.ArrayList

_IntQueue queue =new _IntQueue();
queue.add(v);
while(!
queue.isEmpty())
{
int fromV
=
queue.remove();
visitor.visit(
this
, fromV);
visitTags[fromV]
=true
;
for(Edge e=firstEdge(fromV);isEdge(e);e=
nextEdge(e))
{
if(!
visitTags[toVertex(e)])
{
queue.add(toVertex(e));
}
}
}
}


OK,最后我们测试一下:


public static void main(String[] args) {


DefaultGraph g
=new DefaultGraph(9 );
g.setEdge(
0, 1, 0
);
g.setEdge(
0, 3, 0
);
g.setEdge(
1, 2, 0
);
g.setEdge(
4, 1, 0
);
g.setEdge(
2, 5, 0
);

g.setEdge(
3, 6, 0
);
g.setEdge(
7, 4, 0
);
g.setEdge(
5, 8, 0
);
g.setEdge(
6, 7, 0
);
g.setEdge(
8, 7, 0
);

//now we visit it

GraphVisitor visitor =new GraphVisitor()
{
@Override
public void visit(Graph g,
int
vertex) {
System.out.print(g.getVertexLabel(vertex)
+" "
);

}

};
System.out.println(
"DFS==============:"
);
g.deepFirstTravel(visitor);
System.out.println();
System.out.println(
"BFS==============:"
);
g.breathFirstTravel(visitor);
System.out.println();
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中的深度优先搜索(Depth-First Search, DFS)和广度优先搜索(Breadth-First Search, BFS)是两种常用的图遍历算法,它们主要用于探索图或树结构中节点间的连接。 **深度优先搜索(DFS)**: - DFS通过递归或栈实现,从根节点开始,尽可能深地探索分支,直到到达叶子节点,然后回溯到上一个未访问的节点。 - 它的主要特点是能够找到一条从起点到终点的路径,如果存在这样的路径。 - 实现时,通常会使用递归方法标记已访问节点,避免重复访问。 **广度优先搜索(BFS)**: - BFS采用队列数据结构,从根节点开始,逐层地访问节点,先访问同一层的所有节点,然后再访问下一层。 - BFS的特点是保证最先被访问的是距离起点最近的节点,用于寻找最短路径或最小代价路径。 - 在实现时,通常用一个队列来存储当前层级的所有节点。 **算法分析**: 1. **时间复杂度**:DFS在最坏情况下可能需要遍历所有节点,时间复杂度为O(V+E)(V为顶点数,E为边数)。BFS同样如此,因为它们都需要访问所有节点。 2. **空间复杂度**:DFS通常占用较少的栈空间,因为只需要保存当前路径;而BFS由于需要存储所有同一层级的节点,所以可能需要较大的队列空间,为O(W),其中W为宽度(最大层级数)。 3. **适用场景**:DFS适用于寻找连通分量、是否存在环等问题,而BFS则适用于求解最短路径、最小生成树等场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值