I'm creating a 2d top down game where the enemy AI is constantly following the player and avoiding obstacles. I did some research about path-finding algorithms and I decided to implement the breadth first search, but for some reason, the xy coordinates of the path are reversed, even though the grid is correct.
Code:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BFSTest {
// 1 = normal node
// 0 = obstacle
// S = start
// D = destination
private static char[][] nodes = {
{'S', '1', '1', '1'},
{'0', '0', '0', '1'},
{'0', '0', '0', '1'},
{'1', '1', '1', 'D'}
};
public static void main(String[] args) {
shortestPath();
}
public static List<Node> shortestPath() {
// key node, value parent
Map<Node, Node> parents = new HashMap<Node, Node>();
Node start = null;
Node end = null;
// find the start node
for (int row = 0; row < nodes.length; row++) {
for (int column = 0; column < nodes[row].length; column++) {
if (nodes[row][column] == 'S') {
start = new Node(row, column, nodes[row][column]);
break;
}
}
}
if (start == null) {
throw new RuntimeException("can't find start node");
}
// traverse every node using breadth first search until reaching the destination
List<Node> temp = new ArrayList<Node>();
temp.add(start);
parents.put(start, null);
boolean reachDestination = false;
while (temp.size() > 0 && !reachDestination) {
Node currentNode = temp.remove(0);
List<Node> children = getChildren(currentNode);
for (Node child : children) {
// Node can only be visited once
if (!parents.containsKey(child)) {
parents.put(child, currentNode);
char value = child.getValue();
if (value == '1') {
temp.add(child);
} else if (value == 'D') {
temp.add(child);
reachDestination = true;
end = child;
break;
}
}
}
}
if (end == null) {
throw new RuntimeException("can't find end node");
}
// get the shortest path
Node node = end;
List<Node> path = new ArrayList<Node>();
while (node != null) {
path.add(0, node);
node = parents.get(node);
}
printPath(path);
return path;
}
private static List<Node> getChildren(Node parent) {
List<Node> children = new ArrayList<Node>();
int x = parent.getX();
int y = parent.getY();
if (x - 1 >= 0) {
Node child = new Node(x - 1, y, nodes[x - 1][y]);
children.add(child);
}
if (y - 1 >= 0) {
Node child = new Node(x, y - 1, nodes[x][y - 1]);
children.add(child);
}
if (x + 1 < nodes.length) {
Node child = new Node(x + 1, y, nodes[x + 1][y]);
children.add(child);
}
if (y + 1 < nodes[0].length) {
Node child = new Node(x, y + 1, nodes[x][y + 1]);
children.add(child);
}
return children;
}
private static void printPath(List<Node> path) {
for (int row = 0; row < nodes.length; row++) {
for (int column = 0; column < nodes[row].length; column++) {
String value = nodes[row][column] + "";
// mark path with X
for (int i = 1; i < path.size() - 1; i++) {
Node node = path.get(i);
if (node.getX() == row && node.getY() == column) {
value = "X";
break;
}
}
if (column == nodes[row].length - 1) {
System.out.println(value);
} else {
System.out.print(value + " ");
}
}
}
System.out.println("Path: " + path);
}
}
class Node {
private int x;
private int y;
private char value;
public Node(int x, int y, char value) {
this.x = x;
this.y = y;
this.value = value;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public char getValue() {
return value;
}
#Override
public String toString() {
return "(x: " + x + " y: " + y + ")";
}
#Override
public int hashCode() {
return x * y;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (this.getClass() != o.getClass()) return false;
Node node = (Node) o;
return x == node.x && y == node.y;
}
/* Output:
* S X X X
* 0 0 0 X
* 0 0 0 X
* 1 1 1 D
* Path: [(x: 0 y: 0), (x: 0 y: 1), (x: 0 y: 2), (x: 0 y: 3), (x: 1 y: 3), (x: 2 y: 3), (x: 3 y: 3)]
*/
}
Thanks!
Think about this for a moment. Remember that your first array index is row, and the second is column. Now, in terms of conventional x and y:
the y index always refers to the row (because you count rows vertically);
the x index always refers to the column (because you count columns horizontally).
So, the correct way to index your grid is nodes[y][x]
Related
I have the below code for a A* pathfinder, however it is taking upwards of 10 minutes to find a solution using a simple 1024 x 1024 array.
I had to comment out //Collections.sort(this.openList); as it was throwing a comparison method violates its general contract! error when running.
Is the algorithm correct and any idea why the bottleneck? Some people using C++ are getting a response time of 40ms, not 10+ mins!
When using the maze array it does it in the blink of an eye, but thats using something like a 14x10 array, rather than 1024 from the collisionMap.
Is the method flawed in some way? Or using the wrong data structures?
import java.util.List;
import javax.imageio.ImageIO;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.GraphicsConfiguration;
import java.awt.Paint;
import java.awt.image.BufferedImage;
import java.io.Console;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
class AStarTwo {
// Closed list, open list and calculatedPath lists
private final List<Node> openList;
private final List<Node> closedList;
private final List<Node> calcPath;
// Collision Map to store tha map in
private final int[][] collisionMap;
// Current node the program is executing
private Node currentNode;
// Define the start and end coords
private final int xstart;
private final int ystart;
private int xEnd, yEnd;
// Node class
static class Node implements Comparable {
public Node parent;
public int x, y;
public double g;
public double h;
Node(Node parent, int xpos, int ypos, double g, double h) {
this.parent = parent;
this.x = xpos;
this.y = ypos;
this.g = g;
this.h = h;
}
// Compare f value (g + h)
#Override
public int compareTo(Object o) {
Node that = (Node) o;
return (int)((this.g + this.h) - (that.g + that.h));
}
}
// construct and initialise
public AStarTwo(int[][] collisionMap, int xstart, int ystart) {
this.openList = new ArrayList<>();
this.closedList = new ArrayList<>();
this.calcPath = new ArrayList<>();
this.collisionMap = collisionMap;
this.currentNode = new Node(null, xstart, ystart, 0, 0);
this.xstart = xstart;
this.ystart = ystart;
}
// returns a List<> of nodes to target
public List<Node> findPathTo(int xTo, int yTo) {
this.xEnd = xTo;
this.yEnd = yTo;
// Add this to the closed list
this.closedList.add(this.currentNode);
// Add neighbours to openList for iteration
addNeigborsToOpenList();
// Whilst not at our target
while (this.currentNode.x != this.xEnd || this.currentNode.y != this.yEnd) {
// If nothing in the open list then return with null - handled in error message in main calling func
if (this.openList.isEmpty()) {
return null;
}
// get the lowest f value and add it to the closed list, f calculated when neighbours are sorted
this.currentNode = this.openList.get(0);
this.openList.remove(0);
this.closedList.add(this.currentNode);
addNeigborsToOpenList();
}
// add this node to the calculated path
this.calcPath.add(0, this.currentNode);
while (this.currentNode.x != this.xstart || this.currentNode.y != this.ystart) {
this.currentNode = this.currentNode.parent;
this.calcPath.add(0, this.currentNode);
}
return this.calcPath;
}
// Searches the current list for neighbouring nodes returns bool
private static boolean checkNeighbourHasBeenSearched(List<Node> array, Node node) {
return array.stream().anyMatch((n) -> (n.x == node.x && n.y == node.y));
}
// Calculate distance from current node to the target
private double distance(int dx, int dy) {
return Math.hypot(this.currentNode.x + dx - this.xEnd, this.currentNode.y + dy - this.yEnd); // return hypothenuse
}
// Add neighbouring nodes to the open list to iterate through next
#SuppressWarnings("unchecked")
private void addNeigborsToOpenList() {
Node node;
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
node = new Node(this.currentNode, this.currentNode.x + x, this.currentNode.y + y, this.currentNode.g, this.distance(x, y));
// if we are not on the current node
if ((x != 0 || y != 0)
&& this.currentNode.x + x >= 0 && this.currentNode.x + x < this.collisionMap[0].length // check collision map boundaries
&& this.currentNode.y + y >= 0 && this.currentNode.y + y < this.collisionMap.length
&& this.collisionMap[this.currentNode.y + y][this.currentNode.x + x] != -1) { // check if tile is walkable (-1)
// and finally check we haven't already searched the nodes
if(!checkNeighbourHasBeenSearched(this.openList, node) && !checkNeighbourHasBeenSearched(this.closedList, node)){
node.g = node.parent.g + 1.; // Horizontal/vertical cost = 1.0
node.g += collisionMap[this.currentNode.y + y][this.currentNode.x + x]; // add movement cost for this square
// Add diagonal movement cost sqrt(hor_cost² + vert_cost²) + 0.4
if (x != 0 && y != 0) {
node.g += .4;
}
// Add the node to the List<>
this.openList.add(node);
}
}
}
}
// sort in ascending order
//Collections.sort(this.openList);
}
public static void main(String[] args) {
int [][] maze =
{ {1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,-1,0,-1,0,1,0,0,0,0,0,1},
{1,0,-1,0,0,0,1,0,1,1,1,0,1},
{1,0,0,0,-1,-1,-1,0,0,0,0,0,1},
{1,0,1,0,0,0,0,0,1,1,1,0,1},
{1,0,1,0,-1,-1,-1,0,1,0,0,0,-1},
{1,0,-1,0,-1,0,0,0,-1,-1,-1,0,-1},
{1,0,1,0,-1,-1,-1,0,1,0,-1,0,-1},
{1,0,0,0,0,0,0,0,0,0,1,0,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1}
};
// Define the size of the grid
final int sizeOf = 20;
int[][] collisionMap = new int[sizeOf][];
for(int i=0;i < sizeOf; i++) {
// -1 = blocked
// 0+ = cost
collisionMap[i] = new int[sizeOf];
}
// set the value of the nodes
for (int k = 0; k < sizeOf; k++) {
for (int j = 0; j < sizeOf; j++) {
if(j == 30 && k < 100) {
collisionMap[k][j] = -1;
} else if (j == 50 && k > 230) {
collisionMap[k][j] = -1;
}else {
collisionMap[k][j] = 0;
}
}
}
AStarTwo as = new AStarTwo(maze, 9, 9);
List<Node> path = as.findPathTo(0,0);
if(path == null) {
System.out.println("Unable to reach target");
}
// create image buffer to write output to
BufferedImage img = new BufferedImage(sizeOf, sizeOf, BufferedImage.TYPE_INT_RGB);
// Set colours
int r = 255;
int g = 0;
int b = 0;
int colRed = (r << 16) | (g << 8) | b;
r = 0;
g = 255;
b = 0;
int colGreen = (r << 16) | (g << 8) | b;
r = 0;
g = 0;
b = 255;
int colBlue = (r << 16) | (g << 8) | b;
r = 255;
g = 255;
b = 255;
int colWhite = (r << 16) | (g << 8) | b;
int i = 0;
int j = 0;
if (path != null) {
path.forEach((n) -> {
System.out.print("[" + n.x + ", " + n.y + "] ");
maze[n.y][n.x] = 2;
});
for (int[] maze_row : maze) {
for (int maze_entry : maze_row) {
switch (maze_entry) {
// normal tile
case 0:
img.setRGB(j, i, colWhite);
break;
// final path
case 2:
img.setRGB(j, i, colBlue);
break;
// Object to avoid
case -1:
img.setRGB(j, i, colRed);
break;
// Any other value
default:
img.setRGB(j, i, colWhite);
}
j++;
}
// count j - reset as if it were a for loop
if(i != 12) {
j=0;
}
i++;
System.out.println();
}
}
// output file
File f = new File("aStarPath.png");
try {
ImageIO.write(img, "PNG", f);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("i: " + i + ", j: " + j);
}
}
I suspect your problem is this line:
return array.stream().anyMatch((n) -> (n.x == node.x && n.y == node.y));
which is called around O(n^2) times and will take time proportional to the size of the array (which will also be O(n^2) in the worst case for a n by n maze).
You want a faster method of performing this test.
For example:
Use a set to hold the open and closed lists instead of list
Or use an extra field in the node structure to indicate if it is in the open or closed list
I have a 2D matrix.
Given a 2D matrix where some of the elements are filled with '1' and the rest of the elements are filled with '0', except 2 elements, of which one is S (start point) and D (endpoint). Here '0' means you cannot traverse to that particular point. From a cell you can either traverse to left, right, up or down. Given two points in the matrix find the shortest path between these points.
One of the shortest paths (from S to D both exclusive) is: [(3, 2), (3, 1), (2, 1), (2, 0)]. Return null if there is no path between S and D.
I have writtes a piece of code which returns the distance to reach from S to D, my method returns int but how to return as expected output?
My code:
public class ShortestPath {
public static void main(String args[])
{
char[][] matrix = {
{'S', '0', '1', '1'},
{'1', '1', '0', '1'},
{'0', '1', '1', '1'},
{'1', '0', 'D', '1'}
};
int path = pathExists(matrix);
System.out.println(path);
}
private static int pathExists(char[][] matrix) {
Node source = new Node(0, 0, 0);
Queue<Node> queue = new LinkedList<Node>();
queue.add(source);
while(!queue.isEmpty()) {
Node poped = queue.poll();
if(matrix[poped.x][poped.y] == 'D' ) {
return poped.distanceFromSource;
}
else {
matrix[poped.x][poped.y]='0';
List<Node> neighbourList = addNeighbours(poped, matrix);
queue.addAll(neighbourList);
}
}
return -1;
}
private static List<Node> addNeighbours(Node poped, char[][] matrix) {
List<Node> list = new LinkedList<Node>();
if((poped.x-1 > 0 && poped.x-1 < matrix.length) && matrix[poped.x-1][poped.y] != '0') {
list.add(new Node(poped.x-1, poped.y, poped.distanceFromSource+1));
}
if((poped.x+1 > 0 && poped.x+1 < matrix.length) && matrix[poped.x+1][poped.y] != '0') {
list.add(new Node(poped.x+1, poped.y, poped.distanceFromSource+1));
}
if((poped.y-1 > 0 && poped.y-1 < matrix.length) && matrix[poped.x][poped.y-1] != '0') {
list.add(new Node(poped.x, poped.y-1, poped.distanceFromSource+1));
}
if((poped.y+1 > 0 && poped.y+1 < matrix.length) && matrix[poped.x][poped.y+1] != '0') {
list.add(new Node(poped.x, poped.y+1, poped.distanceFromSource+1));
}
return list;
}
}
class Node {
int x;
int y;
int distanceFromSource;
Node(int x, int y, int dis) {
this.x = x;
this.y = y;
this.distanceFromSource = dis;
}
}
You are essentially implementing BFS (Breadth first search) to detect the existence of a path from the source (S) to the destination (D). All you need to trace the path is maintain a parent Node in your Node definition.
Set the starting node's parent to null. Then, as your discover nodes in your BFS from the current node, set the parent of the discovered node to the current node.
Now, if your search is successful (i.e. you hit D in your search), just traverse the chain of parent nodes backwards from D until you hit S, throwing the visited parents into a stack.
Finally just keep popping the stack until it turns empty to get the nodes on the path from S to D.
you are getting only the distance because you are only returning the distance between source and Destination.
follow these to solve and print the route as well;
algo 1:-
just print the node value when you are updating the distance calculation.
algo 2:-
1. create a queue to store the nodes.
2. insert the node point of S to queue
3. keep adding to the node value to queue when you are adding the value to distance. unless reaching to 'D'
4. now just print the nodes from the queue which will print the path structure.
class Cello {
int row;
int col;
public Cello(int rowIndex, int colIndex) {
super();
this.row = rowIndex;
this.col = colIndex;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Cello cell = (Cello) o;
return row == cell.row &&
col == cell.col;
}
#Override
public int hashCode() {
return Objects.hash(row, col);
}
}
public class ShortestPathWithParentChildMap {
public static void main(String[] args) {
char[][] grid2 = {{'S', '0', '1', '1'},
{'1', '1', '0', '1'},
{'0', '1', '1', '1'},
{'1', '0', 'D', '1'}};
List<int[]> path = shortestPath(grid2);
System.out.println("Path length:" + (path.size() - 1));
path.stream().forEach(i -> {
System.out.println("{" + i[0] + "," + i[1] + "}");
});
}
private static void bfs(char[][] grid, Cello start, List<int[]> path) {
int[] xDirs = new int[] {0,0,1, -1};
int[] yDirs = new int[] {1,-1, 0, 0};
Queue<Cello> bfsQueue = new LinkedList<>();
bfsQueue.add(start);
HashMap<Cello, Cello> parentMap = new HashMap<>();
boolean[][] visited = new boolean[grid.length][grid[0].length];
Cello endCell = null;
while(!bfsQueue.isEmpty()) {
boolean flag = false;
Cello from = bfsQueue.poll();
for (int k = 0; k < xDirs.length; ++k) {
int nextX = from.row + xDirs[k];
int nextY = from.col + yDirs[k];
if (nextX < 0 || nextX >= grid.length || nextY < 0
|| nextY >= grid[0].length || grid[nextX][nextY] == '0'
|| visited[nextX][nextY]) {
continue;
}
visited[nextX][nextY] = true;
Cello nextCell = new Cello(nextX, nextY);
bfsQueue.add(nextCell);
//we need a way to determine from where we have reached here
//storing the child to parent mapping, this will be used to retrieve the entire path
parentMap.put(nextCell, from);
//if (grid[nextX][nextY] == 'E')
if (grid[nextX][nextY] == 'D') {
endCell = new Cello(nextX, nextY);
flag = true;
break;
}
}
if (flag) {
break;
}
}
Stack<Cello> stack = new Stack<>();
stack.push(endCell);
//build the path from destination to source
while (true) {
Cello fromCell = parentMap.get(endCell);
stack.push(fromCell);
if (fromCell == start) break;
endCell = fromCell;
}
//reverse the above path and convert as List<int[]>
while (!stack.isEmpty()) {
Cello p = stack.pop();
path.add(new int[] {p.row, p.col});
}
}
private static List<int[]> shortestPath(char[][] grid) {
ArrayList<int[]> path = new ArrayList<>();
for (int i = 0; i < grid.length; ++i) {
for (int j = 0; j < grid[0].length; ++j) {
if (grid[i][j] == 'S') {
bfs(grid, new Cello(i, j), path);
}
}
}
return path;
}
}
Output is:
Path length:5
{0,0}
{1,0}
{1,1}
{2,1}
{2,2}
{3,2}
My problem is that the movement cost(G cost) of my node and heuristic is inaccurate it does not match with the picture.
Here is the image of what I'm following.There are three labels here and the movement cost is labelled at the bottom left and the heuristic is at bottom right. Label at top-left is the F = H + G
Here is my output. As you can see the movement cost is not the same as the desired output. The red circle is the goal node.
Also the same with my Heuristic cost.
public class AStarPathFinder implements PathFinder {
private List<Node> open = new ArrayList<Node>();
private List<Node> close = new ArrayList<Node>();
private Node[][] nodes;
private TileMap map;
private Heuristic heuristic;
public AStarPathFinder(TiledMapStage mapStage, Heuristic heuristic) {
this.heuristic = heuristic;
nodes = mapStage.getNodes();
map = mapStage.getMap();
}
#Override
public Path findPath(int startX, int startY, int goalX, int goalY) {
clearNodes();
Node goal = nodes[goalX][goalY];
Node current = nodes[startX][startY];
open.add(current);
while (!open.isEmpty()) {
current = getLowestFcost(open);
open.remove(current);
close.add(current);
if (current == goal) {
Path path = new Path();
while (current != null) {
path.add(current);
current = current.parent;
}
return path;
}
// neighbors of current
for (int x = -1; x < 2; x++) {
for (int y = -1; y < 2; y++) {
int dx = current.x + x;
int dy = current.y + y;
if (map.isValidLocation(dx, dy)) {
if (!map.isWalkable(nodes[dx][dy], x, y) || close.contains(nodes[dx][dy]))
continue;
float newScore = movementCost(current.g, isDiagonal(x, y));
if (!open.contains(nodes[dx][dy])) {
open.add(nodes[dx][dy]);
} else if (newScore >= nodes[dx][dy].g) continue;
nodes[dx][dy].g = newScore;
nodes[dx][dy].h = heuristic.estimate(nodes[dx][dy], goal);
nodes[dx][dy].f = nodes[dx][dy].g + nodes[dx][dy].h;
nodes[dx][dy].parent = current;
nodes[dx][dy].label.setText((int) nodes[dx][dy].g + "");
}
}
}
}
return null;
}
private Node getLowestFcost(List<Node> open) {
Node lowestNode = open.get(0);
for (int i = 0; i < open.size(); i++) {
if (open.get(i).f <= lowestNode.f && open.get(i).h < lowestNode.h) {
lowestNode = open.get(i);
}
}
return lowestNode;
}
private boolean isDiagonal(int x, int y) {
return (x == -1 && y == 1 ||
x == 1 && y == 1 ||
x == 1 && y == -1 ||
x == -1 && y == -1);
}
private float movementCost(float cost, boolean diagonal) {
return diagonal ? cost + 14 : cost + 10;
}
#Override
public void clearNodes() {
for (int i = 0; i < map.getTileWidth(); i++) {
for (int j = 0; j < map.getTileHeight(); j++) {
if (nodes[i][j].cell != null) {
nodes[i][j].label.setText("");
nodes[i][j].f = 0;
nodes[i][j].h = 0;
nodes[i][j].g = 0;
nodes[i][j].arrow.setDrawable("cursor");
nodes[i][j].arrow.setVisible(false);
nodes[i][j].parent = null;
}
}
}
close.clear();
open.clear();
}
}
Here is the pseudocode that I'm following. Also my heuristic is a diagonal distance
It looks like your problem is in the isWalkable method of your TileMap map variable.
The image you're following doesn't allow to pass diagonally alongside a wall, where your algorithm does.
You can see this because the score gets added with 14 as follows: 14 + 14 = 28. While you expected it to go as follows: 14 + 10 (going down first) + 10 (going right) = 34.
I hope I explained your problem clearly. I don't know your implementation of isWalkable, so I can't provide a full solution but I hope I have pointed you in the right direction.
I am trying to implement a B-tree and stuck on the deletion method. When the number of key-value pairs in a node is less than half of a predefined number M, i.e., M / 2, then it needs to combine the consecutive nodes. I am encountering an IndexOutOfBound error, which happens when I needs to delete an element in the first children of the node. Here is the deletion method:
public Value delete(Key key){
return (Value) delete(root, key, HT).value;
}
private Entry delete(Node h, Key key, int ht){
Entry[] children = h.children;
int j = 0;
if(ht == 0){
for(; j < h.m; j++){
if(children[j].key.equals(key)){
break;
}
}
}else {
for(;j < h.m; j++){
if(j + 1 == h.m || key.compareTo((Key) children[j + 1].key) < 0){
Entry deleted = delete(children[j].next, key, ht - 1);
if(children[j].next.m < M / 2){
children[j].next = combine(children[j - 1].next, children[j].next);
if(children[j].next.m == 0)
break;
}
return deleted;
}
}
}
Entry toReturn = children[j];
for(int i = j; i < h.m; i++){
children[i] = children[i + 1];
}
h.m--;
N--;
//if(h.m == 0)
//h = null;
return toReturn;
}
private Node combine(Node a, Node b){
int size = a.m;
for (int i = size; i < size + b.m; i++){
a.children[i] = b.children[i - b.m];
a.m++;
}
if(a.m < M)
b.m = 0;
else
b = split(a);
return b;
}
Here is the whole class, the insertion and searching method can be compiled perfectly fine.
public class BTree <Key extends Comparable<Key>, Value>{
//maximum children per B-tree
private static int M;
private Node root;
//height of the B-tree
private int HT;
//number of key-value pairs in the B-tree
private int N;
/***************************************************************
* private class for the node
***************************************************************/
/**
* tree node class
* Entry list is used for all key - value pairs stored in the node
* m is the index of the pair with the largest key
* since its 0 index, the node contains m + 1 key - value pairs
* #author shirleyyoung
*
*/
private static class Node{
private int m;//current number of key-value pairs, as well as the index where next key-value pair will be added
private Entry[] children = new Entry[M];//the array for the key value pair, different from the children of a tree
private Node(int k){
m = k;
}
}
/**
* The structure to store the key-value pairs
* #author shirleyyoung
*
*/
private static class Entry implements Comparable<Entry>{
private Comparable key;
private Object value;
private Node next;
public Entry(Comparable key, Object value, Node next){
this.key = key;
this.value = value;
this.next = next;
}
public int compareTo(Entry e) {
return key.compareTo(e.key);
}
}
/*******************************************************
* constructor
* M is the maximum key-value pairs a node can store,
* it should be an even value for the sake of split
* #param M
********************************************************/
public BTree(int M){
if(M % 2 != 0)
throw new IllegalArgumentException("Maximum children must be an even number!");
this.M = M;
HT = 0;
N = 0;
root = new Node(0);
}
/******************************************************
* search for an object
*******************************************************/
public Value get(Key key){
return (Value) search(root, key, HT).value;
}
//ht:current height
private Entry search(Node x, Key key, int ht){
Entry[] children = x.children;
//root
if(ht == 0){
for(int j = 0; j < x.m; j++){
if(key.equals(children[j].key))
return children[j];
}
} else{//internal nodes
for(int j = 0; j < x.m; j++){
if(j == x.m - 1|| key.compareTo((Key) children[j + 1].key) < 0)
return search(children[j].next, key, ht - 1);
}
}
return null;
}
/***************************************************
* Insertion
***************************************************/
public void put(Key key, Object value){
Entry tmp = search(root, key, HT);
if(tmp != null){
tmp.value = value;
return;
}
Node u = insert(root, key, value, HT);
N++;
if(u == null) return;
//if the previous node is split, initialize a new node as the root
Node t = new Node(2);//2 children
t.children[0] = new Entry(root.children[0].key, null, root);
t.children[1] = new Entry(u.children[0].key, null, u);
root = t;
HT++;
}
private Node insert(Node h, Key key, Object value, int ht){
int j;
//the key-value pair to be added
Entry toAdd = new Entry(key, value, null);
if(ht == 0){
//find the correct place to insert the value
for(j = 0; j < h.m; j++){
if(key.compareTo((Key) h.children[j].key) < 0)
break;
}
}else {
for(j = 0; j < h.m; j++){
if((j + 1 == h.m) || key.compareTo((Key) h.children[j + 1].key) < 0){
Node u = insert(h.children[j++].next, key, value, ht - 1);
if(u == null) return null;
toAdd.key = u.children[0].key;
toAdd.next = u;
break;
}
}
}
//shift the larger nodes right
for(int i = h.m; i > j; i--) h.children[i] = h.children[i - 1];
h.children[j] = toAdd;
h.m++;
if(h.m < M) return null;
else return split(h);
}
private Node split(Node h){
Node t = new Node(M / 2);
h.m = M / 2;
for(int j = 0; j < M / 2; j++){
t.children[j] = h.children[M / 2 + j];
}
return t;
}
public Value delete(Key key){
return (Value) delete(root, key, HT).value;
}
private Entry delete(Node h, Key key, int ht){
Entry[] children = h.children;
int j = 0;
if(ht == 0){
for(; j < h.m; j++){
if(children[j].key.equals(key)){
break;
}
}
}else {
for(;j < h.m; j++){
if(j + 1 == h.m || key.compareTo((Key) children[j + 1].key) < 0){
Entry deleted = delete(children[j].next, key, ht - 1);
if(children[j].next.m < M / 2){
children[j].next = combine(children[j - 1].next, children[j].next);
if(children[j].next.m == 0)
break;
}
return deleted;
}
}
}
Entry toReturn = children[j];
for(int i = j; i < h.m; i++){
children[i] = children[i + 1];
}
h.m--;
N--;
//if(h.m == 0)
//h = null;
return toReturn;
}
private Node combine(Node a, Node b){
int size = a.m;
for (int i = size; i < size + b.m; i++){
a.children[i] = b.children[i - b.m];
a.m++;
}
if(a.m < M)
b.m = 0;
else
b = split(a);
return b;
}
public String toString(){
return toString(root, HT, "") + "\n";
}
private String toString(Node h, int ht, String indent){
String s = "";
Entry[] children = h.children;
if(ht == 0){
for(int j = 0; j < h.m; j++){
s += indent + children[j].key + "-" + children[j].value + " ";
}
} else{
for(int j = 0; j < h.m; j++){
System.out.println("curr node: " + children[j].key);
s += indent + "\n(" + children[j].key + ")\n";
s += toString(children[j].next, ht - 1, indent + " ");
}
}
return s;
}
public int size(){
return N;
}
public int height(){
return HT;
}
}
Thanks a lot!
assume that we have a board like this :
and we want to find the most profitable path from left to right with the following movement pattern :
for example in this board the most profitable path is :
i.e. {2, 0} -> {2, 1} -> {3, 2} -> {3, 3}
I wrote the following code :
import java.util.*;
public final class Board {
private final int[][] board;
private final int n;
public Board(int n) {
board = new int[n][n];
this.n = n;
generateBoard();
}
public static class Node {
public int x;
public int y;
public int value;
public Node(int x, int y, int value) {
this.x = x;
this.y = y;
this.value = value;
}
#Override
public String toString() {
return "{" + x + ", " + y + "}";
}
}
public static class Path implements Comparable<Path> {
private LinkedList<Node> path = new LinkedList<>();
public Path() {
}
public Path(List<Node> nodes) {
this.path.addAll(nodes);
}
public void addLast(Node node) {
path.addLast(node);
}
public void removeLast() {
path.removeLast();
}
public List<Node> get() {
return path;
}
public int getProfit() {
return path.stream().map(node -> node.value).mapToInt(value -> value).sum();
}
#Override
public String toString() {
return path.toString();
}
#Override
public int compareTo(Path o) {
return getProfit() > o.getProfit() ? 1 : -1;
}
}
public void generateBoard() {
Random random = new Random();
for (int x = 0; x < n; x++) {
for (int y = 0; y < n; y++) {
board[x][y] = random.nextInt(200) + 1 - 100;
}
}
}
public void printBoard() {
for (int[] b : board) {
System.out.println(Arrays.toString(b));
}
}
public Path findTheMostProfitablePath() {
TreeSet<Path> paths = new TreeSet<>();
for (int x = 0; x < n; x++) {
visit(new Node(x, 0, board[x][0]), paths);
}
return paths.last();
}
private void visit(Node root, Collection<Path> paths) {
Stack<Node> stack = new Stack<>();
stack.add(root);
Node node;
Path currentPath = new Path();
while (!stack.isEmpty()) {
node = stack.pop();
currentPath.addLast(node);
List<Node> children = getChildren(node.x, node.y);
if (children == null) {
paths.add(new Path(currentPath.get()));
currentPath.removeLast();
} else {
stack.addAll(children);
}
}
}
private List<Node> getChildren(int x, int y) {
if (y == n - 1) {
return null;
}
y++;
List<Node> children = new LinkedList<>();
if (x == 0) {
children.add(new Node(x, y, board[x][y]));
children.add(new Node(x + 1, y, board[x + 1][y]));
} else if (x == n - 1) {
children.add(new Node(x - 1, y, board[x - 1][y]));
children.add(new Node(x, y, board[x][y]));
} else {
children.add(new Node(x - 1, y, board[x - 1][y]));
children.add(new Node(x, y, board[x][y]));
children.add(new Node(x + 1, y, board[x + 1][y]));
}
return children;
}
public static void main(String[] args) {
Board board = new Board(3);
System.out.println("Board :");
board.printBoard();
System.out.println("\nThe most profitable path :\n" + board.findTheMostProfitablePath());
}
}
but it fails to find the path...
output :
Board :
[-7, 1, 18]
[88, 56, 18]
[-18, -13, 100]
The most profitable path :
[{1, 0}, {2, 1}, {1, 1}, {2, 2}]
whats's wrong with my code?
I know this is not the best algorithm to find the most profitable path and also it is very slow.
in a n*n board the number of paths would be :
n * 2 ^ (n-1) < number of paths < n * 3 ^ (n-1)
and in this algorithm we are checking all paths to find the most profitable one.
Thanks.
You are solving the Longest Path Problem, which is usually NP-Complete. However, in your case, you actually have a Directed Acyclic Graph, and thus an efficient solution is available with the following recurrence formula:
D(i,-1) = D(i,m) = -INFINITY //out of bounds
D(-1,j) = 0 [ j>=0 ] //succesful path
D(i,j) = max{ D(i-1, j-1) , D(i-1,j),D(i-1,j+1)} + A[i][j] //choose best out of 3
By applying Dynamic Programming techniques to implement this formula, an efficient O(n*m) optimal solution can be achieved.
When you are done, a simple scan on the rightest column will find you the destination of the "most profitable" path, and you can reconstruct the actual path from there by going back through the above and writing down each decision at each step.