简体   繁体   中英

How can you check if a sequence of nodes exists in an undirected graph, where each node is adjacent to the next?

I have an undirected graph of letters in a rectangular format, where each node has an edge to the adjacent neighboring node. For example:

d x f p
o y a a
z t i b
l z t z

In this graph node "d" is adjacent to [x, y, o]. I want to check if the sequence of nodes "dot" exists in the graph, where each subsequent node is adjacent to the next. The main application is a word search game, where only words with adjacent letters count. For example, the sequence "zap" does NOT count, since the nodes are not adjacent. I do not need to check if the sequence is a real word, only that it is adjacent in the graph. My graph.h is as follows:

// graph.h

#include <queue>
#include "SLList.h"
#include "DynArray.h"

template<typename Type>
class Graph {

public:
    struct Edge {
        unsigned int toVertex; // index to vertex the edge connects to 
    };
    
    struct Vertex {
        // the data that this vertex is storing
        Type element;

        // the list of edges that connect this vertex to another vertex
        SLList<Edge> edges;

        ///////////////////////////////////////////////////////////////////////////
        // Function : addEdge
        // Parameters : toVertex - the index of the vertex we are adjacent to
        ///////////////////////////////////////////////////////////////////////////
        void addEdge(const unsigned int& toVertex) {
            Edge e;
            e.toVertex = toVertex;
            edges.addHead(e);
        }
    };

private:
    // dynarray of vertices
    DynArray<Vertex> vertices;
    // helper function to check if a vertex is a in a queue
    bool IsInQueue(DynArray<Edge> arrayOfEdges, unsigned int _toVertex) {
        for (unsigned int i = 0; i < arrayOfEdges.size(); ++i) {
            if (arrayOfEdges[i].toVertex == _toVertex)
                return true;
        }
        
        return false;
    }

public:
    /////////////////////////////////////////////////////////////////////////////
    // Function : addVertex
    // Parameters : value - the data to store in this vertex
    // Return : unsigned int - the index this vertex was added at
    /////////////////////////////////////////////////////////////////////////////
    unsigned int addVertex(const Type& value) {
        Vertex v;
        v.element = value;
        vertices.append(v);

        return vertices.size();
    }

    /////////////////////////////////////////////////////////////////////////////
    // Function : operator[]
    // Parameters : index - the index in the graph to access
    // Return : Vertex& - the vertex stored at the specified index
    /////////////////////////////////////////////////////////////////////////////
    Vertex& operator[](const unsigned int& index) {
        return vertices[index];
    }

    /////////////////////////////////////////////////////////////////////////////
    // Function : size
    // Return : unsiged int - the number of vertices in the graph
    /////////////////////////////////////////////////////////////////////////////
    unsigned int size() const {
        return vertices.size();
    }

    /////////////////////////////////////////////////////////////////////////////
    // Function : clear
    // Notes : clears the graph and readies it for re-use
    /////////////////////////////////////////////////////////////////////////////
    void clear() {
        // for each node, remove all its edges
        // then remove the node from the array
        for (unsigned int i = 0; i < vertices.size(); ++i) {
            vertices[i].edges.clear();
        }
        vertices.clear();
    }   
};

So far I tried:

my algorithm:
finding the starting node
setting a current node to this start node
searching all edges of the current node for the next node in sequence without visiting nodes that have been visited
if next node in sequence is found then current is set to next and next is incremented
if current == end and next == null then return true
else false

However, this does not work every time. For example, it works for "dot", but not "pay" in the above graph. This is because once it visits the second "a" it marks as visited and cannot find "y" anymore. I believe there are other problems with this algorithm. I have searched other answers on here, but they only explain how to find a path from a start node to an end node, where the path doesn't matter. In this case, the path is what matters.

Solution in c++ using my graph.h preferred.

Here is a simple Depth-First Search-based procedure that attempts to find a path that creates a specified string in a grid of characters. This DFS is an example of a basic brute-force algorithm, as it simply tries all possible paths that could be right. In the below program, I use my own Graph class (sorry), but it should be simple enough to understand. Here is my code in C++:

#include <iostream>
#include <fstream>
#include <cmath>
#include <algorithm>
#include <vector>

using namespace std;

struct Graph{
    int rows, cols;
    vector <vector<char>> grid;

    // DFS: Recursively tries all possible paths - SEE THE BELOW FUNCTION FIRST
    void dfs(int r, int c, size_t len, string &str, bool &done, auto &vis, auto &path){

        // if (len == str.size()), that means that we've found a path that
        // corresponds to the whole string, meaning that we are done.
        if(len == str.size()){
            done = true;
            return;
        }

        // Check all nodes surrounding the node at row r and column c
        for(int next_r = r-1; next_r <= r+1; ++next_r){
            for(int next_c = c-1; next_c <= c+1; ++next_c){

                // Bounds check on next_r and next_c
                if(next_r < 0 || next_r >= rows){continue;}
                else if(next_c < 0 || next_c >= cols){continue;}

                // KEY: We don't visit nodes that we have visited before!
                if(vis[next_r][next_c]){
                    continue;
                }

                // ONLY if grid[next_r][next_c] happens to be the next character in str
                // that we are looking for, recurse.
                if(grid[next_r][next_c] == str[len]){
                    vis[next_r][next_c] = true;
                    path.push_back({next_r, next_c});
                    dfs(next_r, next_c, len + 1, str, done, vis, path);

                    // If done is true, that means we must've set it to true in
                    // the previous function call, which means we have found
                    // a valid path. This means we should keep return-ing.
                    if(done){return;}

                    vis[next_r][next_c] = false;
                    path.pop_back();
                }
            }

            if(done){return;} // see the above comment
        }
    }

    // Returns a vector <pair<int, int>> detailing the path, if any, in the grid
    // that would produce str.
    vector <pair<int, int>> get_path_of(string &str){
        bool done = false;
        vector <pair<int, int>> path;

        // Try starting a DFS from every possible starting point until we find a valid
        // path
        for(int r = 0; r < rows; ++r){
            for(int c = 0; c < cols; ++c){
                vector <vector<bool>> vis(rows, vector <bool> (cols, false));
                dfs(r, c, 0, str, done, vis, path);

                // Found a path during the above function call! We can return now
                if(done){
                    return path;
                }
            }
        }

        return {};
    }

    Graph(int r, int c){
        rows = r;
        cols = c;
        grid = vector <vector<char>> (r, vector <char> (c));
    }
};

int main()
{
    // Input in the number of rows and columns in the grid
    int R, C;
    cin >> R >> C;

    Graph G(R, C);

    // Input the letters of the grid to G
    for(int i = 0; i < R; ++i){
        for(int j = 0; j < C; ++j){
            cin >> G.grid[i][j];
        }
    }

    // Input the strings to find in G
    string str;
    while(cin >> str){
        vector <pair<int, int>> path = G.get_path_of(str);

        cout << "PATH OF " << str << ": ";
        for(const pair <int, int> &item : path){
            cout << "{" << item.first << ", " << item.second << "} ";
        }
        cout << "\n";
    }

    return 0;
}

If you have any questions, please don't hesitate to ask!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM