package trains;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import trains.bean.City;
import trains.bean.ToOherCityRoute;
import trains.util.MyAssert;
/**
* 铁路线路图
* @author xujw
* 2019年3月9日20:03:05
*/
public class TrainsMap {
/**
* 铁路中包含的所有城市
*/
private Map<String,City> allCity = new HashMap<String,City>();
/**
* 城市间的所有路线
*/
private Map<String,Double> allRoute = new HashMap<String,Double>();
/**
* 向地图里添加一个路线
* @param fromCityName 开始城市
* @param toCityName 终点城市
* @param distance 路线距离
*/
public void addRoute(String fromCityName,String toCityName,double distance) {
MyAssert.isNotBlank(fromCityName, "起始城市不能为空!");
MyAssert.isNotBlank(toCityName, "终点城市不能为空!");
MyAssert.isTrue(distance > 0, "两城市间的距离必须大于0");
Double route = allRoute.get(fromCityName+"-"+toCityName);
if(route != null) {
System.out.println("警告:"+fromCityName+"到"+toCityName+"的路线已经存在不需要重复添加!");
return;
}
City fromCity = allCity.get(fromCityName);
if(fromCity == null) {
fromCity = new City(fromCityName);
}
City toCity = allCity.get(toCityName);
if(toCity == null) {
toCity = new City(toCityName);
}
ToOherCityRoute toRoute = new ToOherCityRoute(toCityName, distance);
fromCity.addRoute(toRoute);
allCity.put(fromCityName, fromCity);
allCity.put(toCityName, toCity);
allRoute.put(fromCityName+"-"+toCityName,distance);
System.out.println("信息:"+fromCityName+"到"+toCityName+"的路线添加成功。");
}
/**
* 给定一个路线 计算出路线距离
* @param cityeNames 路线以此经过的城市节点
* @return 该路线距离
*/
public double getRouteDistance(String ...cityeNames) {
int length = cityeNames.length;
MyAssert.isTrue(length>1, "路线最少需要2个节点");
double rs = 0;
for(int i=1;i<length;i++) {
Double route = allRoute.get(cityeNames[i-1]+"-"+cityeNames[i]);
MyAssert.isNotBlank(route, "NO SUCH ROUTE");
rs = rs+route;
}
return rs;
}
/**
* 两个城市 的路线个数 ps:允许走重复路
* @param fromCityName 开始城市
* @param toCityName 终点城市
* @param maxSiteCount 路线经过的站点不能超过 maxSiteCount
* @return 路线个数
*/
public int getRouteCount(String fromCityName,String toCityName,int maxStopsCount,boolean isExactly) {
MyAssert.isNotBlank(fromCityName, "起始城市不能为空!");
MyAssert.isNotBlank(toCityName, "终点城市不能为空!");
City fromCity = allCity.get(fromCityName);
MyAssert.isNotBlank(fromCity, "起始城市不存在");
City toCity = allCity.get(toCityName);
MyAssert.isNotBlank(toCity, "终点城市不存在");
MyAssert.isTrue(maxStopsCount>1, "最大站点数必须大于1");
//当前已经探索的 站点数
int tempStops = 0;
//已经计算出的路线个数
int calRouteCount = 0;
return calRouteCount(calRouteCount, tempStops, fromCity, toCity, maxStopsCount,isExactly);
}
/**
* 递归算 路径个数 由于题目只需要算个数不需要具体路径 所有相对简单点,如果需要路径需要用到回溯算法
* @param calRouteCount 当前计算出的路径个数
* @param tempStops 当前试探的站点数
* @param fromCity 当前位于的站点
* @param toCity 要去的站点
* @param maxStopsCount 允许经过的最大站点
* @return 路径个数
*/
private int calRouteCount(int calRouteCount,int tempStops,City fromCity,City toCity,int maxStopsCount,boolean isExactly) {
//当前探索的步数已经达到允许的站点数 则直接返回 当前探索出的路线个数
if(isExactly && tempStops >= maxStopsCount) {
return calRouteCount;
}
if(!isExactly && tempStops >= maxStopsCount) {
return calRouteCount;
}
List<ToOherCityRoute> routes = fromCity.getCanReachCity();
for(int i=0;i<routes.size();i++) {
ToOherCityRoute tempRoute = routes.get(i);
String tempRouteToCityName = tempRoute.getOherCityName();
/* 如果需要不能走重复路线 则这个需要还原 hadGoCity 为方法的入参 类型:Map<String,City>
//站点已经走过 则放弃
if(hadGoCity.get(tempRouteToCityName) != null) {
continue;
}
hadGoCity.put(tempRouteToCityName, allCity.get(tempRouteToCityName));
*/
//探索到一个路径
if(toCity.getName().equals(tempRouteToCityName)) {
if(isExactly && tempStops+1 == maxStopsCount) {
calRouteCount = calRouteCount+1;
}
if(!isExactly) {
calRouteCount = calRouteCount+1;
}
}
//继续探索
calRouteCount = calRouteCount(calRouteCount, tempStops+1, allCity.get(tempRouteToCityName), toCity, maxStopsCount,isExactly);
}
return calRouteCount;
}
/**
* 地接斯科拉算法 计算两点间的最短距离 根据题目需要起点和终点允许相同即走环路
* @param fromCityName 起点城市名称
* @param toCityName 终点城市名称
* @return 返回null 表示没有路线可以到达 否则返回的是最短距离
*/
public Double minDistance(String fromCityName,String toCityName) {
MyAssert.isNotBlank(fromCityName, "起始城市不能为空!");
MyAssert.isNotBlank(toCityName, "终点城市不能为空!");
City fromCity = allCity.get(fromCityName);
MyAssert.isNotBlank(fromCity, "起始城市不存在");
City toCity = allCity.get(toCityName);
MyAssert.isNotBlank(toCity, "终点城市不存在");
Map<String,String> hadVisitor = new HashMap<String,String>();
Map<String,String> preCity = new HashMap<String,String>();
Map<String,Double> toCityMinDistance = new HashMap<String,Double>();
toCityMinDistance = calNextCityDijsktra(fromCityName, 0,hadVisitor,preCity,toCityMinDistance);
printMinRoute(fromCityName,toCityMinDistance,preCity);
//如果两点相同特殊处理 依次计算该节点 下一节点回到该节点距离 然后加上该节点到下一点距离 取最小
if(fromCityName.equals(toCityName)) {
Double minDistance = null;
List<ToOherCityRoute> routes = fromCity.getCanReachCity();
for(int i=0;i<routes.size();i++) {
ToOherCityRoute route = routes.get(i);
Double tempDistance = minDistance(route.getOherCityName(), toCityName);
if(tempDistance != null && (minDistance == null || (tempDistance+route.getDistance()<minDistance))) {
minDistance = tempDistance+route.getDistance();
}
}
return minDistance;
}
return toCityMinDistance.get(toCityName);
}
/**
* 地接斯科拉递归实现
* @param tempCityName 每次计算得到的最小距离节点
* @param calTempDistance 当前节点的权重
* @param hadVisitor 已经处理过的集合 即 s s:地杰斯特拉已经计算的集合 t为非s集合
* @param preCity 节点的前驱
* @param hadToCityMinDistance 已计算节点的最短路径
* @return
*/
private Map<String,Double> calNextCityDijsktra(String tempCityName,double calTempDistance,Map<String,String> hadVisitor,Map<String,String> preCity,Map<String,Double> hadToCityMinDistance) {
City tempCity = allCity.get(tempCityName);
List<ToOherCityRoute> routes = tempCity.getCanReachCity();
hadToCityMinDistance.put(tempCityName, calTempDistance);
for(int i=0;i<routes.size();i++) {
ToOherCityRoute tempRoute = routes.get(i);
String toCityName = tempRoute.getOherCityName();
if(hadToCityMinDistance.get(toCityName) == null || hadToCityMinDistance.get(toCityName) > calTempDistance+tempRoute.getDistance()) {
hadToCityMinDistance.put(toCityName, calTempDistance+tempRoute.getDistance());
preCity.put(toCityName, tempCityName);
}
}
Double tempMinDistance = null;
String minCityName = null;
for(String key:hadToCityMinDistance.keySet()) {
if(hadVisitor.get(key) == null && (tempMinDistance == null || tempMinDistance> hadToCityMinDistance.get(key))) {
tempMinDistance = hadToCityMinDistance.get(key);
minCityName = key;
}
}
if(minCityName != null) {
hadVisitor.put(minCityName, tempCityName);
hadToCityMinDistance = calNextCityDijsktra(minCityName,tempMinDistance, hadVisitor,preCity, hadToCityMinDistance);
}
return hadToCityMinDistance;
}
/**
* 输出计算后的路径
* @param fromCity 开始城市
* @param toCityMinDistance 可以到达城市的距离
* @param hadVisitor 每个节点的前驱
*/
private void printMinRoute(String fromCity,Map<String,Double> toCityMinDistance,Map<String,String> preCity) {
for(String key:toCityMinDistance.keySet()) {
List<String> routes = new ArrayList<String>();
String tempEnd = key;
while(preCity.get(tempEnd) != null) {
if(fromCity.equals(tempEnd)) {
break;
}
routes.add(tempEnd);
tempEnd = preCity.get(tempEnd);
}
StringBuilder routeStr = new StringBuilder(fromCity+"->"+key+"距离"+toCityMinDistance.get(key)+",路径:");
routeStr.append(fromCity);
for(int i=0;i<routes.size();i++) {
routeStr.append("->"+routes.get(routes.size()-i-1));
}
System.out.println(routeStr);
}
}
/**
* 在规定距离内 两点间路线个数
* @param fromCityName 起点城市
* @param toCityName 终点城市
* @param maxDistance 允许的最大距离
* @return 返回路线个数
*/
public int getRouteCountLessDistance(String fromCityName,String toCityName,double maxDistance) {
MyAssert.isNotBlank(fromCityName, "起始城市不能为空!");
MyAssert.isNotBlank(toCityName, "终点城市不能为空!");
City fromCity = allCity.get(fromCityName);
MyAssert.isNotBlank(fromCity, "起始城市不存在");
City toCity = allCity.get(toCityName);
MyAssert.isNotBlank(toCity, "终点城市不存在");
MyAssert.isTrue(maxDistance>0, "允许距离必须大于0");
//当前已经探索的 距离
double tempDistance = 0;
//已经计算出的路线个数
int calRouteCount = 0;
return calRouteDistance(calRouteCount, tempDistance, fromCity, toCity, maxDistance);
}
/**
* 递归求路线个数
* @param calRouteCount 当前计算出的路径个数
* @param tempDistance 当前已经走了的距离
* @param fromCity 当前所在城市
* @param toCity 目标城市
* @param maxDistance 允许的最大距离
* @return 范湖路线个数
*/
private int calRouteDistance(int calRouteCount,double tempDistance,City fromCity,City toCity,double maxDistance) {
//当前探索的路径长度已经达到允许的路径长度 则直接返回 当前探索出的路线个数
if(tempDistance >= maxDistance) {
return calRouteCount;
}
List<ToOherCityRoute> routes = fromCity.getCanReachCity();
for(int i=0;i<routes.size();i++) {
ToOherCityRoute tempRoute = routes.get(i);
String tempRouteToCityName = tempRoute.getOherCityName();
//探索到一个路径
if(toCity.getName().equals(tempRouteToCityName) && tempDistance+tempRoute.getDistance() < maxDistance) {
calRouteCount = calRouteCount+1;
}
//继续探索
calRouteCount = calRouteDistance(calRouteCount, tempDistance+tempRoute.getDistance(), allCity.get(tempRouteToCityName), toCity, maxDistance);
}
return calRouteCount;
}
public static void main(String[] args) {
TrainsMap app = new TrainsMap();
app.addRoute("A", "B", 5);
app.addRoute("B", "C", 4);
app.addRoute("C", "D", 8);
app.addRoute("D", "C", 8);
app.addRoute("D", "E", 6);
app.addRoute("A", "D", 5);
app.addRoute("C", "E", 2);
app.addRoute("E", "B", 3);
app.addRoute("A", "E", 7);
System.out.println("A-B-C 路线距离为:"+app.getRouteDistance("A","B","C"));
System.out.println("A-D 路线距离为:"+app.getRouteDistance("A","D"));
System.out.println("A-D-C 路线距离为:"+app.getRouteDistance("A","D","C"));
System.out.println("A-E-B-C-D 路线距离为:"+app.getRouteDistance("A","E","B","C","D"));
try {
System.out.println("A-E-D 路线距离为:"+app.getRouteDistance("A","E","D"));
}
catch(Exception e) {
System.out.println("A-E-D 路线距离为:"+e.getMessage());
}
System.out.println("C到C经过站点个数不超过3的路线个数:"+app.getRouteCount("C", "C",3,false));
System.out.println("A到C经过站点个数为4的路线个数:"+app.getRouteCount("A", "C",4,true));
System.out.println("A到C最短路径:"+app.minDistance("A", "C"));
System.out.println("B到B最短路径:"+app.minDistance("B", "B"));
System.out.println("C到C路径长度不超过 30的路线个数:"+app.getRouteCountLessDistance("C","C",30));
}
}