Showing posts with label artificial intelligence. Show all posts
Showing posts with label artificial intelligence. Show all posts

Breadth and depth first search - part 3


As an extra, today I'm posting the C++ code of the breadth and depth first search algorithms. Take a look at part 1 and part 2 of this series.

When I had to hand in the work for the artificial intelligence discipline, the teacher wanted the code in C++ and I had already started developing the code in C#. The result was two versions of the same functions. The good part is that I could master both languages while developing such a code.

The code presented here uses an adjacency matrix to represent the links between the cities that are part of the Romania map shown bellow.

The following is the adjacency matrix:

// Adjacency matrix
int map[21][21] = {
/*   A B C D E F G H I L M N O P R S T U V Z */
  {0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0},
  {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1}, // Arad
  {2,0,0,0,0,0,1,1,0,0,0,0,0,0,1,0,0,0,1,0,0}, // Bucharest
  {3,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0}, // Craiova
  {4,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0}, // Dobreta
  {5,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0}, // Eforie
  {6,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0}, // Fagaras
  {7,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, // Girgiu
  {8,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0}, // Hirsova
  {9,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0}, // Iasi
  {0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0}, // Lugoj
  {1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0}, // Mehadia
  {2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0}, // Neamt
  {3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1}, // Oradea
  {4,0,1,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0}, // Pitesti
  {5,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0}, // Rimnicu Vilcea
  {6,1,0,0,0,0,1,0,0,0,0,0,0,1,0,1,0,0,0,0,0}, // Sibiu
  {7,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0}, // Timisoara
  {8,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0}, // Urziceni
  {9,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0}, // Vaslui
  {0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0}  // Zerind
};

Note that the first commented line represents the initial letter of each city's name. The mapping done with the adjacency matrix refers to these letters so that it's easier to understand. For example, getting the first entry of the adjacency matrix that refers to Arad: we have that Arad has paths that lead us to Sibiu, Timisoara and Zerind, thus we put a value of 1 on the columns that represent those cities, in this case, the columns beneath the letters S, T and Z. That's how the mapping is done. We put a value of 0 on the other columns to state that there is no path that leads us to those cities.

The code also has a hand made version of the stack and queue data structures. Each one of these structures is on its proper header file and are inline functions. See their implementations:

// Queue
struct Queue
{
  int start, end, tot;

  int info[max + 1];
};

void StartQueue(Queue *q)
{
  q->tot = 0;

  q->start = 1;

  q->end = 0;
}

int IsQueueEmpty(Queue *q)
{
  return q->tot == 0 ? 1 : 0;
}

int IsQueueFull(Queue *q)
{
  return q->tot == max ? 1 : 0;
}

int Adc(int x)
{
  return x == max ? 1 : x + 1;
}

void Enqueue(Queue *q, int x)
{
  if(!IsQueueFull(q))
  {
    q->end = Adc(q->end);

    q->info[q->end] = x;

    q->tot++;
  }
}

int Dequeue(Queue *q)
{
  int ret = 0;

  if(!IsQueueEmpty(q))
  {
    ret = q->info[q->start];
  
    q->start = Adc(q->start);
  
    q->tot--;
  }

  return ret;
}
// End Queue
// Stack
struct Stack
{
  int topo;

  int info[max + 1];
};

void StartStack(Stack *s)
{
  s->topo = 0;
}

int IsStackEmpty(Stack *s)
{
  return s->topo==0 ? 1 : 0;
}

int IsStackFull(Stack *s)
{
  return s->topo == max ? 1 : 0;
}

void Push(Stack *s, int x)
{
  if(!IsStackFull(s))
  {
    s->topo++;
  
    s->info[s->topo] = x;
  }
}

int Pop(Stack *s)
{
  int ret = 0;

  if(!IsStackEmpty(s))
  {
    ret = s->info[s->topo];
  
    s->topo--;
  }

  return ret;
}
// End Stack

The Breadth First Search and Depth First Search functions are written in the same fashion of the C# code, but with little modifications.

void BreadthFirstSearch(int origin, int destination)
{
  Queue *q = new Queue();

  StartQueue(q);

  Enqueue(q, origin);

  while(IsQueueEmpty(q) == 0)
  {
    int u = Dequeue(q);

    if(u == destination)
    {
      printf("Path found.");

      break;
    }
    else
    {
      visited[u] = 1;

      for(int v = 1; v <= 20; v++)
      {
        if(map[u][v] != 0)
        {
          if(visited[v] == 0)
          {
            visited[v] = 1;

            parents[v] = u;

            if(v != destination)
            {
              if(!IsQueueFull(q))
              {
                Enqueue(q, v);

                ShowPath(v);

                printf("\n");
              }
              else
              {
                printf("Queue full.");

                break;
              }
            }
            else
            {
              ShowPath(v);

              return;
            }
          }       
        }
      }
    }
  }
}
void DepthFirstSearch(int origin, int destination)
{
  Stack *s = new Stack();

  StartStack(s);

  Push(s, origin);

  while(IsStackEmpty(s) == 0)
  {
    int u = Pop(s);

    if(u == destination)
    {
      printf("Path found.");

      break;
    }
    else
    {
      visited[u] = 1;

      for(int v = 1; v <= 20; v++)
      {
        if(map[u][v] != 0)
        {
          if(visited[v] == 0)
          {
            visited[v] = 1;

            parents[v] = u;

            if(v != destination)
            {
              if(!IsStackFull(s))
              {
                Push(s, v);

                ShowPath(v);

                printf("\n");
              }
              else
              {
                printf("Stack full.");

                break;
              }
            }
            else
            {
              ShowPath(v);

              return;
            }
          }      
        }
      }
    }
  }
}

To show the travelled paths there is a recursive function called ShowPath:

void ShowPath(int u)
{
  if(parents[u] != 0)
    ShowPath(parents[u]);

  printf(" %s", cities[u]);
}

You see, I had finalized the C# code and even sent the code to the teacher, but I received his reply stating that he wanted the code in C++. I complained with him! I told him about the easiness that modern programming languages as C# offers us when writing code.

Today, the data structures (queue and stack) "hand made" in this code are present in modern and optimized fashions inside standard libraries. We just need to instantiate an object from that specific class, in this case, stack or queue, and tada, we get lots of functions that do the hard work. But the point here is not the easiness. What the teacher wanted to force us to do was to comprehend how those data structures really function.

Nothing is better than writing the data structures by yourself. Although I didn't agree at the time, I thank him for forcing me to learn. Needless to say, these are basic data structures and are used in a great amount of code. You'll see these data structures during your entire developer life.

Visual Studio C++ Console Application
You can get the Microsoft Visual Studio Project and the app executable at:

http://leniel.googlepages.com/BreadthDepthFirstSearchCPlusPlus.zip

Breadth and depth first search - part 2


As I've written in the previous post Breadth and depth first search - part 1 - I'll dive in more details and explain how to use the breadth and depth search methods. We'll execute a test case using the Romania map shown bellow, print the traveled paths in the screen, calculate the shortest path possible between two cities using Dijkstra's algorithm and print such a path in the screen.

To accomplish all the above let's start presenting the data structures used to represent the map in the C# programming language:
 

public class Node
{
  public Node(string key, object data);
  public Node(string key, object data, AdjacencyList neighbors);
 
  public virtual object Data { get; set; }
  public virtual string Key { get; }
  public virtual AdjacencyList Neighbors { get; }
  public virtual Node PathParent { get; set; }
 
  protected internal virtual void AddDirected(EdgeToNeighbor e);
  protected internal virtual void AddDirected(Node n);
  protected internal virtual void AddDirected(Node n, int cost);
}

The Node class will be used to represent each city of the map.

Each city has its name represented by the key property and some other relevant data represented by the data property. Each city also has an adjacency list implemented by a specific class called AdjacencyList. This adjacency list represents the neighbors cities of a given city. For example, in the above map the neighbors cities of Bucharest are: Urziceni, Giurgiu, Pitesti and Fagaras.

Let's see the code of another class:

public class Graph
{
  public Graph();

  public Graph(NodeList nodes);
 
  public virtual int Count { get; }

  public virtual NodeList Nodes { get; }
 
  public virtual void AddDirectedEdge(Node u, Node v);
  public virtual void AddDirectedEdge(string uKey, string vKey);
  public virtual void AddDirectedEdge(Node u, Node v, int cost);
  public virtual void AddDirectedEdge(string uKey, string vKey,
int cost);
  public virtual void AddNode(Node n);
  public virtual Node AddNode(string key, object data);
  public virtual void AddUndirectedEdge(Node u, Node v);
  public virtual void AddUndirectedEdge(string uKey, string vKey);
  public virtual void AddUndirectedEdge(Node u, Node v, int cost);
  public virtual void AddUndirectedEdge(string uKey, string vKey,
int cost);
  public virtual void Clear();
  public virtual bool Contains(Node n);
  public virtual bool Contains(string key);
}

The Graph class has a property that references a collection of nodes, that is, a collection of cities. This collection of cities is represented by the class NodeList that implements the so used interface IEnumerable.

As you can see the Graph class has methods that add directed or undirected edges to the graph. Each line that connects two cities (vertexes) in the Romania map is considered an edge.

The map above contains only undirected edges because they aren't defined just as one way paths between the cities. It's possible to go from Bucharest to Urziceni and then come back to Bucharest for example. So it's a two way path.

Above each line in the map is a value that represents the path cost between two cities. Let's consider this cost as the distance in miles between the cities. The path cost could be any other variable, for example, the time spent to traverse the distance (edge). The cost variable can vary according to the problem.

I implemented a class called Pathfinding as follows:
 

class Pathfinding
{
  private static Graph graph = new Graph();
 
  ...
 
  public static void BreadthFirstSearch(Node start, Node end)
  {
    ...
  }
  public static void DepthFirstSearch(Node start, Node end)
  {
    ...
  }

  ...
}

This class has additional properties and methods as ShortestPath and PrintPath. I won't spend time explaining its additional methods because they are already well explained in Part 5: From Trees to Graphs (article by Scott Mitchell). So, let's run a test case. For this we need to fill the graph with the Romania map data.
 

class Program
{
  static void Main(string[] args)
  {
    Pathfinding pathFinding = new Pathfinding();
 
    Node start, end;
 
    // Vertexes
    pathFinding.Graph.AddNode("Arad", null);
    pathFinding.Graph.AddNode("Bucharest", null);
    pathFinding.Graph.AddNode("Craiova", null);
    pathFinding.Graph.AddNode("Dobreta", null);
    pathFinding.Graph.AddNode("Eforie", null);
    pathFinding.Graph.AddNode("Fagaras", null);
    pathFinding.Graph.AddNode("Giurgiu", null);
    pathFinding.Graph.AddNode("Hirsova", null);
    pathFinding.Graph.AddNode("Iasi", null);
    pathFinding.Graph.AddNode("Lugoj", null);
    pathFinding.Graph.AddNode("Mehadia", null);
    pathFinding.Graph.AddNode("Neamt", null);
    pathFinding.Graph.AddNode("Oradea", null);
    pathFinding.Graph.AddNode("Pitesti", null);
    pathFinding.Graph.AddNode("Rimnicu Vilcea", null);
    pathFinding.Graph.AddNode("Sibiu", null);
    pathFinding.Graph.AddNode("Timisoara", null);
    pathFinding.Graph.AddNode("Urziceni", null);
    pathFinding.Graph.AddNode("Vaslui", null);
    pathFinding.Graph.AddNode("Zerind", null);
 
    // Edges
 
    // Arad <-> Zerind
    pathFinding.Graph.AddUndirectedEdge("Arad", "Zerind", 75);
    // Arad <-> Timisoara
    pathFinding.Graph.AddUndirectedEdge("Arad", "Timisoara", 118);
    // Arad <-> Sibiu
    pathFinding.Graph.AddUndirectedEdge("Arad", "Sibiu", 140);
 
    // Bucharest <-> Urziceni
    pathFinding.Graph.AddUndirectedEdge("Bucharest", "Urziceni",
85);
    // Bucharest <-> Giurgiu
    pathFinding.Graph.AddUndirectedEdge("Bucharest", "Giurgiu",
90);
    // Bucharest <-> Pitesti
    pathFinding.Graph.AddUndirectedEdge("Bucharest", "Pitesti",
101);
    // Bucharest <-> Fagaras
    pathFinding.Graph.AddUndirectedEdge("Bucharest", "Fagaras",
211);
 
    // Craiova <-> Dobreta
    pathFinding.Graph.AddUndirectedEdge("Craiova", "Dobreta",
120);
    // Craiova <-> Pitesti
    pathFinding.Graph.AddUndirectedEdge("Craiova", "Pitesti",
138);
    // Craiova <-> Rimnicu Vilcea
    pathFinding.Graph.AddUndirectedEdge("Craiova",
"Rimnicu Vilcea", 146);
 
    // Dobreta <-> Mehadia
    pathFinding.Graph.AddUndirectedEdge("Dobreta", "Mehadia", 75);
 
    // Eforie <-> Hirsova
    pathFinding.Graph.AddUndirectedEdge("Eforie", "Hirsova", 86);
 
    // Fagaras <-> Sibiu
    pathFinding.Graph.AddUndirectedEdge("Fagaras", "Sibiu", 99);
 
    // Hirsova <-> Urziceni
    pathFinding.Graph.AddUndirectedEdge("Hirsova", "Urziceni", 98);
 
    // Iasi <-> Neamt
    pathFinding.Graph.AddUndirectedEdge("Iasi", "Neamt", 87);
    // Iasi <-> Vaslui
    pathFinding.Graph.AddUndirectedEdge("Iasi", "Vaslui", 92);
 
    // Lugoj <-> Mehadia
    pathFinding.Graph.AddUndirectedEdge("Lugoj", "Mehadia", 70);
    // Lugoj <-> Timisoara
    pathFinding.Graph.AddUndirectedEdge("Lugoj", "Timisoara",
111);
 
    // Oradea <-> Zerind
    pathFinding.Graph.AddUndirectedEdge("Oradea", "Zerind", 71);
    // Oradea <-> Sibiu
    pathFinding.Graph.AddUndirectedEdge("Oradea", "Sibiu", 151);
 
    // Pitesti <-> Rimnicu Vilcea
    pathFinding.Graph.AddUndirectedEdge("Pitesti", "Rimnicu Vilcea", 97);
 
    // Rimnicu Vilcea <-> Sibiu
    pathFinding.Graph.AddUndirectedEdge("Rimnicu Vilcea", "Sibiu",
80);
 
    // Urziceni <-> Vaslui
    pathFinding.Graph.AddUndirectedEdge("Urziceni", "Vaslui",
142);
 
    start = pathFinding.Graph.Nodes["Oradea"];
 
    end = pathFinding.Graph.Nodes["Neamt"];
 
    Console.WriteLine("\nBreadth First Search algorithm");
 
    Pathfinding.BreadthFirstSearch(start, end);
 
    foreach(Node n in pathFinding.Graph.Nodes)
      n.Data = null;
 
    Console.WriteLine("\n\nDepth First Search algorithm");
 
    Pathfinding.DepthFirstSearch(start, end);
 
    Console.WriteLine("\n\nShortest path");
 
    Pathfinding.ShortestPath(start, end);
 
    pathFinding.Graph.Clear();
 
    Console.ReadKey();
  }
}

Firstly we create a new instance of the Pathfinding class and two instances of the Node class that will reference the start and end city respectively.

The pathfinding object has a graph property that we use to store the nodes, that is, the cities of the map. To accomplish this the method AddNode of the Graph class is used.

The key that represents the node is the name of the city in this case.

After adding the cities to the graph it's time to connect the cities by means of the edges between them. For each undirected edge of the map the fourth overload of the AddUndirectedEdge method is used. The method receives as arguments the names of the edge's vertexes and the path cost.

Supposing we want to go from Oradea to Neamt, we must set the start and end node appropriately and that is done when the start and end node are assigned the values already present in the graph.

After everything is set up we can run the the breadth and depth first search methods. To start I call the the method BreadthFirstSearch already presented in the previous post Breadth and depth first search - part 1. The method receives the start and end nodes as arguments and traverses the graph according to the breadth first search algorithm. During the traversal it prints the paths in the screen so that it's easier to visually debug the code.

We use the same graph data to run the depth first search method but to avoid a wrong behavior it's necessary to set the data property of each graph's node to null. It's because such property is used to store a value indicating if that node was already visited during the path traversal of the breadth first search method.

OK. Now that the graph data is prepared we can run the depth first search method invoking the DepthSearchMethod of the Pathfinding class. This method receives the start and end nodes as arguments and traverses the graph according to the depth first search algorithm. During the traversal it prints the paths in the screen so that it's easier to visually debug the code.

The last and so important method is the ShortestPath one. The shortest path problem can be calculated through different algorithms. In this case the algorithm used (Dijkstra's algorithm) is suitable because we don't have negative costs otherwise we should use other algorithms. The ShortestPath method of the Pathfinding class receives as arguments the start and end nodes and prints in the screen the total distance of such a path and the cities travelled.

See the screenshot of the output:

Note: if you want to see a C++ implementation of the breadth and depth first search, check the third part of this series: Breadth and depth first search - part 3.

Get the complete code and executable of this post at:

http://leniel.googlepages.com/BreadthDepthFirstSearchCSharp.rar

Breadth and depth first search


Search algorithms
The idea behind search algorithms is to simulate the exploration of a space of states or search space through the generation of successor states already explored.

Tree based search algorithms
Tree search algorithms are the heart of searching techniques. The basic principle is that a node is taken from a data structure, its successors examined and added to the data structure. By manipulating the data structure, the tree is explored in different orders for instance level by level (breadth-first search) or reaching a leaf node first and backtracking (depth-first search).

Definitions

  • State - a representation of a physical configuration.
  • Node - a data structure part of a search tree. A node has the following properties: father, sons, depth and path cost.

States don't have father, sons, depth and path cost.

Types of problems

  • Deterministic accessible - simple states problem.
  • Deterministic inaccessible - multiple states problem.
  • Non deterministic inaccessible - contingency problem (maybe) - sensors must be used during the execution process. The solution is a tree or a policy. Many times alternates between search and execution.
  • Unknown search space - exploration problem (online).

Selecting a search space
The real world is extremely complex. The search space must be abstracted from the solution process. Abstracting we have:

  • Abstract state - the set of real states.
  • Abstract operator - combination of real actions.
  • Abstract solution - set of real paths that represents a solution in the real world.

Each abstract action must be easier than the action in the real problem.

A simple algorithm

function GeneralSearch(problem, strategy)
  return a solution or failure
 
Initialize the search tree using the initial state of the problem
 
loop do
  if there are no candidates for expansion then
    return failure
 
Choose a leaf node for expansion according to strategy
 
if the node contains a goal state then
  return the corresponding solution
else
  expand the node and add the resulting nodes to the tree
 
end

Algorithm implementation

function GeneralSearch(problem, QueueingFunction)
nodes <- MakeQueue(MakeNode(InitialState[problem]))
loop do
  if nodes is empty then
    return failure
  node <- RemoveFront(nodes) a leaf node for expansion according
to strategy
  if GoalTest[problem] applied to State(node) succeeds then
    return node
  nodes <- QueueingFunction(nodes, Expand(node, Operators[problem]))
end

Search strategies
A search strategy is a choice made between the methods used to expand the nodes. Each method has its proper order of expansion. Strategies are evaluated according to following parameters:

  • Time complexity - the number of generated or expanded nodes.
  • Space complexity - maximum number of nodes presented in the memory.
  • Optimality - the solution found has the minimum cost?
Complexities in time and space are measured in terms of:
  • b - maximum ramification factor of the search tree.
  • d - depth of the solution that has the minimum cost.
  • m - maximum depth of the search space (can be ∞) infinite.

The two strategies presented in this post (breadth and depth first search) are considered uninformed strategies because they only use the information available in the problem definition.

Breadth first search
The breadth first search expands the less deep node. The data structured used to implement this search algorithm is a queue.

Breadth first search properties
Is complete? Yes, if (b is finite).

Time: 1 + b2 + b3 + … + bd = O(bd). e.g.: exponential in d

Space: O(bd) (maintains all the nodes in memory)

Optimal? Yes, if cost per step is 1; in general is not optimal.

Space is the big problem for can generate nodes at a rate of 1 MB / sec. 24 hours = 86 GB.

Depth first search
The depth first search expands the deepest node.

The data structure used to implement this search algorithm is a stack.

An important note about the depth first search is that it can execute infinite cycles so it’s mandatory to check the states already visited to avoid such infinite cycles.

Depth first search properties
Is complete? No. It is imperfect in spaces of infinite depth or in cyclic paths. Is complete only in a finite search space.

Time: O(bm). Is very bad if m is a lot greater than d. If solutions are dense can be fastest than the breadth first search.

Space: O(bm) e.g.: linear in space

Optimal? No.

Working problem: vacation in Romania
In this post let's explore a simple states problem as shown in the following picture:

Suppose we are taking a vacation in Romania. More specifically we are in the city called Arad. We want to go to Bucharest the capital city of Romania and our flight takes off tomorrow. Breaking down our problem, we have:

  • Objective - go to Bucharest.
  • Search space - several cities to get to Bucharest.
  • Operator - drive through the cities.

In this case a solution is a sequence of cities so that the last city is Bucharest.

A valid path could be: Arad, Sibiu, Fagaras, Bucharest. e.g.: Arad -> Zerind represents a complex set of possible paths, returns, pauses for resting, etc.

To guarantee the realizability, any real state in “Arad” must take us to some real state in “Zerind”.

The problem's formulation can be:

  • Initial state - Arad
  • Operator (or successor function S(x)) - eg.: Arad -> Zerind, Arad -> Sibiu, etc.
  • Goal test - can be explicit or implicit as x = Bucharest or NoDirty(x).
  • Path cost (additive) - can be the sum of distances, used operators, etc.

The solution is a sequence of operators that takes from the initial state to the goal state.

Implementing the breadth and depth first search in C#
The following methods use two classes implemented by Scott Mitchell [1]. The classes are: Node and EdgeToNeighbor.

I just added a new property called PathParent in the node class.

public static void BreadthFirstSearch(Node start, Node end)
{
  Queue<Node> queue = new Queue<Node>();
 
  queue.Enqueue(start);
 
  while(queue.Count != 0)
  {
    Node u = queue.Dequeue();
 
    // Check if node is the end
    if(u == end)
    {
      Console.Write("Path found.");
 
      break;
    }
    else
    {
      u.Data = "Visited";
 
      // Expands u's neighbors in the queue
      foreach(EdgeToNeighbor edge in u.Neighbors)
      {
        if(edge.Neighbor.Data == null)
        {
          edge.Neighbor.Data = "Visited";
 
          if(edge.Neighbor != end)
          {
            edge.Neighbor.PathParent = u;
 
            PrintPath(edge.Neighbor);
          }
          else
          {
            edge.Neighbor.PathParent = u;
 
            PrintPath(edge.Neighbor);
 
            return;
          }
 
          Console.WriteLine();
        }
        /* shows the repeated nodes
        else
        {
          Console.Write(edge.Neighbor.Key);
        } */
 
        queue.Enqueue(edge.Neighbor);
      }
    }
  }
}
public static void DepthFirstSearch(Node start, Node end)
{
  Stack<Node> stack = new Stack<Node>();
 
  stack.Push(start);
 
  while(stack.Count != 0)
  {
    Node u = stack.Pop();
 
    // Check if node is the end
    if(u == end)
    {
      Console.WriteLine("Path found");
 
      break;
    }
    else
    {
      u.Data = "Visited";
 
      // Store n's neighbors in the stack
      foreach(EdgeToNeighbor edge in u.Neighbors)
      {
        if(edge.Neighbor.Data == null)
        {
          edge.Neighbor.Data = "Visited";
 
          if(edge.Neighbor != end)
          {
            edge.Neighbor.PathParent = u;
 
            PrintPath(edge.Neighbor);
          }
          else
          {
            edge.Neighbor.PathParent = u;
 
            PrintPath(edge.Neighbor);
 
            return;
          }
 
          Console.WriteLine();
 
          stack.Push(edge.Neighbor);
        }
        /* shows the repeated nodes
        else
        {
          Console.Write(edge.Neighbor.Key);
        } */
      }
    }
  }
}

Summary
The formulation of a problem requires abstraction to avoid irrelevant details of the real world so that a search space can be defined and explored.

In a next post I'll dive in more details and explain how to use the breadth and depth search methods. We'll execute a test case using the Romania map shown in the picture above, print the traveled paths in the screen, calculate the shortest path possible between two cities using Dijkstra's algorithm and print such a path in the screen. Before that I recommend the reading of the excellent revision about data structures written by Scott Mitchell [1]. Although I only use the facts exposed in part 5 of Scott's article: From Trees to Graphs, it's worth to read starting in part 1. For now, look and think about the breadth and depth first search implementation in C#.

References
[1] Mitchell, Scott. An Extensive Examination of Data Structures. 2004. Available at <http://msdn2.microsoft.com/en-us/library/aa289152(VS.71).aspx>. Accessed on January 8, 2008.

[2] Artificial Intelligence Depot. Blind search. Available at <http://ai-depot.com/Tutorial/PathFinding-Blind.html>. Accessed on January 8, 2008.