'DFS for AI Game State Search using too much memory

I'm doing a CS course, and currently taking a AI class. We have to solve the 15 puzzle using various algorithms, like DFS, BFS, A* etc.

Currently we have implemented pretty much everything, but we are having memory problems with the DFS.

I have locked the DFS so that when he first reaches a depth of 1 million, it stops. It shouldn't really use that much memory from my calculations, should be around 100 MB total, yet, it says it is using almost 1GB. I have tried to find a lot of solutions to this, but I just cannot understand what is going on.

Game.cpp

#include "Game.h"
#include "Util.h"
using namespace std;

vector<vector<unsigned char>> mat;
static vector<pair<signed char, signed char>> directions = {make_pair(-1, 0), make_pair(1, 0), make_pair(0, 1), make_pair(0, -1)};

pair<unsigned char, unsigned char> blankPosition;
int depth;
string path;
static int nodeCount = 0;

// Creates a new game
Game::Game()
{
    depth = 0;
    path = "";
    mat.resize(WIDTH);
    for (int row = 0; row < WIDTH; row++)
    {
        mat[row].resize(WIDTH);
        for (int col = 0; col < WIDTH; col++)
        {
            scanf("%hhu", &mat[row][col]);
            path += to_string(mat[row][col]) + ' ';
            if (mat[row][col] == 0)
                blankPosition = make_pair(row, col);
        }
    }
}

Game::Game(vector<vector<unsigned char>> mat, pair<unsigned char, unsigned char> blankPosition, int depth)
{
    this->mat = mat;
    this->blankPosition = blankPosition;
    this->path = Util::MatrixToString(mat);
    this->depth = depth;
}

vector<Game> Game::CreateChildren()
{
    vector<Game> games;
    for (auto x : directions)
    {
        if (x.first + blankPosition.first > 3 || x.first + blankPosition.first < 0)
            continue;
        if (x.second + blankPosition.second > 3 || x.second + blankPosition.second < 0)
            continue;

        vector<vector<unsigned char>> matTemp = mat;
        swap(matTemp[x.first + blankPosition.first][x.second + blankPosition.second], matTemp[blankPosition.first][blankPosition.second]);

        pair<int, int> newGameBlankPosition = make_pair(x.first + blankPosition.first, x.second + blankPosition.second);
        Game g = Game(matTemp, newGameBlankPosition, depth + 1);
        games.push_back(g);
    }
    return games;
}

void Game::PrintGame()
{
    for (int row = 0; row < WIDTH; row++)
    {
        for (int col = 0; col < WIDTH; col++)
        {
            if (mat[row][col] < 10)
            {
                cout << ' ' << static_cast<int>(mat[row][col]) << ' ';
            }
            else
            {
                cout << static_cast<int>(mat[row][col]) << ' ';
            }
        }

        cout << '\n';
    }
    cout << "--\n";
}

// Compares two games to see if theyre equal
// Passed by reference for efficiency
bool Game::operator==(Game *rhs)
{
    for (int row = 0; row < WIDTH; row++)
    {
        for (int col = 0; col < WIDTH; col++)
        {
            if (mat[row][col] != rhs->mat[row][col])
                return false;
        }
    }
    return true;
}

Game.h

#pragma once
#include <bits/stdc++.h>
using namespace std;

class Game
{
public:
    vector<vector<unsigned char>> mat;
    pair<unsigned char, unsigned char> blankPosition;
    string path;
    int depth;

    Game(); // Takes input from the standard input ONLY
    Game(vector<vector<unsigned char>> mat, pair<unsigned char, unsigned char> blankPosition, int depth);
    void PrintGame();
    vector<Game> CreateChildren();
    bool operator==(Game *rhs);
};

Util.cpp

#include "Util.h"
using namespace std;

int Util::manhattan_distance(pair<unsigned char, unsigned char> &point1, pair<unsigned char, unsigned char> &point2)
{
    return abs(point1.first - point2.first + point1.second - point2.second);
}

// Converts a matrix into an array.
void Util::convert_to_array(vector<vector<unsigned char>> &inMatrix, vector<unsigned char> &outArray)
{
    int pos = 0;
    for (int row = 0; row < WIDTH; row++)
    {
        for (int col = 0; col < WIDTH; col++)
        {
            outArray.push_back(inMatrix[row][col]);
        }
    }
}

// Converts a matrix into a string
string Util::MatrixToString(vector<vector<unsigned char>> &inMatrix)
{
    string result = "";
    for (int row = 0; row < WIDTH; row++)
    {
        for (int col = 0; col < WIDTH; col++)
        {
            result += to_string(inMatrix[row][col]) + ' ';
        }
    }
    return result;
}

void Util::print_path(string &path, int &depth)
{
    int pos = 0;
    for (int curDepth = 0; curDepth <= depth; curDepth++)
    {
        for (int row = 0; row < WIDTH; row++)
        {
            for (int col = 0; col < WIDTH; col++)
            {
                if (isdigit(path[pos]) && isdigit(path[pos + 1]))
                {
                    cout << path[pos] << path[pos + 1] << ' ';
                    pos += 2;
                }
                else if (isdigit(path[pos]))
                {
                    cout << ' ' << path[pos] << ' ';
                    pos++;
                }
                else
                {
                    pos++;
                    col--;
                }
            }
            cout << '\n';
        }
        cout << "============\n";
    }
}

int Util::count_transpositions(Game &initialState, Game &finalState)
{ // isto corre em n^2 (talvez dê para fazer melhor..)
    // a lógica é contar o número de swaps que têm de ser feitos (temos de pensar porque é que isto funciona (se funcionar))
    vector<unsigned char> configuration1(WIDTH * WIDTH);
    vector<unsigned char> configuration2(WIDTH * WIDTH);
    convert_to_array(initialState.mat, configuration1);
    convert_to_array(finalState.mat, configuration2);

    int transpositions = 0;
    for (int i = 0; i < WIDTH * WIDTH; ++i)
    { // mudar width * width
        if (configuration1[i] == configuration2[i])
            continue;
        for (int j = i; j < WIDTH * WIDTH; ++j)
        {
            if (configuration1[i] != configuration2[j])
                continue;
            swap(configuration2[i], configuration2[j]);
            ++transpositions;
            break;
        }
    }
    return transpositions;
}

bool Util::check_solvability(Game &initialState, Game &finalState)
{
    int manDist = manhattan_distance(initialState.blankPosition, finalState.blankPosition);
    int trans = count_transpositions(initialState, finalState);

    return (manDist + trans) % 2 == 0;
}

int Util::CountInversions(Game &game)
{
    vector<unsigned char> arr;
    convert_to_array(game.mat, arr);

    int inversions = 0;
    for (int i = 0; i < WIDTH * WIDTH; i++)
    {
        for (int j = i + 1; j < WIDTH * WIDTH; j++)
        {
            if (arr[i] > arr[j] && arr[j] != 0)
                inversions++;
        }
    }

    return inversions;
}

bool Util::CheckSolvability(Game &initialState, Game &finalState)
{
    int invI = CountInversions(initialState);
    int invF = CountInversions(finalState);

    bool condI = ((invI % 2 == 0) == (initialState.blankPosition.first % 2 == 1));
    bool condF = ((invF % 2 == 0) == (finalState.blankPosition.first % 2 == 1));

    return (condI == condF);
}

Util.h

#pragma once
#include <bits/stdc++.h>
#include "Game.h"

using namespace std;
#define WIDTH 4

namespace Util
{
    // Calculates the manhattan distance between two points
    int manhattan_distance(pair<unsigned char, unsigned char> &point1, pair<unsigned char, unsigned char> &point2);

    // Converts a matrix into an array.
    void convert_to_array(vector<vector<unsigned char>> &inMatrix, vector<unsigned char> &outArray);

    // Checks if an initialState can be transformed into a finalState
    bool check_solvability(Game &initialState, Game &finalState);

    int count_transpositions(Game &initialState, Game &finalState);

    string MatrixToString(vector<vector<unsigned char>> &inMatrix);

    void print_path(string &path, int &depth);

    int CountInversions(Game &game);

    bool CheckSolvability(Game &initialState, Game &finalState);

}

Main.cpp

#include "Game.h"
#include "Util.h"
#pragma GCC optimize("Ofast")

using namespace std;

// g++ -Wall -o main main.cpp && ./main
Game initial;
Game final;

void PrintSolution(stack<Game> visitedStack, int nodes)
{
    int size = visitedStack.size();
    int depth = visitedStack.top().depth;
    while (!visitedStack.empty())
    {
        visitedStack.top().PrintGame();
        visitedStack.pop();
    }

    cout << "Caminho Encontrado!\n";
    // Do the thing here
    cout << "Depth: " << depth << '\n';
    // cout << "Explored: " << nodes << '\n';
    return;
}
int nodeCount = 0;
bool DFS(int DEPTH)
{
    stack<Game> iterativeStack;
    stack<Game> visitedStack;
    unordered_set<string> visited;

    iterativeStack.push(initial);
    visitedStack.push(initial);
    int previousDepth = -1;

    while (!iterativeStack.empty())
    {
        Game currentGame = iterativeStack.top();
        iterativeStack.pop();

        if (currentGame.depth <= previousDepth)
        {
            for (int i = 0; i < previousDepth - currentGame.depth + 1; i++)
            {
                // TODO: Apagar o node
                nodeCount--;
                visited.erase(visitedStack.top().path);
                visitedStack.pop();
            }
        }

        visited.insert(currentGame.path);
        visitedStack.push(currentGame);

        previousDepth = currentGame.depth;

        vector<Game> games;

        if (currentGame.depth < DEPTH)
            games = currentGame.CreateChildren();
        else
            return false;

        for (Game child : games)
        {
            if (visited.find(child.path) != visited.end())
            {
                continue;
            }
            nodeCount++;
            if (child.path == final.path)
            {
                visited.clear();

                iterativeStack.empty();
                visitedStack.push(child);
                PrintSolution(visitedStack, 3);
                return true;
            }
            iterativeStack.push(child);
        }
    }

    return false;
}
int main()
{
    if (!Util::CheckSolvability(initial, final))
    {
        cout << "Impossivel :( \n";
        return -1;
    }

    DFS(1000000);

    return 0;
}

The code is a bit messy, because we have been trying to fix it, so somethings are not as clear as they will be. I'm sorry if this is a bad way to show the code, but I didn't know if I could paste links to it.

Anyway, when I run the program using this input:

1 2 3 4 5 6 8 12 13 9 0 7 14 11 10 15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0

and this command: /usr/bin/time -v ./out < input1, it says that the "Maximum resident set size (kbytes):" is 828360, which is around 800MB, around 10times what it should be using.

Previously, the Game.cpp class was using all Integers, and it was using around 800MB, after I implemented it using unsigned chars, it was still using the same amount, which I find very weird. Shouldn't it use way less memory?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source