2D Nearest Neighbor Search - algorithm

Starting from the green square, I want an efficient algorithm to find the nearest 3 x 3 window with no red squares, only blue squares. The algorithm doesn't need to find exactly the closest 3 x 3 window, but it should find a 3 x 3 all-blue window close to the green square (assuming one exists). I thought about implementing this as a recursive breadth-first search, but that solution would involve re-checking the same square many times. Posting this to see if someone knows of a more efficient solution. Cost to check a given square is constant and cheap, but I want to minimize the execution time of the algorithm as much as possible (practical application of this will involve finding a 3x3 "clear" / all-blue window within a much larger 2D search area).
Here's an example solution, but I don't think it's optimal. It's actually a depth-first search that I will have to restructure to convert to a breadth-first, but I need to think a bit more about how to do that (one way would be to make each point an object that expands to neighboring points, then iterate multiple times across those points to children, visit those children before allowing those children to generate more children). Point is that I think there's a more efficient and common way to do this so I'm trying to avoid reinventing the wheel.
public class Search2D {
private TreeSet<Point> centerpointscheckedsofar;
private Point Search(Point centerpoint) {
if(centerpointscheckedsofar.contains(centerpoint)) {
return null;
}
if(isWithinBounds(centerpoint)) {
if(checkCenterPoint(centerpoint)) {
centerpointscheckedsofar.add(centerpoint);
return null;
}
Point result = Search(getPoint(-1, -1, centerpoint));
if(result != null) return result;
result = Search(getPoint(-1, 0, centerpoint));
if(result != null) return result;
result = Search(getPoint(-1, 1, centerpoint));
if(result != null) return result;
result = Search(getPoint(0, -1, centerpoint));
if(result != null) return result;
result = Search(getPoint(0, 1, centerpoint));
if(result != null) return result;
result = Search(getPoint(1, -1, centerpoint));
if(result != null) return result;
result = Search(getPoint(1, 0, centerpoint));
if(result != null) return result;
result = Search(getPoint(1, 1, centerpoint));
if(result != null) return result;
}
return null;
}
private Point getPoint(int x, int y, Point centerpoint) {
return new Point(centerpoint.x + x, centerpoint.y + y);
}
private boolean checkCenterPoint(Point centerpoint) {
//check here to see if point is valid
return false;
}
private boolean isWithinBounds(Point startPoint) {
//check here to see if point and all neighboring points of 3 x 3 window falls within bounds
return false;
}
}
UPDATE:
Distance measure is not that important, but for simplicity, let's minimize Manhattan distance.
Here's a better algorithm that does not use recursion and will be guaranteed to find the closest solution (or one of the closest solutions if there is a tie). It needs a grid greater than 5 x 5 to work properly, but if you want to search a grid smaller than that, there's probably a more efficient algorithm that can be used. Assumes lowest x-index is 0 and lowest y-index is also 0.
import java.awt.Point;
public class Search2D_v2 {
private boolean[][] bitgrid;
public Search2D_v2() {
bitgrid = new boolean[20][20];
}
public Point search(int centerx, int centery, int maxx, int maxy, int maxsearchsteps) {
//check starting point first, if it works, we're done
if(checkPoint(centerx, centery)) {
return new Point(centerx, centery);
}
int westbound = centerx-1;
boolean keepgoingwest = true;
int eastbound = centerx+1;
boolean keepgoingeast = true;
int southbound = centery-1;
boolean keepgoingsouth = true;
int northbound = centery+1;
boolean keepgoingnorth = true;
//stay within bounds, may move initial search square by 1 east and 1 west
if(westbound <= 0) {
eastbound = 3;
westbound = 1;
}
if(eastbound >= maxx) {
eastbound = maxx - 1;
westbound = maxx - 3;
}
if(southbound == 0) {
northbound = 3;
southbound = 1;
}
if(northbound == maxy) {
northbound = maxy - 1;
southbound = maxy - 3;
}
//always search boundary, we've already searched inside the boundary on previous iterations, expand boundary by 1 step / square for each iteration
for(int i = 0; i < maxsearchsteps && (keepgoingwest || keepgoingeast || keepgoingsouth || keepgoingnorth); i++) {
//search top row
if(keepgoingnorth) { //if we have already hit the north bound, stop searching the top row
for(int x = westbound; x <= eastbound; x++) {
if(checkPoint(x, northbound)) {
return new Point(x, northbound);
}
}
}
//search bottom row
if(keepgoingsouth) {
for(int x = westbound; x <= eastbound; x++) {
if(checkPoint(x, southbound)) {
return new Point(x, southbound);
}
}
}
//search westbound
if(keepgoingwest) {
for(int y = southbound; y <= northbound; y++) {
if(checkPoint(westbound, northbound)) {
return new Point(westbound, y);
}
}
}
//search eastbound
if(keepgoingeast) {
for(int y = southbound; y <= northbound; y++) {
if(checkPoint(eastbound, northbound)) {
return new Point(eastbound, y);
}
}
}
//expand search area by one square on each side
if(westbound - 2 >= 0) {
westbound--;
}
else {
keepgoingwest = false;
}
if(eastbound + 2 <= maxx) {
eastbound++;
}
else {
keepgoingeast = false;
}
if(southbound - 2 >= 0) {
southbound--;
}
else {
keepgoingsouth = false;
}
if(northbound + 2 <= maxy) {
northbound++;
}
else {
keepgoingnorth = false;
}
}
return null; //failed to find a point
}
private boolean checkPoint(int centerx, int centery) {
return !bitgrid[centerx][centery] && //center
!bitgrid[centerx-1][centery-1] && //left lower
!bitgrid[centerx-1][centery] && //left middle
!bitgrid[centerx-1][centery+1] && //left upper
!bitgrid[centerx][centery-1] && //middle lower
!bitgrid[centerx][centery+1] && //middle upper
!bitgrid[centerx+1][centery-1] && //right lower
!bitgrid[centerx+1][centery] && //right middle
!bitgrid[centerx+1][centery+1]; //right upper
}
}

A simple advice would be to mark all the cells you have checked. That way you won't have to check the cells multiple times.
Recursion will definitely take more time than an iteration based approach since it will create a new stack each time you make a new call. If you are trying to find the closest one, prefer BFS over DFS.
I would also suggest making a quick internet research for "Flood Fill Algorithm".

You could spiral outwards from your starting pixel. Whenever you encounter a pixel p that has not been checked, examine the 3x3 environment around p.
For each red pixel r in the environment set the 3x3 environment of r to checked.
If there was no red pixel in the environment you found a solution.

What you're trying to find in a more general sense is a kind of morphological filter of your array.
We can define the filter as a 3x3 sliding window which sets the center of the window to the sum of the array elements within the window. Let blue squares be represented by 1 and red squares be represented by 0.
In this situation, you're trying to find the closest element with a sum value of 9.
Note that one way of solving this problem is slide a 3x3 window across your array so that it covers all possible locations. In this case, you would look at 9*width*height elements. You could then find the nearest sum value of 9 using a breadth-first search in, at most, width*height checks. So the naive time of your algorithm is proportional to 10*width*height
You can reduce this by ensuring that your filter only has to look at one value per focal cell, rather than 9. To do so, generate a summed-area table. Now your time is proportional to 2*width*height.
An example of a summed-area table
You can might be able to make this faster. Each time you find a value of 9, compare it against the location of your green cell at that moment. If most cells are not 9s, this reduces your time to some proportional to width*height.
Hensley et al. (2005)'s paper Fast Summed-Area Table Generation and its Applications explains how to use graphics hardware to generate the summed-area table in O(log n) time. So it's possible to really reduce run-times on this. Nehab et al. (2011)'s paper GPU-efficient recursive filtering and summed-area tables might also be useful (source code): their work suggests that for small windows, such as yours, the direct approach may be most efficient.

I think the easiest way is to use a slightly modified breadth-first search.
If we talk about Manhattan distance, then each square will have maximum 4 neighbors. On each step we check if the number of neighbors is equal to 3 (the fourth neighbor is a square we came from). If so, we check diagonals. Else - continue search.
public class Field3x3 {
private static class Point {
int x, y, distance;
Point previous;
public Point(int x, int y) {
this.x = x;
this.y = y;
this.distance = 0;
this.previous = this;
}
public Point(int x, int y, Point previous) {
this.x = x;
this.y = y;
this.previous = previous;
this.distance = previous.distance + 1;
}
#Override
public String toString() {
return "{x: " + x +", y: " + y + ", distance:" + distance +'}';
}
}
private static Point traverse(int[][] field, int x, int y) {
int i = 0;
Queue<Point> q = new LinkedList<>();
q.add(new Point(x, y));
while (!q.isEmpty()) {
Point p = q.remove();
System.out.print(i++ + ". current: " + p);
if (field[p.y][p.x] == 1) {
field[p.y][p.x] = 2;
List<Point> neighbors = getNeighbors(p, field);
System.out.println(", neighbors: " + neighbors);
if (neighbors.size() == 3 && checkDiagonals(p, field)) return p;
for (Point neighbor : neighbors) {
if (field[neighbor.y][neighbor.x] == 1) {
q.add(neighbor);
}
}
} else System.out.println(", already visited");
}
return null;
}
private static boolean checkDiagonals(Point p, int[][] field) {
return field[p.y - 1][p.x - 1] > 0 && field[p.y + 1][p.x - 1] > 0
&& field[p.y - 1][p.x + 1] > 0 && field[p.y + 1][p.x + 1] > 0;
}
private static List<Point> getNeighbors(Point p, int[][] field) {
List<Point> neighbors = new ArrayList<>();
if (p.y > 0 && field[p.y - 1][p.x] > 0 && p.y <= p.previous.y)
neighbors.add(new Point(p.x, p.y - 1, p));
if (p.y < field.length - 1 && field[p.y + 1][p.x] > 0 && p.y >= p.previous.y)
neighbors.add(new Point(p.x, p.y + 1, p));
if (p.x > 0 && field[p.y][p.x - 1] > 0 && p.x <= p.previous.x)
neighbors.add(new Point(p.x - 1, p.y, p));
if (p.x < field[p.y].length - 1 && field[p.y][p.x + 1] > 0 && p.x >= p.previous.x)
neighbors.add(new Point(p.x + 1, p.y, p));
return neighbors;
}
public static void main(String[] args){
int[][] field = {{1,0,0,1,1,0,1,1,1},
{1,1,1,1,1,1,1,0,1},
{1,1,1,0,1,0,1,1,1},
{0,1,1,1,1,1,1,1,0},
{1,1,1,0,0,1,1,1,0},
{1,0,1,1,1,1,0,1,0},
{1,1,1,1,0,1,1,1,0},
{1,1,1,0,1,1,1,1,0},
{1,1,1,1,0,1,1,1,0}};
System.out.println("Answer: " + traverse(field, 1, 2));
}
}

Related

Minimum Cost Path with Left, Right, Bottom and Up moves allowed

Problem Statement:
Given a square grid of size N, each cell of which contains integer cost which represents a cost to traverse through that cell, we need to find a path from top left cell to bottom right cell by which the total cost incurred is minimum.
From the cell (i,j) we can go (i,j-1), (i, j+1), (i-1, j), (i+1, j).
Note: It is assumed that negative cost cycles do not exist in the input matrix.
Below is the code that I have written:
class Coordinate {
int x;
int y;
public Coordinate(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Coordinate other = (Coordinate) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
}
public class MinCostPath {
public static int minimumCostPath(int[][] grid) {
int R = grid.length;
int C = grid[0].length;
int[][] dist = new int[R][C];
for (int i = 0; i < R; i++) {
for (int j = 0; j < C; j++) {
dist[i][j] = Integer.MAX_VALUE;
}
}
dist[0][0] = grid[0][0];
Queue<Coordinate> q = new LinkedList<>();
q.add(new Coordinate(0, 0));
int[] x = { -1, 1, 0, 0 };
int[] y = { 0, 0, 1, -1 };
while (!q.isEmpty()) {
Coordinate current = q.poll();
for (int i = 0; i < 4; i++) {
int xi = current.getX() + x[i];
int yi = current.getY() + y[i];
if (isSafe(xi, yi, R, C)) {
if (dist[xi][yi] > dist[current.getX()][current.getY()] + grid[xi][yi]) {
dist[xi][yi] = dist[current.getX()][current.getY()] + grid[xi][yi];
Coordinate c = new Coordinate(xi, yi);
if (!q.contains(c))
q.add(c);
}
}
}
}
return dist[R - 1][C - 1];
}
private static boolean isSafe(int xi, int yi, int r, int c) {
return (xi >= 0) && (xi < r) && (yi >= 0) && (yi < c);
}
public static void main(String[] args) {
int[][] grid = { { 9, 4, 9, 9 }, { 6, 7, 6, 4 }, { 8, 3, 3, 7 }, { 7, 4, 9, 10 } };
System.out.println(minimumCostPath(grid));
}
}
It gives 'Runtime Error:Time Limit Exceeded' Error
Below is an article from geeksforgeeks that has the solution.
Minimum Cost Path Problem
What I am not able to understand is why my code is giving 'Time Limit Exceeded' Error while the solution mentioned in the article is working. To my understanding, both the solutions are doing exactly the same thing.
Please help me understand the difference and what optimization is needed in the code that I have written. Thanks in Advance.
It seems from your implementation that you are trying to use Breadth First Search instead of Dijkstra which is the right approach to solve this.
You need to use a priority queue and a map to trace your path.
Also for BFS typically you don't need to do if (! q.contains(node)) that actually unnecessarily consumes time.
what optimization is needed in the code
The problem needs to be reformulated so you can apply the Dijkstra algorithm ( https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm )
That algorithm assumes the links are costed, rather than the nodes ( cells ). Consider two adjacent cells with traversal costs c and d. Travelling from the first to the second adds cost d. Travelling from the second to the first adds cost c. So you can connect the two cells with two unidirectional links of cost c and d. The Dijkstra algorithm will give you the correct cost when the entire path is travelled, minus the the traversal cost of the first cell - which is a constant and can therefor be ignored when finding the optimal path.
I use the boost graph implementation of Dijkstra, which is fast and well tested ( https://www.boost.org/doc/libs/1_76_0/libs/graph/doc/dijkstra_shortest_paths.html ). The API is a challenge if you haven't used the boost library before, so I use a C++ style wrapper to simplify coding ( https://github.com/JamesBremner/PathFinder )
Here is the output from PathFinder on the sample problem you posted
C:\Users\James\code\PathFinder\bin>mazes ..\dat\vivekkumar.txt
Mazes
cellcosts
s9 4 9 9
6 7 6 4
8 3 3 7
7 4 9 e10
4 by 4
0 -> 1 -> 5 -> 9 -> 10 -> 11 -> 15 ->
+---+---+---+---+
| s * |
+ + + + +
| * |
+ + + + +
| * * * |
+ + + + +
| e |
+---+---+---+---+

What makes Breadth First Search algorithm slower than Depth First Search(both are shown below)?

I am using DFS and BFS to solve mouse in a maze problem where the mouse is allowed to move either towards right or upwards. Since the branching factor is just two with the maze being a 5x5 matrix i expected BFS to be way quicker than DFS but turns out to be the other way around.
DFS takes 6793300 nano seconds
BFS takes 26359600 nano seconds
What could possibly explains this difference? Does additional data structure used in BFS cause such overhead?
DFS:
int performMazeSearchDFS(int i, int j, int[][] maze, int destI, int destJ, int size, int moveCount)
{
int result = moveCount;
if (i == destI && j == destJ) {
return moveCount;
}
if(j+1 < size && maze[i][j+1] != 1) {
result = performMazeSearchDFS(i, j + 1, maze, destI, destJ, size, moveCount+1);
if(result > moveCount) {
return result;
}
}
if (i-1 >= 0 && maze[i-1][j] != 1) { // no improvement from previous moves
result = performMazeSearchDFS(i - 1, j, maze, destI, destJ, size, moveCount+1);
if(result > moveCount) {
return result;
}
}
moveCount = moveCount -1;
return moveCount;
}
BFS:
private class Coordinates{
private int i;
private int j;
public Coordinates(int iCord, int jCord){
i= iCord;
j=jCord;
}
public int getI(){return i;}
public int getJ(){return j;}
public boolean equals(Object toCompare){
if(toCompare instanceof Coordinates ){
Coordinates coOrdX = (Coordinates)toCompare;
if(this.i == coOrdX.getI() && this.j == coOrdX.getJ()){
return true;
}
}
return false;
}
}
private class History{
private Coordinates parent;
private Coordinates current;
public History(Coordinates par, Coordinates curr){
parent = par;
current = curr;
}
public Coordinates getParent(){return parent;}
public Coordinates getCurrent(){return current;}
}
public boolean performMazeSearchBFS(int i, int j, int[][] maze, int destI, int destJ, int size) {
LinkedList<History> nodeQ = new LinkedList<>();
HashMap<Coordinates,Boolean> visitedNode = new HashMap<>();
boolean found = false;
nodeQ.add(new History(new Coordinates(-1,-1),(new Coordinates(i,j))));
while(!nodeQ.isEmpty()) {
History currNode = nodeQ.poll();
visitedNode.put(new Coordinates(currNode.getCurrent().getI(),
currNode.getCurrent().getJ()),true);
int m = currNode.current.getI();
int n = currNode.current.getJ();
if(currNode.current.getI() == destI && currNode.current.getJ() == destJ) {
found = true;
break;
}
if (m - 1 >= 0 && maze[m - 1][n] == 0
&& !visitedNode.containsKey(new Coordinates(m - 1, n))) {
if (m - 1 >= 0 && maze[m - 1][n] == 0 ) {
nodeQ.add(new History(new Coordinates(m, n), new Coordinates(m - 1, n)));
}
if (n + 1 < size && maze[m][n] == 0
&& !visitedNode.containsKey(new Coordinates(m, n + 1))) {
nodeQ.add(new History(new Coordinates(m, n), new Coordinates(m, n + 1)));
}
}
return found;
}
The speed really depends on what your are searching for and the stricture of the graph.
If you search all paths in a graph, the speed should be similar.
If you search for nodes close to the root, or you need the shortest path, BFS is likely to be faster.
DFS might be faster if you search node that are deep in the graph, far from the root, and you do not need the shortest path.

Minimax Algorithm doesn't work as expected

I'm currently working on a checkers game against an AI in c#. I have tried to implement the AI using minimax algorithm. Although my function works the moves it selects are not logical at all. I tested it with many plays and algorithm just select bad moves when there are many better option. I don't think its due to the horizon problem because the move it makes have immediate consequences such as loosing a piece without capturing any of the opponents piece.
Som notes about the code:
My function takes a 8x8 2d array of enum Pieces which represents the checkers board.
BlackPlayer is a bool value within the same class with function.
MyPiece(currentPiece) function checks if the currentPiece is the same color with the AI.
Since capture is mandatory in checkers function first checks if the gameState contains any capture moves. If not checks normal moves.
I used alpha-beta pruning to make it more efficient.
I used CloneGameState(gameState) function to copy the 2d array so that original array that represents the game never changes.
public int Minimax (Pieces[,] gameState, int depth, bool is_maximizing, int alpha, int beta)
{
//Base Case - Return the board value
if (depth == 3)
return HeuristicEvaluation(gameState);
Move[] possibleMoves;
int bestValue;
bool currentSide;
if (is_maximizing)
{
bestValue = int.MinValue;
currentSide = BlackPlayer;
}
else
{
bestValue = int.MaxValue;
currentSide = !BlackPlayer;
}
// check forced moves
int moveCount = rules.GetCaptureMoves(gameState,out possibleMoves, currentSide);
// if no forced moves get normal moves
if (moveCount < 1)
moveCount = rules.GetNormalMoves(gameState,out possibleMoves, currentSide);
// traverse moves
for (int i = 0; i < moveCount; i++)
{
Pieces[,] newGameState = ApplyMove(CloneGameState(gameState), possibleMoves[i]);
int newStateValue = Minimax(newGameState, depth + 1, !is_maximizing,alpha, beta);
if (is_maximizing)
{
if (newStateValue > bestValue)
{
bestValue = newStateValue;
if (depth == 0)
bestMove = possibleMoves[i];
if (newStateValue > alpha)
alpha = newStateValue;
if (alpha >= beta)
return bestValue;
}
}
//Evaluation for min
else
{
if (newStateValue < bestValue)
{
bestValue = newStateValue;
if (newStateValue < beta)
beta = newStateValue;
if (alpha >= beta)
return bestValue;
}
}
}
return bestValue;
}
The heuristics function:
public int HeuristicEvaluation(Pieces[,] gameState)
{
int stateValue = 0;
//use loops to check each piece
for (int j = 0; j < 8; j++)
{
int i = 0;
if (j % 2 == 1)
i++;
for (; i < 8; i += 2)
{
Pieces currentPiece = gameState[i, j];
if (currentPiece != Pieces.empty)
{
// if the current piece is mine
if (MyPiece(currentPiece))
{
// check if my piece is a king
if (currentPiece == Pieces.whiteKing || currentPiece == Pieces.blackKing)
stateValue += 80;
// my piece is a man
else
{
stateValue += 30;
// row values, closer to king zone higher the value
if (currentPiece == Pieces.blackMan)
{
// black goes in reverse direction
int y = 7-j;
stateValue += y;
}
else
stateValue += j;
}
// pieces on the edge are safe from capture
if (i == 0 ||i == 7 || j== 0 ||j ==7)
{
stateValue += 10;
}
}
// point reduction for enemy pieces
else
{
if (currentPiece == Pieces.whiteKing || currentPiece == Pieces.blackKing)
stateValue -= 80;
else
{
stateValue -= 20;
// row values, closer to king zone higher the value
if (currentPiece == Pieces.blackMan )
{
// black goes in reverse direction
int y = 7-j;
stateValue -= y;
}
else
stateValue -= j;
}
// pieces on the edge cant be captured
if (i == 0 || i == 7 || j == 0 || j == 7)
{
stateValue -= 10;
}
}
}
}
}
return stateValue;
}
First, I want to point out that your functions Maximizer and Minimizer can be combined in one function Minimax(Pieces, gameState, depth, bool is_maximizing) because their logic is almost the same except the couple of lines of code. So instead of calling Maximizer, you will call Minimax with is_maximizing set to true. And instead of calling Minimizer, just call Minimax with is_maximizing set to false. This will help to avoid repetition and will make your code more readable.
This first point leads us to a mistake in the algorithm. In the Minimize function you recursively call itself, while you should call the Maximize function.
Another point is the way you handle all valid moves in the given position. You don't have to separate processing of capture moves from non-capture ones. The reason is once again that the logic for processing both types of moves is the same. I suggest to create two functions - GenerateValidMoves() and SortValidMoves(). GenerateValidMoves() function will generate a list of all valid moves in the given position. After the moves list was generated, call SortValidMoves() to sort the list so that capture moves are located in the beginning of the list followed by non-capture moves.
Here is a simplified pseudocode for minimax:
Minimax(color, board, depth, is_max):
if ((depth == DEPTH_CUTOFF) or IsTerminalNode()):
return EvalBoard()
best_score = is_max ? -infinity : infinity
valid_moves = GenerateValidMoves(board, color)
for curr_move in valid_moves:
clone_board = board.clone()
clone_board.make_move(curr_move)
int curr_score = Minimax(opposite_color, clone_board, depth + 1, !is_max)
if (is_max) {
if (curr_score > best_score) {
best_score = curr_score
best_move = curr_move
}
} else {
if (curr_score < best_score) {
best_score = curr_score
best_move = curr_move
}
}
return best_score

Generate 10-digit number using a phone keypad

Given a phone keypad as shown below:
1 2 3
4 5 6
7 8 9
0
How many different 10-digit numbers can be formed starting from 1? The constraint is that the movement from 1 digit to the next is similar to the movement of the Knight in a chess game.
For eg. if we are at 1 then the next digit can be either 6 or 8
if we are at 6 then the next digit can be 1, 7 or 0.
Repetition of digits are allowed - 1616161616 is a valid number.
Is there a polynomial time algorithm which solves this problem? The problem requires us to just give the count of 10-digit numbers and not necessarily list the numbers.
EDIT: I tried modeling this as a graph with each digit having 2 or 3 digits as its neighbors. Then I used DFS to navigate upto the depth of 10 nodes and then increment the count of numbers each time I reached the depth of 10. This obviously is not polynomial time. Assuming each digit had just 2 neighbors, this would have required at least 2^10 iterations.
The variable here is the number of digits. I have taken the eg. of 10 digit numbers. It could as well be n-digits.
Sure it can be done in polynomial time. It's an excellent exercise in dynamic programming or memoization.
Lets assume N (the number of digits) equals 10 for the example.
Think of it recursively like this: How many numbers can I construct using 10 digits starting from 1?
Answer is
[number of 9-digit numbers starting from 8] +
[number of 9-digit numbers starting from 6].
So how many "9-digit numbers starting from 8" are there? Well,
[number of 8-digit numbers starting from 1] +
[number of 8-digit numbers starting from 3]
and so on. Base case is reached when you get the question "How many 1-digit numbers are there starting from X" (and the answer is obviously 1).
When it comes to complexity, the key observation is that you reuse previously computed solutions. That is for instance, the answer to "how many 5-digit numbers starting from 3" there are, can be used both when answering "how many 6-digit numbers are there starting from 8" AND "how many 6-digit numbers are there starting from 4". This reuse make the complexity collapse from exponential to polynomial.
Let's take a closer look at the complexity of a dynamic programming solution:
Such implementation would fill in a matrix in the following way:
num[1][i] = 1, for all 0<=i<=9 -- there are one 1-digit number starting from X.
for digits = 2...N
for from = 0...9
num[digits][from] = num[digits-1][successor 1 of from] +
num[digits-1][successor 2 of from] +
...
num[digits-1][successor K of from]
return num[N][1] -- number of N-digit numbers starting from 1.
The algorithm simply fills the matrix one cell at a time, and the matrix is of dimension 10*N, and thus runs in linear time.
Wrote it down from the top of my head, please correct me if there are any typos.
I decided to tackle this problem and make it as extensible as I can. This solution allows you to:
Define your own board (phone pad, chess board, etc.)
Define your own chess piece (Knight, Rook, Bishop, etc.); you will have to write the concrete class and generate it from the factory.
Retrieve several pieces of information through some useful utility methods.
The classes are as follows:
PadNumber: Class defining a button on the phone pad. Could be renamed to 'Square' to represent a board square.
ChessPiece: Abstract class that defines fields for all chess pieces.
Movement: Interface that defines movement methods and allows for factory generation of pieces.
PieceFactory: Factory class to generate Chess pieces.
Knight: Concrete class that inherits from ChessPiece and implements Movement
PhoneChess: Entrance class.
Driver: Driver code.
OK, here's the code :)
package PhoneChess;
import java.awt.Point;
public class PadNumber {
private String number = "";
private Point coordinates = null;
public PadNumber(String number, Point coordinates)
{
if(number != null && number.isEmpty()==false)
this.number = number;
else
throw new IllegalArgumentException("Input cannot be null or empty.");
if(coordinates == null || coordinates.x < 0 || coordinates.y < 0)
throw new IllegalArgumentException();
else
this.coordinates = coordinates;
}
public String getNumber()
{
return this.number;
}
public Integer getNumberAsNumber()
{
return Integer.parseInt(this.number);
}
public Point getCoordinates()
{
return this.coordinates;
}
public int getX()
{
return this.coordinates.x;
}
public int getY()
{
return this.coordinates.y;
}
}
ChessPiece
package PhoneChess;
import java.util.HashMap;
import java.util.List;
public abstract class ChessPiece implements Movement {
protected String name = "";
protected HashMap<PadNumber, List<PadNumber>> moves = null;
protected Integer fullNumbers = 0;
protected int[] movesFrom = null;
protected PadNumber[][] thePad = null;
}
Movement Interface:
package PhoneChess;
import java.util.List;
public interface Movement
{
public Integer findNumbers(PadNumber start, Integer digits);
public abstract boolean canMove(PadNumber from, PadNumber to);
public List<PadNumber> allowedMoves(PadNumber from);
public Integer countAllowedMoves(PadNumber from);
}
PieceFactory
package PhoneChess;
public class PieceFactory
{
public ChessPiece getPiece(String piece, PadNumber[][] thePad)
{
if(thePad == null || thePad.length == 0 || thePad[0].length == 0)
throw new IllegalArgumentException("Invalid pad");
if(piece == null)
throw new IllegalArgumentException("Invalid chess piece");
if(piece.equalsIgnoreCase("Knight"))
return new Knight("Knight", thePad);
else
return null;
}
}
Knight class
package PhoneChess;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public final class Knight extends ChessPiece implements Movement {
/**Knight movements
* One horizontal, followed by two vertical
* Or
* One vertical, followed by two horizontal
* #param name
*/
public Knight(String name, PadNumber[][] thePad)
{
if(name == null || name.isEmpty() == true)
throw new IllegalArgumentException("Name cannot be null or empty");
this.name = name;
this.thePad = thePad;
this.moves = new HashMap<>();
}
private Integer fullNumbers = null;
#Override
public Integer findNumbers(PadNumber start, Integer digits)
{
if(start == null || "*".equals(start.getNumber()) || "#".equals(start.getNumber()) ) { throw new IllegalArgumentException("Invalid start point"); }
if(start.getNumberAsNumber() == 5) { return 0; } //Consider adding an 'allowSpecialChars' condition
if(digits == 1) { return 1; };
//Init
this.movesFrom = new int[thePad.length * thePad[0].length];
for(int i = 0; i < this.movesFrom.length; i++)
this.movesFrom[i] = -1;
fullNumbers = 0;
findNumbers(start, digits, 1);
return fullNumbers;
}
private void findNumbers(PadNumber start, Integer digits, Integer currentDigits)
{
//Base condition
if(currentDigits == digits)
{
//Reset
currentDigits = 1;
fullNumbers++;
return;
}
if(!this.moves.containsKey(start))
allowedMoves(start);
List<PadNumber> options = this.moves.get(start);
if(options != null)
{
currentDigits++; //More digits to be got
for(PadNumber option : options)
findNumbers(option, digits, currentDigits);
}
}
#Override
public boolean canMove(PadNumber from, PadNumber to)
{
//Is the moves list available?
if(!this.moves.containsKey(from.getNumber()))
{
//No? Process.
allowedMoves(from);
}
if(this.moves.get(from) != null)
{
for(PadNumber option : this.moves.get(from))
{
if(option.getNumber().equals(to.getNumber()))
return true;
}
}
return false;
}
/***
* Overriden method that defines each Piece's movement restrictions.
*/
#Override
public List<PadNumber> allowedMoves(PadNumber from)
{
//First encounter
if(this.moves == null)
this.moves = new HashMap<>();
if(this.moves.containsKey(from))
return this.moves.get(from);
else
{
List<PadNumber> found = new ArrayList<>();
int row = from.getY();//rows
int col = from.getX();//columns
//Cases:
//1. One horizontal move each way followed by two vertical moves each way
if(col-1 >= 0 && row-2 >= 0)//valid
{
if(thePad[row-2][col-1].getNumber().equals("*") == false &&
thePad[row-2][col-1].getNumber().equals("#") == false)
{
found.add(thePad[row-2][col-1]);
this.movesFrom[from.getNumberAsNumber()] = this.movesFrom[from.getNumberAsNumber()] + 1;
}
}
if(col-1 >= 0 && row+2 < thePad.length)//valid
{
if(thePad[row+2][col-1].getNumber().equals("*") == false &&
thePad[row+2][col-1].getNumber().equals("#") == false)
{
found.add(thePad[row+2][col-1]);
this.movesFrom[from.getNumberAsNumber()] = this.movesFrom[from.getNumberAsNumber()] + 1;
}
}
if(col+1 < thePad[0].length && row+2 < thePad.length)//valid
{
if(thePad[row+2][col+1].getNumber().equals("*") == false &&
thePad[row+2][col+1].getNumber().equals("#") == false)
{
found.add(thePad[row+2][col+1]);
this.movesFrom[from.getNumberAsNumber()] = this.movesFrom[from.getNumberAsNumber()] + 1;
}
}
if(col+1 < thePad[0].length && row-2 >= 0)//valid
{
if(thePad[row-2][col+1].getNumber().equals("*") == false &&
thePad[row-2][col+1].getNumber().equals("#") == false)
found.add(thePad[row-2][col+1]);
}
//Case 2. One vertical move each way follow by two horizontal moves each way
if(col-2 >= 0 && row-1 >= 0)
{
if(thePad[row-1][col-2].getNumber().equals("*") == false &&
thePad[row-1][col-2].getNumber().equals("#") == false)
found.add(thePad[row-1][col-2]);
}
if(col-2 >= 0 && row+1 < thePad.length)
{
if(thePad[row+1][col-2].getNumber().equals("*") == false &&
thePad[row+1][col-2].getNumber().equals("#") == false)
found.add(thePad[row+1][col-2]);
}
if(col+2 < thePad[0].length && row-1 >= 0)
{
if(thePad[row-1][col+2].getNumber().equals("*") == false &&
thePad[row-1][col+2].getNumber().equals("#") == false)
found.add(thePad[row-1][col+2]);
}
if(col+2 < thePad[0].length && row+1 < thePad.length)
{
if(thePad[row+1][col+2].getNumber().equals("*") == false &&
thePad[row+1][col+2].getNumber().equals("#") == false)
found.add(thePad[row+1][col+2]);
}
if(found.size() > 0)
{
this.moves.put(from, found);
this.movesFrom[from.getNumberAsNumber()] = found.size();
}
else
{
this.moves.put(from, null); //for example the Knight cannot move from 5 to anywhere
this.movesFrom[from.getNumberAsNumber()] = 0;
}
}
return this.moves.get(from);
}
#Override
public Integer countAllowedMoves(PadNumber from)
{
int start = from.getNumberAsNumber();
if(movesFrom[start] != -1)
return movesFrom[start];
else
{
movesFrom[start] = allowedMoves(from).size();
}
return movesFrom[start];
}
#Override
public String toString()
{
return this.name;
}
}
PhoneChess entrant class
package PhoneChess;
public final class PhoneChess
{
private ChessPiece thePiece = null;
private PieceFactory factory = null;
public ChessPiece ThePiece()
{
return this.thePiece;
}
public PhoneChess(PadNumber[][] thePad, String piece)
{
if(thePad == null || thePad.length == 0 || thePad[0].length == 0)
throw new IllegalArgumentException("Invalid pad");
if(piece == null)
throw new IllegalArgumentException("Invalid chess piece");
this.factory = new PieceFactory();
this.thePiece = this.factory.getPiece(piece, thePad);
}
public Integer findPossibleDigits(PadNumber start, Integer digits)
{
if(digits <= 0)
throw new IllegalArgumentException("Digits cannot be less than or equal to zero");
return thePiece.findNumbers(start, digits);
}
public boolean isValidMove(PadNumber from, PadNumber to)
{
return this.thePiece.canMove(from, to);
}
}
Driver Code:
public static void main(String[] args) {
PadNumber[][] thePad = new PadNumber[4][3];
thePad[0][0] = new PadNumber("1", new Point(0,0));
thePad[0][1] = new PadNumber("2", new Point(1,0));
thePad[0][2] = new PadNumber("3",new Point(2,0));
thePad[1][0] = new PadNumber("4",new Point(0,1));
thePad[1][1] = new PadNumber("5",new Point(1,1));
thePad[1][2] = new PadNumber("6", new Point(2,1));
thePad[2][0] = new PadNumber("7", new Point(0,2));
thePad[2][1] = new PadNumber("8", new Point(1,2));
thePad[2][2] = new PadNumber("9", new Point(2,2));
thePad[3][0] = new PadNumber("*", new Point(0,3));
thePad[3][1] = new PadNumber("0", new Point(1,3));
thePad[3][2] = new PadNumber("#", new Point(2,3));
PhoneChess phoneChess = new PhoneChess(thePad, "Knight");
System.out.println(phoneChess.findPossibleDigits(thePad[0][1],4));
}
}
This can be done in O(log N). Consider the keypad and the possible moves on it as a graph G(V, E) where vertices are the available digits and edges say which digits can follow which. Now for each output position i we can form a vector Paths(i) containing the number of different paths each vertex can be reached in. Now it's pretty easy to see that for a given position i and digit v, the possible paths that it can be reached through is the sum of the different paths that possible preceding digits could be reached through, or Paths(i)[v] = sum(Paths(i-1)[v2] * (1 if (v,v2) in E else 0) for v2 in V ). Now, this is taking the sum of each position the preceding vector times a corresponding position in a column of the adjacency matrix. So we can simplify this as Paths(i) = Paths(i-1) · A, where A is the adjacency matrix of the graph. Getting rid of the recursion and taking advantage of associativity of matrix multiplication, this becomes Paths(i) = Paths(1) · A^(i-1). We know Paths(1): we have only one path, to the digit 1.
The total number of paths for an n digit number is the sum of the paths for each digit, so the final algorithm becomes: TotalPaths(n) = sum( [1,0,0,0,0,0,0,0,0,0] · A^(n-1) )
The exponentiation can be calculated via squaring in O(log(n)) time, given constant time multiplies, otherwise O(M(n) * log(n)) where M(n) is the complexity of your favorite arbitrary precision multiplication algorithm for n digit numbers.
A simpler answer.
#include<stdio.h>
int a[10] = {2,2,2,2,3,0,3,2,2,2};
int b[10][3] = {{4,6},{6,8},{7,9},{4,8},{0,3,9},{},{1,7,0},{2,6},{1,3},{2,4}};
int count(int curr,int n)
{
int sum = 0;
if(n==10)
return 1;
else
{
int i = 0;
int val = 0;
for(i = 0; i < a[curr]; i++)
{
val = count(b[curr][i],n+1);
sum += val;
}
return sum;
}
}
int main()
{
int n = 1;
int val = count(1,0);
printf("%d\n",val);
}
celebrate!!
Run time constant time solution:
#include <iostream>
constexpr int notValid(int x, int y) {
return !(( 1 == x && 3 == y ) || //zero on bottom.
( 0 <= x && 3 > x && //1-9
0 <= y && 3 > y ));
}
class Knight {
template<unsigned N > constexpr int move(int x, int y) {
return notValid(x,y)? 0 : jump<N-1>(x,y);
}
template<unsigned N> constexpr int jump( int x, int y ) {
return move<N>(x+1, y-2) +
move<N>(x-1, y-2) +
move<N>(x+1, y+2) +
move<N>(x-1, y+2) +
move<N>(x+2, y+1) +
move<N>(x-2, y+1) +
move<N>(x+2, y-1) +
move<N>(x-2, y-1);
}
public:
template<unsigned N> constexpr int count() {
return move<N-1>(0,1) + move<N-1>(0,2) +
move<N-1>(1,0) + move<N-1>(1,1) + move<N-1>(1,2) +
move<N-1>(2,0) + move<N-1>(2,1) + move<N-1>(2,2);
}
};
template<> constexpr int Knight::move<0>(int x, int y) { return notValid(x,y)? 0 : 1; }
template<> constexpr int Knight::count<0>() { return 0; } //terminal cases.
template<> constexpr int Knight::count<1>() { return 8; }
int main(int argc, char* argv[]) {
static_assert( ( 16 == Knight().count<2>() ), "Fail on test with 2 lenght" ); // prof of performance
static_assert( ( 35 == Knight().count<3>() ), "Fail on test with 3 lenght" );
std::cout<< "Number of valid Knight phones numbers:" << Knight().count<10>() << std::endl;
return 0;
}
Method returns list of 10 digit numbers starting with 1. Again the count is 1424.
public ArrayList<String> getList(int digit, int length, String base ){
ArrayList<String> list = new ArrayList<String>();
if(length == 1){
list.add(base);
return list;
}
ArrayList<String> temp;
for(int i : b[digit]){
String newBase = base +i;
list.addAll(getList(i, length -1, newBase ));
}
return list;
}
I'm not sure if I missed something, but reading the description of the problem I came to this solution. It has O(n) time complexity and O(1) space complexity.
I figured that number 1 is at a corner, right? In each corner you can either move to one of the sides (4 from 9 and 3, or 6 from 7 an 1) or one of the 'vertical' sides (8 from 3 and 1, or 2 from 9 and 7). So, corners add two moves: a side move and a 'vertical' move. This is true for all four corners (1,3,9,7).
From each side, you can either move to two corners (7 and 1 from 6, 9 and 3 from 4) or you can reach the bottom key (0). That's three moves. Two corners and one bottom.
On the bottom key (0), you can move to both sides (4 and 6). So, in each step, you check out all possible endings for the path of the previous length (that is, how many ended on a corner, a side, a 'vertical' or the 'bottom' zero key) and then generate new ending counts according to the generation rules stated before.
Each corner ending adds a side and a vertical.
Each side ending adds 2 corners and a bottom.
Each vertical ending adds 2 corners.
Each bottom ending adds 2 sides.
If you start from the '1' key, you start with one possible corner solution, in each step you count the number of corner, side, vertical and bottom endings of the previous step and then apply the rules to generate the next count.
In plain javascript code.
function paths(n) {
//Index to 0
var corners = 1;
var verticals = 0;
var bottom = 0;
var sides = 0;
if (n <= 0) {
//No moves possible for paths without length
return 0;
}
for (var i = 1; i < n; i++) {
var previousCorners = corners;
var previousVerticals = verticals;
var previousBottom = bottom;
var previousSides = sides;
sides = 1 * previousCorners + 2 * previousBottom;
verticals = 1 * previousCorners;
bottom = 1 * previousSides;
corners = 2 * previousSides + 2 * previousVerticals;
//console.log("Moves: %d, Length: %d, Sides: %d, Verticals: %d, Bottom: %d, Corners: %d, Total: %d", i, i + 1, sides, verticals, bottom, corners, sides+verticals+bottom+corners);
}
return sides + verticals + bottom + corners;
}
for (var i = 0; i <= 10; i++) {
console.log(paths(i));
}
This problem may be also modelled as a Constraint satisfaction problem (aka CSP for short).
I suggest to use the Minion solver (fast and scalable) that you can find here.
Modelling maybe tedious and time consumming (steep learning curve).
Instead of using Minion language input, my advice is to formulate the model with solver independent modelling language such as ESSENCE and find a converter accordingly.
//Both the iterative and recursive with memorize shows count as 1424 for 10 digit numbers starting with 1.
int[][] b = {{4,6},{6,8},{7,9},{4,8},{0,3,9},{},{1,7,0},{2,6},{1,3},{2,4}};
public int countIterative(int digit, int length) {
int[][] matrix = new int[length][10];
for(int dig =0; dig <=9; dig++){
matrix[0][dig] = 1;
}
for(int len = 1; len < length; len++){
for(int dig =0; dig <=9; dig++){
int sum = 0;
for(int i : b[dig]){
sum += matrix[len-1][i];
}
matrix[len][dig] = sum;
}
}
return matrix[length-1][digit];
}
public int count(int index, int length, int[][] matrix ){
int sum = 0;
if(matrix[length-1][index] > 0){
System.out.println("getting value from memoize:"+index + "length:"+ length);
return matrix[length-1][index];
}
if( length == 1){
return 1;
}
for(int i: b[index] ) {
sum += count(i, length-1,matrix);
}
matrix[length-1][index] = sum;
return sum;
}
Recursive memoization approach:
vector<vector<int>> lupt = { {4, 6}, {6, 8}, {9, 7}, {4, 8}, {3, 9, 0},
{}, {1,7,0}, {6, 2}, {1, 3}, {2, 4} };
int numPhoneNumbersUtil(int startdigit, int& phonenumberlength, int currCount, map< pair<int,int>,int>& memT)
{
int noOfCombs = 0;
vector<int> enddigits;
auto it = memT.find(make_pair(startdigit,currCount));
if(it != memT.end())
{
noOfCombs = it->second;
return noOfCombs;
}
if(currCount == phonenumberlength)
{
return 1;
}
enddigits = lupt[startdigit];
for(auto it : enddigits)
{
noOfCombs += numPhoneNumbersUtil(it, phonenumberlength, currCount + 1, memT);
}
memT.insert(make_pair(make_pair(startdigit,currCount), noOfCombs));
return memT[make_pair(startdigit,currCount)];
}
int numPhoneNumbers(int startdigit, int phonenumberlength)
{
map<pair<int,int>,int> memT;
int currentCount = 1; //the first digit has already been added
return numPhoneNumbersUtil(startdigit, phonenumberlength, currentCount, memT);
}
I implemented both brute force and dynamic programming models
import queue
def chess_numbers_bf(start, length):
if length <= 0:
return 0
phone = [[7, 5], [6, 8], [3, 7], [9, 2, 8], [], [6, 9, 0], [1, 5], [0, 2], [3, 1], [5, 3]]
total = 0
q = queue.Queue()
q.put((start, 1))
while not q.empty():
front = q.get()
val = front[0]
len_ = front[1]
if len_ < length:
for elm in phone[val]:
q.put((elm, len_ + 1))
else:
total += 1
return total
def chess_numbers_dp(start, length):
if length <= 0:
return 0
phone = [[7, 5], [6, 8], [3, 7], [9, 2, 8], [], [6, 9, 0], [1, 5], [0, 2], [3, 1], [5, 3]]
memory = {}
def __chess_numbers_dp(s, l):
if (s, l) in memory:
return memory[(s, l)]
elif l == length - 1:
memory[(s, l)] = 1
return 1
else:
total_n_ways = 0
for number in phone[s]:
total_n_ways += __chess_numbers_dp(number, l+1)
memory[(s, l)] = total_n_ways
return total_n_ways
return __chess_numbers_dp(start, 0)
# bf
for i in range(0, 10):
print(i, chess_numbers_bf(3, i))
print('\n')
for i in range(0, 10):
print(i, chess_numbers_bf(9, i))
print('\n')
# dp
for i in range(0, 10):
print(i, chess_numbers_dp(3, i))
print('\n')
# dp
for i in range(0, 10):
print(i, chess_numbers_dp(9, i))
print('\n')
Recursive function in Java:
public static int countPhoneNumbers (int n, int r, int c) {
if (outOfBounds(r,c)) {
return 0;
} else {
char button = buttons[r][c];
if (button == '.') {
// visited
return 0;
} else {
buttons[r][c] = '.'; // record this position so don't revisit.
// Count all possible phone numbers with one less digit starting
int result=0;
result = countPhoneNumbers(n-1,r-2,c-1)
+ countPhoneNumbers(n-1,r-2,c+1)
+ countPhoneNumbers(n-1,r+2,c-1)
+ countPhoneNumbers(n-1,r+2,c+1)
+ countPhoneNumbers(n-1,r-1,c-2)
+ countPhoneNumbers(n-1,r-1,c+2)
+ countPhoneNumbers(n-1,r+1,c-2)
+ countPhoneNumbers(n-1,r+1,c+2);
}
buttons[r][c] = button; // Remove record from position.
return result;
}
}
}

Greatest linear dimension 2d set of points

Given an ordered set of 2D pixel locations (adjacent or adjacent-diagonal) that form a complete path with no repeats, how do I determine the Greatest Linear Dimension of the polygon whose perimeter is that set of pixels? (where the GLD is the greatest linear distance of any pair of points in the set)
For my purposes, the obvious O(n^2) solution is probably not fast enough for figures of thousands of points. Are there good heuristics or lookup methods that bring the time complexity nearer to O(n) or O(log(n))?
An easy way is to first find the convex hull of the points, which can be done in O(n log n) time in many ways. [I like Graham scan (see animation), but the incremental algorithm is also popular, as are others, although some take more time.]
Then you can find the farthest pair (the diameter) by starting with any two points (say x and y) on the convex hull, moving y clockwise until it is furthest from x, then moving x, moving y again, etc. You can prove that this whole thing takes only O(n) time (amortized). So it's O(n log n)+O(n)=O(n log n) in all, and possibly O(nh) if you use gift-wrapping as your convex hull algorithm instead. This idea is called rotating calipers, as you mentioned.
Here is code by David Eppstein (computational geometry researcher; see also his Python Algorithms and Data Structures for future reference).
All this is not very hard to code (should be a hundred lines at most; is less than 50 in the Python code above), but before you do that -- you should first consider whether you really need it. If, as you say, you have only "thousands of points", then the trivial O(n^2) algorithm (that compares all pairs) will be run in less than a second in any reasonable programming language. Even with a million points it shouldn't take more than an hour. :-)
You should pick the simplest algorithm that works.
On this page:
http://en.wikipedia.org/wiki/Rotating_calipers
http://cgm.cs.mcgill.ca/~orm/diam.html
it shows that you can determine the maximum diameter of a convex polygon in O(n). I just need to turn my point set into a convex polygon first (probably using Graham scan).
http://en.wikipedia.org/wiki/Convex_hull_algorithms
Here is some C# code I came across for computing the convex hull:
http://www.itu.dk/~sestoft/gcsharp/index.html#hull
I ported the Python code to C#. It seems to work.
using System;
using System.Collections.Generic;
using System.Drawing;
// Based on code here:
// http://code.activestate.com/recipes/117225/
// Jared Updike ported it to C# 3 December 2008
public class Convexhull
{
// given a polygon formed by pts, return the subset of those points
// that form the convex hull of the polygon
// for integer Point structs, not float/PointF
public static Point[] ConvexHull(Point[] pts)
{
PointF[] mpts = FromPoints(pts);
PointF[] result = ConvexHull(mpts);
int n = result.Length;
Point[] ret = new Point[n];
for (int i = 0; i < n; i++)
ret[i] = new Point((int)result[i].X, (int)result[i].Y);
return ret;
}
// given a polygon formed by pts, return the subset of those points
// that form the convex hull of the polygon
public static PointF[] ConvexHull(PointF[] pts)
{
PointF[][] l_u = ConvexHull_LU(pts);
PointF[] lower = l_u[0];
PointF[] upper = l_u[1];
// Join the lower and upper hull
int nl = lower.Length;
int nu = upper.Length;
PointF[] result = new PointF[nl + nu];
for (int i = 0; i < nl; i++)
result[i] = lower[i];
for (int i = 0; i < nu; i++)
result[i + nl] = upper[i];
return result;
}
// returns the two points that form the diameter of the polygon formed by points pts
// takes and returns integer Point structs, not PointF
public static Point[] Diameter(Point[] pts)
{
PointF[] fpts = FromPoints(pts);
PointF[] maxPair = Diameter(fpts);
return new Point[] { new Point((int)maxPair[0].X, (int)maxPair[0].Y), new Point((int)maxPair[1].X, (int)maxPair[1].Y) };
}
// returns the two points that form the diameter of the polygon formed by points pts
public static PointF[] Diameter(PointF[] pts)
{
IEnumerable<Pair> pairs = RotatingCalipers(pts);
double max2 = Double.NegativeInfinity;
Pair maxPair = null;
foreach (Pair pair in pairs)
{
PointF p = pair.a;
PointF q = pair.b;
double dx = p.X - q.X;
double dy = p.Y - q.Y;
double dist2 = dx * dx + dy * dy;
if (dist2 > max2)
{
maxPair = pair;
max2 = dist2;
}
}
// return Math.Sqrt(max2);
return new PointF[] { maxPair.a, maxPair.b };
}
private static PointF[] FromPoints(Point[] pts)
{
int n = pts.Length;
PointF[] mpts = new PointF[n];
for (int i = 0; i < n; i++)
mpts[i] = new PointF(pts[i].X, pts[i].Y);
return mpts;
}
private static double Orientation(PointF p, PointF q, PointF r)
{
return (q.Y - p.Y) * (r.X - p.X) - (q.X - p.X) * (r.Y - p.Y);
}
private static void Pop<T>(List<T> l)
{
int n = l.Count;
l.RemoveAt(n - 1);
}
private static T At<T>(List<T> l, int index)
{
int n = l.Count;
if (index < 0)
return l[n + index];
return l[index];
}
private static PointF[][] ConvexHull_LU(PointF[] arr_pts)
{
List<PointF> u = new List<PointF>();
List<PointF> l = new List<PointF>();
List<PointF> pts = new List<PointF>(arr_pts.Length);
pts.AddRange(arr_pts);
pts.Sort(Compare);
foreach (PointF p in pts)
{
while (u.Count > 1 && Orientation(At(u, -2), At(u, -1), p) <= 0) Pop(u);
while (l.Count > 1 && Orientation(At(l, -2), At(l, -1), p) >= 0) Pop(l);
u.Add(p);
l.Add(p);
}
return new PointF[][] { l.ToArray(), u.ToArray() };
}
private class Pair
{
public PointF a, b;
public Pair(PointF a, PointF b)
{
this.a = a;
this.b = b;
}
}
private static IEnumerable<Pair> RotatingCalipers(PointF[] pts)
{
PointF[][] l_u = ConvexHull_LU(pts);
PointF[] lower = l_u[0];
PointF[] upper = l_u[1];
int i = 0;
int j = lower.Length - 1;
while (i < upper.Length - 1 || j > 0)
{
yield return new Pair(upper[i], lower[j]);
if (i == upper.Length - 1) j--;
else if (j == 0) i += 1;
else if ((upper[i + 1].Y - upper[i].Y) * (lower[j].X - lower[j - 1].X) >
(lower[j].Y - lower[j - 1].Y) * (upper[i + 1].X - upper[i].X))
i++;
else
j--;
}
}
private static int Compare(PointF a, PointF b)
{
if (a.X < b.X)
{
return -1;
}
else if (a.X == b.X)
{
if (a.Y < b.Y)
return -1;
else if (a.Y == b.Y)
return 0;
}
return 1;
}
}
You could maybe draw a circle that was bigger than the polygon and slowly shrink it, checking if youve intersected any points yet. Then your diameter is the number youre looking for.
Not sure if this is a good method, it sounds somewhere between O(n) and O(n^2)
My off-the-cuff solution is to try a binary partitioning approach, where you draw a line somwwhere in the middle and check distances of all points from the middle of that line.
That would provide you with 2 Presumably Very Far points. Then check the distance of those two and repeat the above distance check. Repeat this process for a while.
My gut says this is an n log n heuristic that will get you Pretty Close.

Resources