Home > Blockchain >  Finding the shortest path between nodes in graph
Finding the shortest path between nodes in graph

Time:08-19

I have a state machine that looks like this:

           G--H
          /
A--B--C--D--E--F

I want to have a function, goToState(target) that has as an input argument the target state, and then the function will execute all the transitions starting from the current state until it reaches the target state.

For example, let's say that the current state is B and we call goToState(F). Then the function will do the following state transitions B->C, C->D, D->E, E->F.

The transitions work both ways so if current state is F and we call goToState(G), then the function will do F->E, E->D, D->G.

If we had a linear state machine (eg, no branch G--H), then I would just do an array of functions for each transition, in the legal order, and then find the index for current state and the index for the target state and call all the transition function in between those two indexes in a for loop.

However now that I have a branch, this method would not work. What would be the most efficient way to encode the legal transitions and to implement a function that executes them in the right order based on the target state in C?

EDIT: As some other users very well pointed out, what I'm looking for is some sort of path finding algorithm that finds the shortest path between two states. I just couldn't find the right words to formulate it properly in the original post. I need the simplest path finding algorithm that would work for a state diagram as shown above. The state diagram will never get more complex than this so the algorithm doesn't need to cover any other scenarios either.

EDIT2: Updated the title to better describe the problem. Your comments helped me find the right terminology so I can search the web for the solution.

CodePudding user response:

I found the answer on GeeksforGeeks website: https://www.geeksforgeeks.org/shortest-path-unweighted-graph/

It's a modified version of the Breadth-First Search algorithm and does exactly what I need!

Thank you all for your answers and comments. They helped me find the right terminology to search for the right solution online.

CodePudding user response:

You can model the states as an array of structs, each of which contains a function pointer for the transition and an array of possible destination states.

Then, create a function that takes the current and destination state and have it loop through the list of possible states. If one matches the destination, put that at the start of an empty list of states and return the list. If not, recurse through each possible intermediate state until either one returns a nonempty list and add the current state to the front of the list.

After the recursive function returns, you can iterate through the list running the transitions.

#include <stdio.h>
#include <stdlib.h>

typedef void (*func)(void);    // modify as needed
typedef enum { NONE=-1, A, B, C, D, E, F, G, H, MAX_STATES } states;

struct transitions {
    func transition;
    states slist[MAX_STATES];
};

struct tlist {
    struct transitions *trans;
    struct tlist *next;
};

void trans_a(void) { printf("transition A\n"); }
void trans_b(void) { printf("transition B\n"); }
void trans_c(void) { printf("transition C\n"); }
void trans_d(void) { printf("transition D\n"); }
void trans_e(void) { printf("transition E\n"); }
void trans_f(void) { printf("transition F\n"); }
void trans_g(void) { printf("transition G\n"); }
void trans_h(void) { printf("transition H\n"); }

struct transitions transitions[] = {
    { trans_a, { B, NONE } },
    { trans_b, { A, C, NONE } },
    { trans_c, { B, D, NONE } },
    { trans_d, { C, G, E, NONE } },
    { trans_e, { D, F, NONE } },
    { trans_f, { E, NONE } },
    { trans_g, { D, H, NONE } },
    { trans_h, { G, NONE } }
};

struct tlist *getStates(states prev, states start, states end)
{
    int i;
    for (i = 0; transitions[start].slist[i] != NONE; i  ) {
        if (transitions[start].slist[i] == prev) continue;
        if (transitions[start].slist[i] == end) {
            struct tlist *entry = malloc(sizeof *entry);
            entry->trans = transitions   start;
            entry->next = NULL;
            return entry;
        }
        struct tlist *list = getStates(start, transitions[start].slist[i], end);
        if (list) {
            struct tlist *entry = malloc(sizeof *entry);
            entry->trans = transitions   start;
            entry->next = list;
            return entry;
        }
    }
    return NULL;
}

void runStates(states start, states end)
{
    printf("from %d to %d\n", start, end);
    struct tlist *list = getStates(NONE,start,end);
    while (list) {
        struct tlist *tmp = list;
        list->trans->transition();
        list = list->next;
        free(tmp);
    }
    printf("\n");
}

int main()
{
    runStates(A,H);
    runStates(A,E);
    runStates(E,A);
    runStates(F,H);
    return 0;
}

Output:

from 0 to 7
transition A
transition B
transition C
transition D
transition G

from 0 to 4
transition A
transition B
transition C
transition D

from 4 to 0
transition E
transition D
transition C
transition B

from 5 to 7
transition F
transition E
transition D
transition G

CodePudding user response:

I think the answers already posted are adequate to answer the question, but I want to add that this is a classical problem that is studied in the abstract mathematical field of theoretical computations. The mathematical model of computation that describes what you're asking for is called a Finite State Machine. An FSM is an abstract machine that contains (without going to deep into math jargon or notation):

  1. An input language, which in your case is whatever you pass to the function to find out which state your program should transition to ("target" in my example)
  2. A set of states, which in your case is A, B, C, D, E, F , G, and H
  3. An initial state, which is whichever of the set of states above that your program starts in
  4. A transition function that tells each state where to go from any given state given any input in the input language (the function "transtition" in my example)
  5. A set of final states which is either empty or a subset of your set of states

The crux of your question is about item 4, the transition function. A C implementation of a transition function for an FSM that may fit your needs may look something like this:

//Model states as integers from 0-7 plus an extra invalid state for error handling
enum state{A, B, C, D, E, F, G, H, invalid};

enum state transition(enum state current, enum state target){
  
  if(target < A || target > H || current < A || current > H)
    return invalid;
  else if(target == current)
    return current;

  switch(current){
    case A:
        return B;
    case B: //B and C have combinable transition behavior
    case C:
      if(target > current)
        return current   1;
      else
        return current - 1;
    case D:
      if(target < D)
        return C;
      else if(target > F)
        return G;
      else
        return E;
    case E:
      if(target < E)
        return D;
      else
        return F;
    case F:
      return E;
    case G:
      if(target < G)
        return D;
      else
        return H;
    case H:
      return G;
    //Edge-cases have been managed so no need for default case
  }
}

Then in you can implement goToState() like so:

int goToState(enum state current,enum state target){

  while(current != target && current != invalid){
    current = transtition(current, target);
    handle(current);  //Do whatever must be done for whatever state we are in along the way
  }
  if(current == invalid)
    return -1; //Error code
  else
    return 0;
}

CodePudding user response:

No matter the state machine, you'll want to avoid "stateghetti", which is what happens when each and every state makes multiple, local decisions on what state to execute next.

Instead, have each state return a result code. You can still have an array of function pointers no matter. Then at the top level of the program, write something along the lines of this pseudo:

while(...)
{
  result = state_machine[current_state]();
  current_state = evaluate(current_state, result);
}

The function evaluate() is the only place in your code where state transitions are allowed. Optionally it can double as your top-level error handler.

Then simply code down all dependencies you need inside this function:

...
if(state==C && result==OK)
  return D;

if(state==D)
{
  if(result==OK)
    return E;
  else
    return G;
}
...
  • Related