通过递归创建完整的有效路径列表

时间:2018-04-30 23:20:54

标签: java algorithm recursion path-finding

我有一张带有ids&的表格。他们的邻居需要创建一个递归函数,从起始id到结束id找到所有可能的路径,而不会两次相同的点。假设起始id为1,结束id为3。

String username = getIntent().getStringExtra("Username");
String firstName = getIntent().getStringExtra("firstName");
String lastName = getIntent().getStringExtra("lastName");
String gre = getIntent().getStringExtra("gre");
String toefl = getIntent().getStringExtra("toefl");

@Jeffrey Phillips Freeman编写的当前片段效果很好,只是它只返回一条可能的路径而不是所有可能的路径(1,2,3)& (1,5,3)。我被告知A *算法最适合这种情况,但是我仍然希望创建一个有效路径列表,这些路径将我从A点发送到B而不用去那条路线。新代码段需要简单地将所有有效路径放在ArrayList中。我打算通过考虑路径长度和其他因素来使用ArrayList来确定最佳路径。因此按路径距离排序的ArrayList将是一个奖励。作为补充说明,实际问题中的节点附有空间坐标,但节点之间的路径并不总是直线。

{1 | 2,5}
{2 | 1,3,4,5}
{3 | 2,5}
{4 | 2}
{5 | 1,2,3}

我有大约200分,并且在目前状态下,从A点到B点的简单测试(仅一点之外)带我进行22跳的旅程。

2 个答案:

答案 0 :(得分:2)

完全没有理由使用A *。它旨在尽可能有效地找到最短路径。鉴于你想要找到所有路径而不管长度如何,A *将是开销而没有任何好处。

在pseduocode中,您的算法应该类似于:

findPaths for path:
    if path is complete
        add to solution set
    else
        for each link from last step that is not in path
            add next step to path
            call findPaths
            remove next step from path

目前你正在返回一条路。如果要查找所有路径,则需要将其存储在路径列表中。

以下是一个示例实现:

public class FindPath {
    private final Stack<Integer> path = new Stack<>();
    private final Map<Integer, Set<Integer>> links = new HashMap<>();

    public void addLink(int from, int to) {
        links.putIfAbsent(from, new HashSet<>());
        links.get(from).add(to);
    }

    public void find(int from, int to, Consumer<Stack<Integer>> action) {
        path.push(from);
        if (from == to)
            action.accept(path);
        else
            links.getOrDefault(from, Set.of()).stream()
                    .filter(s -> !path.contains(s))
                    .forEach(s -> find(s, to, action));
        path.pop();
    }

    public static void main(String[] args) {
        FindPath finder = new FindPath();
        Random rand = new Random();
        IntStream.range(0, 20).forEach(n -> rand.ints(7, 0, 20).forEach(t -> finder.addLink(n, t)));
        finder.find(0, 19, System.out::println);
    }
}

答案 1 :(得分:1)

您可以修改我的原始代码,按照您的要求将所有路径作为List返回。只是没有早期的代码返回。这不会按路径长度排序,但是,如果你想要那么你需要A *。

public List<List<Integer>> searchHops(int from, int to, Set<Integer> seen) {
    seen.add(from);

    if (from == to) {
        final List<List<Integer>> newList = new ArrayList<>();
        newList.add(new ArrayList<>(Arrays.asList(from)));
        return newList;
    }

    List<List<Integer>> allPaths = null;
    for (int neighbor : getNeighbors(from)) {
        if (!seen.contains(neighbor)) {
            List<List<Integer>> results = searchHops(neighbor, to, new HashSet<>(seen));

            if (results != null) {
                for(List<Integer> result : results) {
                    result.add(0, from);
                    if( allPaths != null )
                        allPaths.add(result);
                }
                if( allPaths == null )
                    allPaths = results;
            }
        }
    }
    return allPaths;
}

如果您真的关心从最短路径到最长路径的路径,那么使用A * 会好得多。 A *将首先按照最短路径的顺序返回尽可能多的可能路径。因此,如果您真正想要的是从最短到最长的所有可能路径,那么您仍然需要A *算法。如果您关心从最短到最长的排序,我上面建议的代码会比它需要的慢得多,更不用说会占用更多的空间,然后您想要一次性存储每个可能的路径。 / p>

由于您表示您首先关心最短路径,并且可能想要检索N路最短路径,所以您绝对应该在这里使用A *。

如果您希望基于A *的实现能够返回从最短到最长的所有路径,则以下内容将实现此目的。它有几个优点。首先,它从最短到最长的排序是有效的。此外,它仅在需要时计算每个附加路径,因此如果您提前停止,因为您不需要每个路径,则可以节省一些处理时间。它还在每次计算下一个路径时重用后续路径的数据,因此它更有效。如果您关心按路径长度排序,总体来说应该是最有效的算法。

import java.util.*;

public class AstarSearch {
    private final Map<Integer, Set<Neighbor>> adjacency;
    private final int destination;

    private final NavigableSet<Step> pending = new TreeSet<>();

    public AstarSearch(Map<Integer, Set<Neighbor>> adjacency, int source, int destination) {
        this.adjacency = adjacency;
        this.destination = destination;

        this.pending.add(new Step(source, null, 0));
    }

    public List<Integer> nextShortestPath() {
        Step current = this.pending.pollFirst();
        while( current != null) {
            if( current.getId() == this.destination )
                return current.generatePath();
            for (Neighbor neighbor : this.adjacency.get(current.id)) {
                if(!current.seen(neighbor.getId())) {
                    final Step nextStep = new Step(neighbor.getId(), current, current.cost + neighbor.cost + predictCost(neighbor.id, this.destination));
                    this.pending.add(nextStep);
                }
            }
            current = this.pending.pollFirst();
        }
        return null;
    }

    protected int predictCost(int source, int destination) {
        return 0; //Behaves identical to Dijkstra's algorithm, override to make it A*
    }

    private static class Step implements Comparable<Step> {
        final int id;
        final Step parent;
        final int cost;

        public Step(int id, Step parent, int cost) {
            this.id = id;
            this.parent = parent;
            this.cost = cost;
        }

        public int getId() {
            return id;
        }

        public Step getParent() {
            return parent;
        }

        public int getCost() {
            return cost;
        }

        public boolean seen(int node) {
            if(this.id == node)
                return true;
            else if(parent == null)
                return false;
            else
                return this.parent.seen(node);
        }

        public List<Integer> generatePath() {
            final List<Integer> path;
            if(this.parent != null)
                path = this.parent.generatePath();
            else
                path = new ArrayList<>();
            path.add(this.id);
            return path;
        }

        @Override
        public int compareTo(Step step) {
            if(step == null)
                return 1;
            if( this.cost != step.cost)
                return Integer.compare(this.cost, step.cost);
            if( this.id != step.id )
                return Integer.compare(this.id, step.id);
            if( this.parent != null )
                this.parent.compareTo(step.parent);
            if(step.parent == null)
                return 0;
            return -1;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Step step = (Step) o;
            return id == step.id &&
                cost == step.cost &&
                Objects.equals(parent, step.parent);
        }

        @Override
        public int hashCode() {
            return Objects.hash(id, parent, cost);
        }
    }

   /*******************************************************
   *   Everything below here just sets up your adjacency  *
   *   It will just be helpful for you to be able to test *
   *   It isnt part of the actual A* search algorithm     *
   ********************************************************/

    private static class Neighbor {
        final int id;
        final int cost;

        public Neighbor(int id, int cost) {
            this.id = id;
            this.cost = cost;
        }

        public int getId() {
            return id;
        }

        public int getCost() {
            return cost;
        }
    }

    public static void main(String[] args) {
        final Map<Integer, Set<Neighbor>> adjacency = createAdjacency();
        final AstarSearch search = new AstarSearch(adjacency, 1, 4);
        System.out.println("printing all paths from shortest to longest...");
        List<Integer> path = search.nextShortestPath();
        while(path != null) {
            System.out.println(path);
            path = search.nextShortestPath();
        }
    }

    private static Map<Integer, Set<Neighbor>> createAdjacency() {
        final Map<Integer, Set<Neighbor>> adjacency = new HashMap<>();

        //This sets up the adjacencies. In this case all adjacencies have a cost of 1, but they dont need to. Otherwise
        //They are exactly the same as the example you gave in your question
        addAdjacency(adjacency, 1,2,1,5,1);         //{1 | 2,5}
        addAdjacency(adjacency, 2,1,1,3,1,4,1,5,1); //{2 | 1,3,4,5}
        addAdjacency(adjacency, 3,2,1,5,1);         //{3 | 2,5}
        addAdjacency(adjacency, 4,2,1);             //{4 | 2}
        addAdjacency(adjacency, 5,1,1,2,1,3,1);     //{5 | 1,2,3}

        return Collections.unmodifiableMap(adjacency);
    }

    private static void addAdjacency(Map<Integer, Set<Neighbor>> adjacency, int source, Integer... dests) {
        if( dests.length % 2 != 0)
            throw new IllegalArgumentException("dests must have an equal number of arguments, each pair is the id and cost for that traversal");

        final Set<Neighbor> destinations = new HashSet<>();
        for(int i = 0; i < dests.length; i+=2)
            destinations.add(new Neighbor(dests[i], dests[i+1]));
        adjacency.put(source, Collections.unmodifiableSet(destinations));
    }
}

以上代码的输出如下:

[1, 2, 4]
[1, 5, 2, 4]
[1, 5, 3, 2, 4]

请注意,每次拨打nextShortestPath()时,都会根据需要为您生成下一条最短路径。它只计算所需的额外步骤,并且不会两次遍历任何旧路径。此外,如果您决定不需要所有路径并尽早结束,那么您可以节省大量的计算时间。您只计算所需路径的数量,而不是更多。

如果您有某种启发式方法可以帮助您估算路径成本,那么请覆盖predictCost()方法并将其放在那里。您提到您的节点也有与之关联的空间坐标。在这种情况下,良好的启发式是两个节点之间的欧氏距离(它们之间的直线距离)。但它完全是选项,只有在计算所有可能的路径之前退出时才有助于缩短计算时间。

最后应该注意的是A *和Dijkstra算法确实有一些小的限制,尽管我认为它不会对你产生影响。即它在具有负权重的图表上无法正常工作。

以下是JDoodle的链接,您可以在浏览器中自行运行代码并查看其是否正常工作。您还可以更改图表以显示其在其他图表上的作用:http://jdoodle.com/a/ukx