Search code examples
algorithmperformancematrixtime-complexityadjacency-matrix

How to get minimum number of moves to solve `game of fifteen`?


I was reading about this and thought to form an algorithm to find the minimum number of moves to solve this.

Constraints I made: An N X N matrix having one empty slot ,say 0, would be plotted having numbers 0 to n-1.

Now we have to recreate this matrix and form the matrix having numbers in increasing order from left to right beginning from the top row and have the last element 0 i.e. (N X Nth)element.

For example,

Input :

8 4 0
7 2 5
1 3 6

Output:
1 2 3
4 5 6
7 8 0

Now the problem is how to do this in minimum number of steps possible. As in game(link provided) you can either move left, right, up or bottom and shift the 0(empty slot) to corresponding position to make the final matrix.

The output to printed for this algorithm is number of steps say M and then Tile(number) moved in the direction say, 1 for swapping with upper adjacent element, 2 for lower adjacent element, 3 for left adjacent element and 4 for right adjacent element.

Like, for

2     <--- order of N X N matrix
3 1
0 2

Answer should be: 3 4 1 2 where 3 is M and 4 1 2 are steps to tile movement.

So I have to minimise the complexity for this algorithm and want to find minimum number of moves. Please suggest me the most efficient approach to solve this algorithm.

Edit:

What I coded in c++, Please see the algorithm rather than pointing out other issues in code .

#include <bits/stdc++.h>
using namespace std;
int inDex=0,shift[100000],N,initial[500][500],final[500][500];
struct Node
{
    Node* parent;
    int mat[500][500];
    int x, y;
    int cost;
    int level;
};

Node* newNode(int mat[500][500], int x, int y, int newX,
              int newY, int level, Node* parent)
{
    Node* node = new Node;
    node->parent = parent;
    memcpy(node->mat, mat, sizeof node->mat);
    swap(node->mat[x][y], node->mat[newX][newY]);
    node->cost = INT_MAX;
    node->level = level;
    node->x = newX;
    node->y = newY;
    return node;
}

int row[] = { 1, 0, -1, 0 };
int col[] = { 0, -1, 0, 1 };

int calculateCost(int initial[500][500], int final[500][500])
{
    int count = 0;
    for (int i = 0; i < N; i++)
      for (int j = 0; j < N; j++)
              if (initial[i][j] && initial[i][j] != final[i][j])
                count++;
    return count;

}

int isSafe(int x, int y)
{
    return (x >= 0 && x < N && y >= 0 && y < N);
}
struct comp
{
    bool operator()(const Node* lhs, const Node* rhs) const
    {
        return (lhs->cost + lhs->level) > (rhs->cost + rhs->level);
    }
};

void solve(int initial[500][500], int x, int y,
           int final[500][500])
{
    priority_queue<Node*, std::vector<Node*>, comp> pq;
    Node* root = newNode(initial, x, y, x, y, 0, NULL);
    Node* prev = newNode(initial,x,y,x,y,0,NULL);
    root->cost = calculateCost(initial, final);
    pq.push(root);
    while (!pq.empty())
    {
        Node* min = pq.top();
        if(min->x > prev->x)
        {
            shift[inDex] = 4;
            inDex++;
        }
        else if(min->x < prev->x)
        {
            shift[inDex] = 3;
            inDex++;
        }
        else if(min->y > prev->y)
        {
            shift[inDex] = 2;
            inDex++;
        }
        else if(min->y < prev->y)
        {
            shift[inDex] = 1;
            inDex++;
        }
        prev = pq.top();
        pq.pop();
        if (min->cost == 0)
        {
            cout << min->level << endl;
            return;
        }
        for (int i = 0; i < 4; i++)
        {
            if (isSafe(min->x + row[i], min->y + col[i]))
            {
                Node* child = newNode(min->mat, min->x,
                              min->y, min->x + row[i],
                              min->y + col[i],
                              min->level + 1, min);

                child->cost = calculateCost(child->mat, final);
                pq.push(child);
            }
        }
    }
}
int main()
{
    cin >> N;
    int i,j,k=1;
    for(i=0;i<N;i++)
    {
        for(j=0;j<N;j++)
        {
            cin >> initial[j][i];
        }
    }
    for(i=0;i<N;i++)
    {
        for(j=0;j<N;j++)
        {
            final[j][i] = k;
            k++;
        }
    }
    final[N-1][N-1] = 0;
    int x = 0, y = 1,a[100][100];
    solve(initial, x, y, final);
    for(i=0;i<inDex;i++)
    {
        cout << shift[i] << endl;
    }
    return 0;
}

In this above code I am checking for each child node which has the minimum cost(how many numbers are misplaced from the final matrix numbers).

I want to make this algorithm further efficient and reduce it's time complexity. Any suggestions would be appreciable.


Solution

  • While this sounds a lot like a homework problem, I'll lend a bit of help.

    For significantly small problems, like your 2x2 or 3x3, you can just brute force it. Basically, you do every possible combination with every possible move, track how many turns each took, and then print out the smallest.

    To improve on this, maintain a list of solved solutions, and then any time you make a possible move, if that moves already done, stop trying that one since it can't possible be the smallest.

    Example, say I'm in this state (flattening your matrix to a string for ease of display):

    5736291084
    6753291084
    5736291084
    

    Notice that we're back to a state we've seen before. That means it can't possible be the smallest move, because the smallest would be done without returning to a previous state.

    You'll want to create a tree doing this, so you'd have something like:

                                134
                                529
                                870
                               /   \
                              /     \
                             /       \
                            /         \
                         134           134
                         529           520
                         807           879
                        / | \         / | \
                       /  |  X       /  X  \
                    134  134  134 134  134  130
                    509  529  529 502  529  524
                    827  087  870 879  870  879
    

    And so on. Notice I marked some with X because they were duplicates, and thus we wouldn't want to pursue them any further since we know they can't be the smallest.

    You'd just keep repeating this until you've tried all possible solutions (i.e., all non-stopped leaves reach a solution), then you just see which was the shortest. You could also do it in parallel so you stop once any one has found a solution, saving you time.

    This brute force approach won't be effective against large matrices. To solve those, you're looking at some serious software engineering. One approach you could take with it would be to break it into smaller matrices and solve that way, but that may not be the best path.

    This is a tricky problem to solve at larger values, and is up there with some of the trickier NP problems out there.


    Start from solution, determine ranks of permuation

    The reverse of above would be how you can pre-generate a list of all possible values.

    Start with the solution. That has a rank of permutation of 0 (as in, zero moves):

    012
    345
    678
    

    Then, make all possible moves from there. All of those moves have rank of permutation of 1, as in, one move to solve.

                             012
    0                        345
                             678
                            /   \
                           /     \
                          /       \
                       102         312
    1                  345         045 
                       678         678
    

    Repeat that as above. Each new level all has the same rank of permutation. Generate all possible moves (in this case, until all of your branches are killed off as duplicates).

    You can then store all of them into an object. Flattening the matrix would make this easy (using JavaScript syntax just for example):

    {
      '012345678': 0,
      '102345678': 1,
      '312045678': 1,
      '142305678': 2,
      // and so on
    }
    

    Then, to solve your question "minimum number of moves", just find the entry that is the same as your starting point. The rank of permutation is the answer.

    This would be a good solution if you are in a scenario where you can pre-generate the entire solution. It would take time to generate, but lookups would be lightning fast (this is similar to "rainbow tables" for cracking hashes).

    If you must solve on the fly (without pre-generation), then the first solution, start with the answer and work your way move-by-move until you find a solution would be better.

    While the maximum complexity is O(n!), there are only O(n^2) possible solutions. Chopping off duplicates from the tree as you go, your complexity will be somewhere in between those two, probably in the neighborhood of O(n^3) ~ O(2^n)