Головоломка из 8 плиток через BFS

Я искал глубины Интернета, и я еще не нашел решение моей проблемы. Я реализовал (я думаю) BFS для игры с раздвижными плитками. Однако он не может решить проблему, если состояние не находится в нескольких шагах, иначе это просто приведет к ошибкам нехватки памяти. Итак, мой вопрос к вам, где я ошибаюсь? Насколько я знаю, мой код следует псевдокоду BFS.

РЕДАКТИРОВАТЬ/ПРИМЕЧАНИЕ. Я прошел через отладчик и еще не нашел ничего необычного на мой взгляд, но я сравнительно начинающий программист.

#include <ctime>
#include <string>
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <deque>
#include <vector>

using namespace std;

///////////////////////////////////////////////////////////////////////////////////////////
//
// Search Algorithm:  Breadth-First Search 
//
// Move Generator:  
//
////////////////////////////////////////////////////////////////////////////////////////////
class State{
public:
    int state[9];

    State(){
        for (int i = 0; i < 9; i++){
            state[i] = i;
        }
    }

    State(string st){
        for (int i = 0; i < st.length(); i++){
            state[i] = st.at(i) - '0';
        }
    }

    State(const State &st){
        for (int i = 0; i < 9; i++){
            state[i] = st.state[i];
        }
    }

    bool operator==(const State& other) {

        for (int i = 0; i < 9; i++){
            if (this->state[i] != other.state[i]){return false;}
        }
        return true;
    }

    bool operator!=(const State& other) {
        return !(*this == other);
    }

    void swap(int x, int y){
        // State b; // blank state
        // for (int i = 0; i < 9; i++) // fill blank state with current state
        //  b.state[i] = state[i];

        int t = this->state[x]; // saves value of the value in position x of the state

        this->state[x] = this->state[y]; // swaps value of position x with position y in the state
        this->state[y] = t; // swaps value of position y with saved position x in the state
    }

    int findBlank(){ // finds position in 3x3 of blank tile
        for (int i=0; i<9; i++){
            if (state[i]==0) return i;
        }
    }

    vector<State> blankExpand(){
        int pos = this->findBlank();
        vector<State> vecStates;


        if (pos != 0 && pos != 1 && pos != 2){ // swaps the tile above it
            State newState = State(*this);
            newState.swap(pos,pos - 3);
            vecStates.push_back(newState);
        }

        if (pos != 6 && pos != 7 && pos != 8){ // swaps the tile above it
            State newState = State(*this);
            newState.swap(pos,pos + 3);
            vecStates.push_back(newState);
        }

        if (pos != 0 && pos != 3 && pos != 6){ // swaps the tile above it
            State newState = State(*this);
            newState.swap(pos,pos - 1);
            vecStates.push_back(newState);
        }

        if (pos != 2 && pos != 5 && pos != 8){ // swaps the tile above it
            State newState = State(*this);
            newState.swap(pos,pos + 1);
            vecStates.push_back(newState);
        }

        return vecStates;
    }
};

string breadthFirstSearch_with_VisitedList(string const initialState, string const goalState){
    string path;

  clock_t startTime;
  startTime = clock();

  deque<State> nodesToVisit;
  vector<State> visitedList;
  int maxQLength = 0;


  //Init
    State init(initialState);
    State goal(goalState);
    nodesToVisit.push_back(init);

    int count = 0;
    int numOfStateExpansions = 0 ;
//
    while (!nodesToVisit.empty()){
        if(maxQLength < nodesToVisit.size()){maxQLength = nodesToVisit.size();}

        State cur = nodesToVisit.front();
        nodesToVisit.pop_front();
         //remove front

        if (cur == goal){
            //solution found
            cout << "solved!";
            break;
        }

        //Get children
        vector<State> children = cur.blankExpand();

        numOfStateExpansions += children.size();        

        //For each child
        for (State& child : children) {
            for (int i = 0 ; i < 9;i++){
                cout << child.state[i];
            }
            cout << " child" << endl;

          //If already visited ignore
          if (std::find(visitedList.begin(), visitedList.end(), child) != visitedList.end()) {
            // cout << "duplicate" << endl;
            continue;
          }

          //If not in nodes to Visit
          else if (std::find(nodesToVisit.begin(), nodesToVisit.end(), child) == nodesToVisit.end()) {
            //Add child
            nodesToVisit.push_back(child);
          }
        }
        visitedList.push_back(cur);

    }



//***********************************************************************************************************
    clock_t actualRunningTime = ((float)(clock() - startTime)/CLOCKS_PER_SEC);  
    return path;    

}

int main(){
    breadthFirstSearch_with_VisitedList("042158367", "123804765");
    //042158367
}

// 0 4 2
// 1 5 8
// 3 6 7

person Ryan Mason    schedule 23.04.2018    source источник


Ответы (1)


В вашем коде есть ряд недостатков, которые замедляют его работу. Я действительно удивлен, что у вас хватило терпения дождаться, пока он достигнет состояния нехватки памяти.

Главными виновниками являются обыски: std::find(visitedList.begin(), visitedList.end(), child) != visitedList.end() //and std::find(nodesToVisit.begin(), nodesToVisit.end(), child) == nodesToVisit.end()

Оба они выполняются за O(N), что звучит неплохо, но, поскольку вы выполняете их на каждом узле, это приводит к O(N2).

Вы можете исправить это, используя std::unordered_set<> вместо visitedList. Кроме того, вы можете добавлять узлы в visited_list, как только ставите их в очередь (вместо того, когда вы удаляете их из очереди). Таким образом, у вас будет только один поиск.

Н.Б. Вам нужно будет специализироваться на std::hash<State>, чтобы использовать std::unordered_set.

Еще один совет: эти cout << ... в вашем основном цикле действительно замедляют работу, потому что они вызывают сброс и синхронизацию с ОС по умолчанию, а закомментировав их, ваша программа будет работать намного быстрее.

На самом деле в вашем коде можно сделать еще несколько улучшений, но это тема для другого дня. Исправление алгоритмической сложности вернет ее в целостное царство вещей.

person Frank    schedule 23.04.2018
comment
Я ценю ваш ответ, cout просто от меня немного отлаживает, а не для того, чтобы быть полезным. Я поменяю свой текущий и посмотрю, что произойдет. Я не ждал, пока закончится память, но через 15 секунд я понял, что это будет вероятным исходом. Первоначально я тестировал неупорядоченные наборы, но не знал, как это реализовать, и просто перешел к вектору. - person Ryan Mason; 23.04.2018
comment
@RyanMason 15 секунд - это ничто для BFS, он мог бы работать в течение часа, не сталкиваясь с фактической нехваткой памяти и успешно находя правильное решение с вашей версией кода. Вам действительно следует изменить свой вопрос и описать поведение, которое вы видите, вместо ваших предположений. - person Frank; 23.04.2018
comment
после некоторого исследования unsorted_set, было бы полезно вместо этого использовать unsorted_map? - person Ryan Mason; 23.04.2018
comment
@RyanMason unsorted_map<> создает сопоставление между ключами и значениями. Если State является ключом, каким будет значение? - person Frank; 23.04.2018
comment
Хорошо, я понимаю, поэтому для реализации unsorted_map наиболее распространенным методом, который я видел, является создание структуры «хэш», которая перегружает operator(). Теперь мне нужно это сделать. Я уже перегружаю свой оператор comaprison, но могу ли я использовать хэш по умолчанию для состояния или мне нужно реконструировать мой массив int в хешируемую переменную. - person Ryan Mason; 23.04.2018
comment
@RyanMason, да, это то, что я имею в виду под специализацией std::hash‹State›. - person Frank; 23.04.2018
comment
Я написал большую часть структуры, единственная часть, в которой я не уверен, это то, что я хеширую из своего объекта. Могу ли я хешировать весь свой массив или мне нужно изменить сравнение, чтобы проверить совпадающую строку/целое, чтобы затем я мог хэшировать строку/целое внутри моей структуры - person Ryan Mason; 23.04.2018
comment
@RyanMason Вам нужно придумать способ присвоить уникальное значение std::size_t для каждого возможного состояния доски, это должно быть довольно легко сделать. - person Frank; 23.04.2018
comment
Я сделал хэш-функцию, но у меня есть некоторые ошибки, которые я не могу понять, как исправить. hastebin.com/ehepafeguz.cpp, если бы вы могли взглянуть, это было бы здорово. - person Ryan Mason; 25.04.2018