1.原理
宽度优先搜索(Breadth-First Search)类似于层次搜索,搜索深度逐渐加深。
如上图所示,如果要搜索1=>6的路径,会经过下面的顺序
1
12,18
123,126(找到),186(找到),187
1234,1235,1876(找到)
12345,12356(找到)
123456(找到)
2.Java编码
1)数据结构
A 一个顶点的邻接点的表示
如上图中的顶点6可以表示为:6,{2,5,7,8}
如果加上边的权重可以表示为:6,{2,5,7,8},{3,3,1,2}
import java.util.Arrays;
import java.util.List;
public class Adjacent {
int vertex; //顶点
List child; //顶点的所有邻接顶点
List weight; //上面边对应的权重
public Adjacent(int v,Integer[] c,Integer[] w)
{
vertex=v;
child= Arrays.asList(c);
weight=Arrays.asList(w);
}
}
B 图的表示
顶点集合,如上图{1,2,3,4,5,6,7,8}
所有边集合,如上图可表示为:
1,{2,8}
2,{1,3,6}
3,{2,4,5}
......
8,{1,6,7}
当然,这样表示对于无向图有冗余的,但对有向图的表示还是可以。
public class Graph {
List vertexes=new ArrayList<>(); //顶点集合
List edge=new ArrayList<>(); //所有邻接边的集合
public Graph(){}
public Graph(String[] v,Adjacent[] adj)
{
vertexes= Arrays.asList(v);
edge=Arrays.asList(adj);
}
}
2).简单输出图
public void print()
{
for(int i=0;i
3)查找顶点的编号
public int searchVertex(String v)
{
for(int i=0;i
4)判断路径字符串数组是否存在某个结点
public boolean isInStringArray(String[] s,int v)
{
for(int i=0;i
5)宽度优先搜索代码
思路:frontier表示路径队列,采用字符串表示路径,如路径012,表示到达顶点2需经过编号为0,1的顶点。explored表示已探索的顶点集合,在该集合中的顶点不再扩展。而allPath表示找到的所有路径的集合。代码中的while循环负责遍历所有顶点,而其中的for循环找到当前遍历的顶点的所有邻接顶点,如果找到的顶点不在explored集合中或者不在当前路径中,分两种情况讨论:一是找到的顶点正好是目标顶点,则将当前路径与该顶点组合成新路径并添加到allPath(表示找到的路径)集合中;二是如果找到的顶点不在当前路径上,则将当前路径连同找到的这个结点组合成新路径将其加入到frontier 队列中。
public ArrayList bfs(int src,int dst)
{
if(src<0 || dst<0 || src>=vertexes.size()
|| dst>=vertexes.size()) return null;
//路径队列
Queue frontier=new LinkedList<>();
//已探索顶点集合
ArrayList explored=new ArrayList<>();
//所有路径集合
ArrayList allPath=new ArrayList<>();
frontier.offer(String.valueOf(src));
while(!frontier.isEmpty())
{ //将队首路径出队
String p=frontier.remove();
String[] pathSplit=p.split("-");
//获取路径的最后一个结点
int ver=Integer.parseInt(pathSplit[pathSplit.length-1]);
//表示已探索过该结点
explored.add(ver);
for(int i=0;i
6)按顶点解析输出路径
public void parsePath(ArrayList path)
{
for(int i=0;i"+vertexes[Integer.parseInt(realPath[j])]);
}
System.out.println();
}
}
3.测试编码
import java.util.*;
public class Main {
public static void main(String[] args) {
//顶点集合
String[] city={"c1","c2","c3","c4","c5","c6","c7","c8"};
//每个顶点的邻接顶点集合
Adjacent[] adj=new Adjacent[8];
Integer[][] a={{1,7},{0,2,5},{1,3,4},{2,4},{2,3,5},{1,4,6,7},{5,7},{0,5,6}};
Integer[][] b={{5,2},{5,2,3},{2,4,5},{4,2},{5,2,3},{3,3,1,2},{1,3},{2,2,3}};
for(int i=0;i
输出结果为:
c4=>c5=>c6
c4=>c3=>c2=>c6
c4=>c3=>c5=>c6
c4=>c5=>c3=>c2=>c6
c4=>c3=>c2=>c1=>c8=>c6
c4=>c3=>c2=>c1=>c8=>c7=>c6
c4=>c5=>c3=>c2=>c1=>c8=>c6
c4=>c5=>c3=>c2=>c1=>c8=>c7=>c6
可以看到,找到的路径按路径顶点个数排序,这因为是宽度优先搜索,从源结点一层一层去搜索目标结点。
4.分析
BFS时间复杂度和空间复杂度皆为:O(b^d), b表示每一层的结点为b个,d表示总的搜索深度。当b=10,d=16时,这个数据将非常大,空间复杂度将达到10EB级别。BFS算法在每一步扩展时,若行动代价是一样的,则是最优的,如本例中并没有考虑权值,而只是简单考虑其深度;否则就不是最优的,这可以使用一致代价搜索算法(Uniform-Cost Search,UCS)使得每步都是最优的。BFS使用一般队列即可,而UCS使用优先级队列来处理后续结点。
5.计算路径权重
路径权重的计算比较简单,在此略过
6.DFS及其他
DFS(Deep-First Search)总是沿着当前结点往最深处探索,很快会推进到其最深层。编程时,一般使用栈(Stack,先进后出,LIFO)来存储后继邻接顶点。显然DFS不是最优的。请看DFS的输出结果:
c4=>c5=>c6
c4=>c5=>c3=>c2=>c6
c4=>c5=>c3=>c2=>c1=>c8=>c6
c4=>c5=>c3=>c2=>c1=>c8=>c7=>c6
c4=>c3=>c5=>c6
c4=>c3=>c2=>c6
c4=>c3=>c2=>c1=>c8=>c6
c4=>c3=>c2=>c1=>c8=>c7=>c6
显然DFS的输出有些乱,但也是有规律的,就是一个方向的路径输出完后,再输出其他路径,这是因为DFS使用栈的原因,DFS总是先朝一个方向不断前进、搜索。
DFS的时间复杂度为:O(b^d),这与BFS差不多,但其空间复杂度降低到O(bd),这在空间复杂度上是非常有优势的。DFS的改进算法:深度受限搜索DLS——根据情况限制搜索深度,迭代加深的深度优先搜索IDS——不断增加深度限制,直到找到目标。还有一种称为叫迭代加长搜索ILS,按路径代价不断叠加受限。双向搜索是从两个方向进行搜索,一个从开始点搜索,另一个从目标点搜索,若两者相遇,则找到解,显然双向搜索不是最优解,但其时间、空间复杂度大大降低:O(b^(d/2))。但存在结点的Predecessors(一个结点的双亲集)定义问题,这个定义其实也不难。