上人工智能导论课要求实现这个算法,基本没有找到什么对这个算法的解释,ppt也没说细节,就随便写写了,目前只测试了两个图。
思路:
如上图所示(这里以有向图举例只是为了方便),需要找到从0到3的最短路径:
代价一致的做法,维护一个边缘集,用优先队列存储,步骤:
1.初始边缘集为0的所有后继节点,也就是{1(80),4(90)},()中是到节点的路径长
2.从边缘集合中取出路径值最小的节点A作为扩展节点,如果该扩展节点已经是终点,退出,已经找到最短路径
3.将A从边缘集中移除
4.更新边缘集:原来在里面的节点不管,添加扩展节点的后继节点到边缘集种
6.重复步骤2,3,4
我们始终选择边缘集中的最小代价的节点先行一步,也就是先扩展它的后继节点,
其目的是为了使其更快到达终点,因为它有最小代价,最优性,这也是我们需要的。
以上图举例写出边缘集合和扩展节点的更新:
初始edgeSet={1(80),4(99)}
选择1作为扩展节点,更新edgeSet = {4(99),2(80+97)}
选择4作为扩展节点,更新edgeSet={2(80+97),3(99+211)}
选择2作为扩展节点,更新edgeSet={3(80+97+101),3(99+322)}
选择3(80+97+101)作为扩展节点,检测到3就是终点,结束,找到最短路径为0->1->2>3
代码在最下面。
更新:
我想到如果存在(顺时针或者逆时针)的环路,(想多了,实际上无向图就会构成环路),并且环路有着更小的路径花费,会陷入兜圈子,确实是这样子,但是兜圈子过程中路径花费会增大,而兜圈子中又不含终点,所以最终还是会顺利找到结果,新增下面的测试。
运行结果:
因为需要在环路中不断兜圈子,路径长度才能超过11,输出很长,这里不贴出来了
结果:
花费:11,路径:0->3->2
main函数中的2个测试图:
输出:
初始edgeSet:
node,cost:1,80
node,cost:4,99
扩展节点:index,cost:1,80
更新edgeSet后:
node,cost:4,99
node,cost:0,160
node,cost:2,177
扩展节点:index,cost:4,99
更新edgeSet后:
node,cost:0,160
node,cost:2,177
node,cost:0,198
node,cost:3,310
扩展节点:index,cost:0,160
更新edgeSet后:
node,cost:2,177
node,cost:1,240
node,cost:0,198
node,cost:3,310
node,cost:4,259
扩展节点:index,cost:2,177
更新edgeSet后:
node,cost:0,198
node,cost:1,240
node,cost:4,259
node,cost:3,310
node,cost:1,274
node,cost:3,278
扩展节点:index,cost:0,198
更新edgeSet后:
node,cost:1,240
node,cost:1,274
node,cost:4,259
node,cost:3,310
node,cost:3,278
node,cost:1,278
node,cost:4,297
扩展节点:index,cost:1,240
更新edgeSet后:
node,cost:4,259
node,cost:1,274
node,cost:1,278
node,cost:3,310
node,cost:3,278
node,cost:4,297
node,cost:0,320
node,cost:2,337
扩展节点:index,cost:4,259
更新edgeSet后:
node,cost:1,274
node,cost:3,278
node,cost:1,278
node,cost:3,310
node,cost:2,337
node,cost:4,297
node,cost:0,320
node,cost:0,358
node,cost:3,470
扩展节点:index,cost:1,274
更新edgeSet后:
node,cost:1,278
node,cost:3,278
node,cost:4,297
node,cost:3,310
node,cost:2,337
node,cost:3,470
node,cost:0,320
node,cost:0,358
node,cost:0,354
node,cost:2,371
扩展节点:index,cost:1,278
更新edgeSet后:
node,cost:3,278
node,cost:3,310
node,cost:4,297
node,cost:0,354
node,cost:2,337
node,cost:3,470
node,cost:0,320
node,cost:0,358
node,cost:2,371
node,cost:0,358
node,cost:2,375
花费:278,路径:0->1->2->3
图2:
运行结果:
也很长不贴过程了,直接贴结果
花费:3,路径:0->3->4->6
代码如下:
1.EdgeSetElement
import java.util.PriorityQueue;
//边缘集元素描述
public class EdgeSetElement implements Comparable<EdgeSetElement>{
//节点的索引,下标
public int nodeIndex;
//到达当前节点所需的花费
public int cost;
//走过的路径
public String path;
public EdgeSetElement(int nodeIndex, int cost, String path) {
this.nodeIndex = nodeIndex;
this.cost = cost;
this.path = path;
}
@Override
public int compareTo(EdgeSetElement o) {
//只要加进来就认为不重复
//允许存在nodeIndex相同的元素
if(this.cost < o.cost) {
return -1;
} else{
return 1;
}
}
@Override
public boolean equals(Object obj) {
EdgeSetElement e = (EdgeSetElement) obj;
if(e.nodeIndex == this.nodeIndex && e.cost == this.cost) {
return true;
}
return false;
}
}
2.UniformCostSearch(主要的算法)
package work2;
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;
/**
* 代价一致搜索
* @author outsider
*
*/
public class UniformCostSearch {
//边缘集合
private PriorityQueue<EdgeSetElement> edgeSet = new PriorityQueue<>();
private int vertexNum;//顶点数
private int[][] matrix;//无向图
public UniformCostSearch() {}
public UniformCostSearch(int vertexNum) {
this.vertexNum = vertexNum;
matrix = new int[vertexNum][vertexNum];
}
public UniformCostSearch(int vertexNum, int[][] matrix) {
this.vertexNum = vertexNum;
this.matrix = matrix;
}
/**
* 搜索
* @param startVertex 起点
* @param endVertex 终点
* @return
*/
public List<EdgeSetElement> search(int startVertex, int endVertex) {
//优先队列保存边缘集合
/*PriorityQueue<EdgeSetElement> edgeSet;*/
//初始化边缘集合
for(int i = 0; i < vertexNum; i++) {
if(matrix[startVertex][i] > 0) {
EdgeSetElement e = new EdgeSetElement(i, matrix[startVertex][i], startVertex+"->"+i);
edgeSet.add(e);
}
}
System.out.println("初始edgeSet:");
edgeSet.forEach((edge)->System.out.println("node,cost:"+edge.nodeIndex+","+edge.cost));
//无法到达终点
if(edgeSet.size() == 0) {
return null;
}
List<EdgeSetElement> results = new ArrayList<>();
//需要保存被删除的节点的前一个节点是什么
while(true) {
//选择一个边缘集种最小的路径花费值作为扩展节点
//如果已经是终点的节点被优先队列拿出来说明已经找到最短路径,不需要再寻找
EdgeSetElement exploded = edgeSet.poll();
if(exploded.nodeIndex == endVertex) {
results.add(exploded);
//可能存在多条路径
EdgeSetElement node = edgeSet.peek();
while(node!=null && node.nodeIndex == endVertex&&node.cost == exploded.cost) {
results.add(edgeSet.poll());
node = edgeSet.peek();
}
return results;
}
System.out.println("扩展节点:index,cost:"+exploded.nodeIndex+","+exploded.cost);
//更新边缘集
updateEdgeSet(exploded);
}
}
/**
* 更新边缘集,注意已经是终点的结合
* @param exploded 扩展节点
*/
public void updateEdgeSet(EdgeSetElement exploded) {
//找出扩展节点的后继添加到边缘集中
for(int i = 0; i < vertexNum; i++) {
if(matrix[exploded.nodeIndex][i] > 0) {
EdgeSetElement e = new EdgeSetElement(i, exploded.cost + matrix[exploded.nodeIndex][i], exploded.path+"->"+i);
edgeSet.add(e);
}
}
System.out.println("更新edgeSet后:");
edgeSet.forEach((edge)->System.out.println("node,cost:"+edge.nodeIndex+","+edge.cost));
}
/**
* 只是为了方便打印结果
* @param start 起点
* @param end 终点
*/
public void test(int start, int end) {
List<EdgeSetElement> results = this.search(start, end);
for(EdgeSetElement result : results) {
System.out.println("花费:"+result.cost+",路径:"+result.path);
}
}
public static void main(String[] args) {
System.out.println("测试样例1:ppt上的图");
int[][] g = new int[5][5];
g[0][1] = 80;
g[1][0] = 80;
g[1][2] = 97;
g[2][1] = 97;
g[2][3] = 101;
g[3][2] = 101;
g[0][4] = 99;
g[4][0] = 99;
g[4][3] = 211;
g[3][4] = 211;
UniformCostSearch uniformCostSearch = new UniformCostSearch(5, g);
uniformCostSearch.test(0, 3);
//测试样例二自己定义一个复杂的图
System.out.println("\n\n测试样例2:自定义一个图");
int[][] g2 = new int[8][8];
g2[0][1] = 4;
g2[0][3] = 1;
g2[0][2] = 1;
g2[3][1] = 1;
g2[1][4] = 1;
g2[2][7] = 1;
g2[2][3] = 1;
g2[2][5] = 2;
g2[3][4] = 1;
g2[4][6] = 1;
g2[5][6] = 1;
g2[1][0] = 4;
g2[3][0] = 1;
g2[2][0] = 1;
g2[1][3] = 1;
g2[4][1] = 1;
g2[7][2] = 1;
g2[3][2] = 1;
g2[5][2] = 2;
g2[4][3] = 1;
g2[6][4] = 1;
g2[6][5] = 1;
UniformCostSearch uniformCostSearch2 = new UniformCostSearch(8, g2);
uniformCostSearch2.test(0, 6);
//当出现闭合同向环时将出现死循环
//测试
System.out.println("\n\n闭合回路测试:");
int[][] g3 = new int[4][4];
g3[0][1] = 1;
g3[1][2] = 10;
g3[1][3] = 1;
g3[3][0] = 1;
g3[3][2] = 10;
g3[1][0] = 1;
g3[2][1] = 10;
g3[3][1] = 1;
g3[0][3] = 1;
g3[2][3] = 10;
UniformCostSearch ucs3 = new UniformCostSearch(4, g3);
ucs3.test(0, 2);
}
}