1
\$\begingroup\$

Intro

I have this GitHub repository for doing pathfinding in directed unweighted graphs. This post is about BIDDFS proposed by Richard Korf in his paper.


Code

package io.github.coderodde.libid.impl;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import io.github.coderodde.libid.NodeExpander;
public final class BidirectionalIterativeDeepeningDepthFirstSearch<N> {
 private final Deque<N> backwardSearchStack;
 private final Set<N> frontier;
 private final Set<N> visitedForward;
 private final Set<N> visitedBackward;
 private final NodeExpander<N> forwardExpander;
 private final NodeExpander<N> backwardExpander;
 private int previousVisitedSizeForward;
 private int previousVisitedSizeBackward;
 public BidirectionalIterativeDeepeningDepthFirstSearch() {
 this.backwardSearchStack = null;
 this.frontier = null;
 this.visitedForward = null;
 this.visitedBackward = null;
 this.forwardExpander = null;
 this.backwardExpander = null;
 }
 private BidirectionalIterativeDeepeningDepthFirstSearch(
 NodeExpander<N> forwardExpander,
 NodeExpander<N> backwardExpander) {
 
 this.backwardSearchStack = new ArrayDeque<>();
 this.frontier = new HashSet<>();
 this.visitedForward = new HashSet<>();
 this.visitedBackward = new HashSet<>();
 this.forwardExpander = forwardExpander;
 this.backwardExpander = backwardExpander;
 }
 public List<N> search(N source, 
 N target, 
 NodeExpander<N> forwardExpander,
 NodeExpander<N> backwardExpander) {
 // Handle the easy case. We need this in order to terminate the 
 // recursion in buildPath.
 if (source.equals(target)) {
 return new ArrayList<>(Arrays.asList(source));
 }
 BidirectionalIterativeDeepeningDepthFirstSearch<N> state = 
 new BidirectionalIterativeDeepeningDepthFirstSearch<>(
 forwardExpander,
 backwardExpander);
 // The actual search routine:
 for (int forwardDepth = 0;; ++forwardDepth) {
 state.frontier.clear();
 state.visitedForward.clear();
 
 // Do a depth limited search in forward direction. Put all nodes at 
 // depth == 0 to the frontier.
 state.depthLimitedSearchForward(source,
 forwardDepth);
 
 
 if (state.visitedForward.size() == 
 state.previousVisitedSizeForward) {
 // No improvement since the last run of forward search. Return
 // 'null' indicating that there is no path from source to 
 // target:
 return null;
 }
 
 state.previousVisitedSizeForward = state.visitedForward.size();
 state.visitedBackward.clear();
 
 N meetingNode = 
 state.depthLimitedSearchBackward(
 target,
 forwardDepth,
 state.visitedBackward);
 
 if (meetingNode != null) {
 return state.buildPath(source, 
 meetingNode);
 }
 
 state.visitedBackward.clear();
 
 meetingNode = 
 state.depthLimitedSearchBackward(
 target, 
 forwardDepth + 1, 
 state.visitedBackward);
 
 if (meetingNode != null) {
 return state.buildPath(source,
 meetingNode);
 }
 
 if (state.visitedBackward.size() == 
 state.previousVisitedSizeBackward) {
 return null;
 }
 
 state.previousVisitedSizeBackward = 
 state.visitedBackward.size();
 }
 }
 private void depthLimitedSearchForward(N node, int depth) {
 if (visitedForward.contains(node)) {
 return;
 }
 
 visitedForward.add(node);
 
 if (depth == 0) {
 frontier.add(node);
 return;
 }
 for (N child : forwardExpander.expand(node)) {
 depthLimitedSearchForward(child,
 depth - 1);
 }
 }
 private N depthLimitedSearchBackward(N node, 
 int depth,
 Set<N> visited) {
 if (visited.contains(node)) {
 return null;
 }
 
 backwardSearchStack.addFirst(node);
 if (depth == 0) {
 if (frontier.contains(node)) {
 return node;
 }
 backwardSearchStack.removeFirst();
 return null;
 }
 
 visited.add(node); 
 for (N parent : backwardExpander.expand(node)) {
 N meetingNode = depthLimitedSearchBackward(parent,
 depth - 1, 
 visited);
 if (meetingNode != null) {
 return meetingNode;
 } 
 }
 backwardSearchStack.removeFirst();
 return null;
 }
 private List<N> buildPath(N source, N meetingNode) {
 List<N> path = new ArrayList<>();
 List<N> prefixPath = 
 new BidirectionalIterativeDeepeningDepthFirstSearch<N>()
 .search(source, 
 meetingNode, 
 forwardExpander, 
 backwardExpander);
 path.addAll(prefixPath);
 path.remove(path.size() - 1);
 path.addAll(backwardSearchStack);
 return path;
 }
}

Typical output

I usually see something like this:

<<< runGridBenchmark() >>>
IterativeDeepeningAStar in 2 milliseconds. Path length: 20
BIDDFS in 11 milliseconds. Path length: 20
Seed = 1750572292356
Source node: io.github.coderodde.libid.demo.RubiksCubeNode@489f1f6a
Target node: io.github.coderodde.libid.demo.RubiksCubeNode@f1e35bea
BIDDFS path (24 ms):
 io.github.coderodde.libid.demo.RubiksCubeNode@489f1f6a
 io.github.coderodde.libid.demo.RubiksCubeNode@3d6106ea
 io.github.coderodde.libid.demo.RubiksCubeNode@3d3b8e6a
 io.github.coderodde.libid.demo.RubiksCubeNode@f5864bea
 io.github.coderodde.libid.demo.RubiksCubeNode@f569346a
 io.github.coderodde.libid.demo.RubiksCubeNode@f1e35bea
IDDFS path (856 ms):
 io.github.coderodde.libid.demo.RubiksCubeNode@489f1f6a
 io.github.coderodde.libid.demo.RubiksCubeNode@3d6106ea
 io.github.coderodde.libid.demo.RubiksCubeNode@3d3b8e6a
 io.github.coderodde.libid.demo.RubiksCubeNode@f5864bea
 io.github.coderodde.libid.demo.RubiksCubeNode@f569346a
 io.github.coderodde.libid.demo.RubiksCubeNode@f1e35bea
Bidirectional BFS path (45 ms):
 io.github.coderodde.libid.demo.RubiksCubeNode@489f1f6a
 io.github.coderodde.libid.demo.RubiksCubeNode@3d6106ea
 io.github.coderodde.libid.demo.RubiksCubeNode@3d3b8e6a
 io.github.coderodde.libid.demo.RubiksCubeNode@f5864bea
 io.github.coderodde.libid.demo.RubiksCubeNode@f569346a
 io.github.coderodde.libid.demo.RubiksCubeNode@f1e35bea
Algorithms returns correct paths: true
<<< runGeneralGraphBenchmark() >>>
Nodes are ready.
Actual edge count: 13999938
Seed = 1751615254340
BidirectionalIterativeDeepeningDepthFirstSearch in 6 millseconds. . Path length: 7
IterativeDeepeningDepthFirstSearch in 727 millseconds. Path length: 7
io.github.coderodde.libid.impl.BreadthFirstSearch in 5132 millseconds. Path length: 7
BidirectionalBreadthFirstSearch in 22 millseconds. Path length: 7
Algorithms agree: true
-----
.
.
.
<<< run15PuzzleGraphBenchmark() >>>
Seed = 1751615314783
BreadthFirstSearch in 1996 milliseconds. Path length: 19
BidirectionalIterativeDeepeningDepthFirstSearch in 3 milliseconds. Path length: 19
IterativeDeepeningAStar in 2 milliseconds. Path length: 19
BidirectionalBreadthFirstSearch in 8 milliseconds. Path length: 19
Algorithms agree: true

Critique request

What do you think? Is my implementation correct?

asked Jul 4 at 7:40
\$\endgroup\$

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.