AdSense

Showing posts with label Depth-first search. Show all posts
Showing posts with label Depth-first search. Show all posts

Saturday, March 28, 2015

Task Scheduler

Given the interface below, implement a task scheduler.
interface Task {
    void Run();
    Set<Task> GetDependencies();
}

Additionally, the task scheduler should follow two rules.
1. Each task may only be executed once.
2. The dependencies of a task should be executed before the task itself.

This one is quite straightforward. Given a set of task, using a DFS approach.


public class TaskScheduler {
 Set executed;
 Set allTasks;
 Set inProcess;
 public TaskScheduler(Set tasks){
  allTasks = tasks;
  executed = new HashSet();
  inProcess = new HashSet();
 }
 public void schedule(Set allTasks){
  for(Task t : allTasks){
   if(executed.contains(t))
    continue;
   if(!inProcess.isEmpty() && inProcess.contains(t)){
    t.Run();
    inProcess.remove(t);
    executed.add(t);
    continue;
   }
   inProcess.add(t);
   schedule(t.GetDependencies());
   t.Run();
   inProcess.remove(t);
   executed.add(t);
  }
 }
}


Now comes the second one:


implement the same task scheduler with parallel task execution.

So I am thinking, maybe it needs a concurrency approach. So I go back to my old posts and tried to come up an approach. Since all dependencies should be executed first, I use a stack to schedule all tasks. The scheduler will first add all its dependencies to the stack. Then I use a releaseCount to track the available resources. If releaseCount == 0 or if the current Task is not on top of the stack, it should wait for its turn. Pop the task out and execute it, while executing, the task has acquired the resource, so releaseCount should decrement by one, after executing, the task should release the resource, so releaseCount increment by one.

However, I am not sure if my approach is correct, so I have an open question on Stackoverflow.


public class TaskSchedulerParallel {
 Set executed;
 Stack scheduler;
 int releaseCount;
 //number of parallel nodes
 public TaskSchedulerParallel(int N){
  executed = new HashSet();
  scheduler = new Stack();
  releaseCount = N;
 }
 public synchronized void schedule(Task t) throws InterruptedException {
  scheduler.push(t);
  for(Task dep : t.GetDependencies()){
   if(!executed.contains(dep) && !scheduler.contains(dep))
    schedule(dep);
  }
  if(releaseCount == 0 || (!scheduler.isEmpty() && scheduler.peek() != t))
   t.wait();
  releaseCount--;
  scheduler.pop();
  t.Run();
  executed.add(t);
  releaseCount++;
 }
 

}

Thursday, January 22, 2015

The amazing maze II: searching the maze

This post I will talk about how to solve a maze. If you are interested in how to build a maze. Take a look at my last post.

There are couple ways to solve a maze, the easiest ones are using DFS and BFS. Here, I implemented DFS using recursion, DFS using a stack, and BFS.


package mazeDFS;
import java.util.*;
public class SolveMaze {
 
 List path;
 private Maze m;
 
 public SolveMaze(Maze m) {
  path = new ArrayList ();
  this.m = m;
 }
 /**
  * Using DFS to solve the maze
  */
 public void solveByDFS() {
  boolean[] visited = new boolean[m.grid.length];
  //Stack stack = new Stack ();
  dfs(path, visited, 0);
 }
 //using recursion
 private void dfs(List path, boolean[] visited, int curr) {
  if (curr == m.grid.length - 1) {
   visited[curr] = true; 
   path.add(curr);
   return;
  }
  path.add(curr);
  visited[curr] = true;
  int cell = m.grid[curr];
  if ((cell & Maze.LEFT) == 0 && (curr - 1) >= 0 && !visited[curr - 1] && !visited[m.grid.length - 1] && !visited[m.grid.length - 1])
   dfs(path, visited, curr - 1);
  if ((cell & Maze.RIGHT) == 0 && (curr + 1) < m.grid.length && !visited[curr + 1] && !visited[m.grid.length - 1])
   dfs(path, visited, curr + 1);
  if ((cell & Maze.UP) == 0 && (curr - m.columns) >= 0 && !visited[curr - m.columns] && !visited[m.grid.length - 1])
   dfs(path,visited, curr - m.columns);
  if ((cell & Maze.DOWN) == 0 && (curr + m.columns) < m.grid.length && !visited[curr + m.columns] && !visited[m.grid.length - 1])
   dfs(path, visited, curr + m.columns);
  if (visited[m.grid.length - 1])
   return;
  path.remove(path.size() - 1);
 }
 //using a stack
 public void solveByDFS2() {
  Stack stack = new Stack ();
  boolean[] visited = new boolean[m.grid.length];
  int[] distTo = new int[m.grid.length];
  int[] predecessor = new int[m.grid.length];
  Arrays.fill(distTo, Integer.MAX_VALUE);
  stack.push(0);
  visited[0] = true;
  distTo[0] = 0;
  predecessor[0] = -1;
  while (!stack.isEmpty() && !visited[m.grid.length - 1]) {
   int curr = stack.pop();
   int cell = m.grid[curr];
   if (curr == m.grid.length - 1) {
    break;
   }
   if ((cell & Maze.LEFT) == 0 && (curr - 1) >= 0 && !visited[curr - 1]) {
    stack.push(curr - 1);
    visited[curr - 1] = true;
    distTo[curr - 1] = distTo[curr] + 1;
    predecessor[curr - 1] = curr;
   }
   if ((cell & Maze.RIGHT) == 0 && (curr + 1) < m.grid.length && !visited[curr + 1]) {
    stack.push(curr + 1);
    visited[curr + 1] = true;
    distTo[curr + 1] = distTo[curr] + 1;
    predecessor[curr + 1] = curr;
   }
   if ((cell & Maze.UP) == 0 && (curr - m.columns) >= 0 && !visited[curr - m.columns]) {
    stack.push(curr - m.columns);
    visited[curr - m.columns] = true;
    distTo[curr - m.columns] = distTo[curr] + 1;
    predecessor[curr - m.columns] = curr;
   }
   if ((cell & Maze.DOWN) == 0 && (curr + m.columns) < m.grid.length && !visited[curr + m.columns]) {
    stack.push(curr + m.columns);
    visited[curr + m.columns] = true;
    distTo[curr + m.columns] = distTo[curr] + 1;
    predecessor[curr + m.columns] = curr;
   }
  }
  int x;
  for (x = m.grid.length - 1; distTo[x] != 0; x = predecessor[x]) {
   path.add(x);
  }
  path.add(0);
  Collections.reverse(path);
  
 }
 
 public void solveByBFS() {
  Queue q = new LinkedList ();
  boolean[] visited = new boolean[m.grid.length];
  int[] distTo = new int[m.grid.length];
  int[] predecessor = new int[m.grid.length];
  Arrays.fill(distTo, Integer.MAX_VALUE);
  q.offer(0);
  visited[0] = true;
  distTo[0] = 0;
  predecessor[0] = -1;
  while (!q.isEmpty() && !visited[m.grid.length - 1]) {
   int curr = q.poll();
   int cell = m.grid[curr];
   if (curr == m.grid.length - 1) {
    break;
   }
   if ((cell & Maze.LEFT) == 0 && (curr - 1) >= 0 && !visited[curr - 1]) {
    q.offer(curr - 1);
    visited[curr - 1] = true;
    distTo[curr - 1] = distTo[curr] + 1;
    predecessor[curr - 1] = curr;
   }
   if ((cell & Maze.RIGHT) == 0 && (curr + 1) < m.grid.length && !visited[curr + 1]) {
    q.offer(curr + 1);
    visited[curr + 1] = true;
    distTo[curr + 1] = distTo[curr] + 1;
    predecessor[curr + 1] = curr;
   }
   if ((cell & Maze.UP) == 0 && (curr - m.columns) >= 0 && !visited[curr - m.columns]) {
    q.offer(curr - m.columns);
    visited[curr - m.columns] = true;
    distTo[curr - m.columns] = distTo[curr] + 1;
    predecessor[curr - m.columns] = curr;
   }
   if ((cell & Maze.DOWN) == 0 && (curr + m.columns) < m.grid.length && !visited[curr + m.columns]) {
    q.offer(curr + m.columns);
    visited[curr + m.columns] = true;
    distTo[curr + m.columns] = distTo[curr] + 1;
    predecessor[curr + m.columns] = curr;
   }
  }
  int x;
  for (x = m.grid.length - 1; distTo[x] != 0; x = predecessor[x]) {
   path.add(x);
  }
  path.add(0);
  Collections.reverse(path); 
 }
}


Performance

Average memory usage on a 50 by 50 maze

Average running time on a 50 by 50 maze

As we can see, DFS by recursion outperforms the other two methods in both time and memory. It is understandable that BFS is slower in this circumstance. If we search layer by layer, we probably need to traverse the whole maze to find the end point. Yet if we use DFS, any path leads to the end point can terminate the searching. 

Recursive solutions often result in smaller code, which means it's more likely that the code will fit into the CPU cache. A no-recursive solution that requires an explicitly managed stack can result in larger code, more cache misses, and slower performance than recursive solution. 

Wednesday, December 17, 2014

Word Ladder I & II

The first one:
Given two words (start and end), and a dictionary, find the length of shortest transformation sequence from start to end, such that:
  1. Only one letter can be changed at a time
  2. Each intermediate word must exist in the dictionary
For example,
Given:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]
As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
return its length 5.
Note:
  • Return 0 if there is no such transformation sequence.
  • All words have the same length.
  • All words contain only lowercase alphabetic characters.

A typical BFS problem. Start from "start",  we go through all possible replacements and see if we can find some transformations in the dict. If we can, we put the intermediate word in the queue. The queue always stores transformations of words in the current level. (e.g, "dot" and "lot" will both be placed in the queue after "hot" is polled). Whenever "end" is reached, we return the length + 1, where "+ 1" indicates the "end" string.

public class Solution {
    public int ladderLength(String start, String end, Set dict) {
        if (dict == null || dict.size() == 0)
            return 0;
        if (start.equals(end))
            return 1;
        int length = 1;
        Queue queue = new LinkedList ();
        queue.offer(start);
        dict.remove(start);
        while (!queue.isEmpty())
        {
            //for multiple transformations, we only want to increment length once, 
            // thus only increment length after current stage of transformation is done
            int size = queue.size();
            for (int j = 0; j < size; j++)
            {
                String current = queue.poll();
                for (char c = 'a'; c <= 'z'; c++)
                {
                    for (int i = 0; i < current.length(); i++)
                    {
                        if (current.charAt(i) == c)
                            continue;
                        String tmp = replace(current, i, c);
                        if (tmp.equals(end))
                                return length + 1;
                        if (dict.contains(tmp))
                        {
                            //it is possible that another replacement of the character will lead to a shorter length, 
                            //thus length cannot be incremented here
                            //e.g., "a", "b", "c", if length is incremented at b, we will have one extra count
                            queue.offer(tmp);
                            dict.remove(tmp);
                        }
                    }
                }
            }
            //if no transformation is found, queue is empty, and we will exit the loop and return 0
            // otherwise we assume one transformation is found
            length++;
        }
        return 0;
    }

    private String replace(String s, int pos, char character)
    {
        char[] current = s.toCharArray();
        current[pos] = character;
        return new String(current);
    }
}


The second one:
Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from start to end, such that:
  1. Only one letter can be changed at a time
  2. Each intermediate word must exist in the dictionary
For example,
Given:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]
Return
  [
    ["hit","hot","dot","dog","cog"],
    ["hit","hot","lot","log","cog"]
  ]
Note:
  • All words have the same length.
  • All words contain only lowercase alphabetic characters.
This problem is hard one because of the time and memory complexity.
Word Ladder I is a typical BFS problem, so for this one, intuitively we would think of using BFS too.
Not yet done...
Because we need to return every possible shortest path, we cannot simply remove the used words ("hot" is used in both paths). A possible approach can be as follows:

1. Do BFS first.
    Use a map<String, ArrayList<String>> to track all possible transformations to the key String. e.g., "cog", {"dog", "log"};
    Use another map<String, Integer> to track the level of the string. e.g., "hot", 1; "cog", 5;
2. The transformation is achieved by the same method as we used in the first problem.
3. Do DFS.
    From the "end" string, search the map and get the previous strings, until the start string is reached.
    Reverse the path (because we search from the end to the start) and return the path.

Note: Difference between Backtracking and DFS. 
Backtracking is a more general purpose algorithm. It can be used on any type of structure where portions of the domain can be eliminated.
DFS is a specific form of backtracking related to searching tree structures, and is limited to a tree structure.


public class Solution {
    public ArrayList> findLadders(String start, String end, Set dict) {
        ArrayList> ladders = new ArrayList> ();
        if (start.equals(end))
            return ladders;
        HashMap> prevWord = new HashMap>();
        HashMap level = new HashMap ();
        
        dict.add(start);
        dict.add(end);
        
        getTransformation(prevWord, level, start, dict);
        
        ArrayList path = new ArrayList();
        
        getPath(ladders, path, prevWord, level, end, start);
        
        return ladders;
        
    }
    //apply DFS
    private void getPath(ArrayList> ladders, ArrayList path, 
    HashMap> prevWord, HashMap level, String curr, String start)
    {
        path.add(curr);
        if (curr.equals(start))
        {
            Collections.reverse(path);
            ladders.add(new ArrayList(path));
            Collections.reverse(path);
        }
        else
        {
            for (String word : prevWord.get(curr))
            {
                if (level.containsKey(word) && level.get(curr) == level.get(word) + 1)
                    getPath(ladders, path, prevWord, level, word, start);
            }
        }
        // should be outside the loop since we add the word at the beginning of the method
        path.remove(path.size() - 1);
    }
    //applying BFS
    private void getTransformation(HashMap> prevWord, HashMap level,
    String start, Set dict)
    {
        Queue queue = new LinkedList();
        queue.offer(start);
        for (String s : dict)
            prevWord.put(s, new ArrayList());
        level.put(start, 0);
        while (!queue.isEmpty())
        {
            String curr = queue.poll();
            ArrayList list = transformWord(curr, dict);
            for (String word : list)
            {
                //prevWord tracks the word before 
             //the key is the word transformed from the value
             //curr is the word before the next
                prevWord.get(word).add(curr);
                //if level already contains the word, it means the word is at the upper level 
                //we do not want to change it to a lower level
                if (!level.containsKey(word))
                {
                    level.put(word, level.get(curr) + 1);
                    queue.offer(word);
                }
            }
        }
    }
    private ArrayList transformWord(String s, Set dict)
    {
        ArrayList transformations = new ArrayList();
        for (char c = 'a'; c <= 'z'; c++)
        {
            for (int i = 0; i < s.length(); i++)
            {
                if(s.charAt(i) == c)
                    continue;
                String tmp = s.substring(0,i) + c + s.substring(i + 1);
                if (dict.contains(tmp))
                    transformations.add(tmp);
            }
        }
        return transformations;
    }
    
}