Introduction
Often computers are used to perform consecutive jobs. Sometimes the order of doing these jobs does not matter. Other times there are dependencies between jobs that restrict the order that jobs can be performed. Additionally, jobs may require significant resources to store results, so it may be necessary to first schedule those jobs that will consume previous results before performing jobs that produce even more results. Furthermore, there may be significant costs to change from one type of job to another. The challenge then becomes finding the optimal execution order of jobs.
Challenge
Allow me to illustrate the problem with an example. Consider a list of 10 jobs, numbered 0 to 9. Each of these jobs can be dependent on zero or more other jobs. (Assume there are no circular dependencies.) For example:
Job : Dependent on
--- ------------
0 : 8
1 : 6
2 :
3 : 0 6 9
4 : 7 9
5 : 2
6 : 4 8
7 :
8 : 9
9 :
This can be illustrated graphically as follows:

One possible solution to this particular example is to perform the 10 jobs in the order: 9 8 7 4 6 0 2 1 3 5
While this solution satisfies the dependency requirements, it is not ideal. To see why, let us perform the jobs one at a time:
Job : Results stored in memory
--- ------------------------
9 : 9
8 : 8 9
7 : 7 8 9
4 : 4 8 9
6 : 6 8 9
0 : 0 6 9
2 : 0 2 6 9
1 : 0 1 2 6 9
3 : 1 2 3
5 : 1 3 5
After performing the first job (job 9), we only have the result of job 9 in memory. After performing job 8 we need to store the results of 8 and 9, and after job 7 we have the results of jobs 7, 8 and 9. After performing job 4 we find that the results of job 7 is no longer needed and we can discard that, leaving only the results of job 4, 8 and 9. Carrying on we find that the worst case memory usage is after job 1 when we have to store the results of 5 different jobs (0, 1, 2, 6 and 9).
As it turns out there is a better solution to this example, and that is to do the jobs in the order: 7 9 4 8 6 0 3 2 5 1
The memory usage is then:
Job : Results stored in memory
--- ------------------------
7 : 7
9 : 7 9
4 : 4 9
8 : 4 8 9
6 : 6 8 9
0 : 0 6 9
3 : 3 6
2 : 2 3 6
5 : 3 5 6
1 : 1 3 5
This time we only ever needed to store the results of 3 jobs in memory, instead of the 5 in the previous solution.
Finally, consider that each job is of a certain "type", and changing from one type to another is something that should be avoided. For example, let's say we have 3 types of jobs: A, B and C, and our 10 jobs in the example have the following types: Jobs 0-3 is type A, jobs 4-7 is type B, and jobs 8 and 9 is type C.
Job : Results stored in memory : Job Type Change
--- ------------------------ : ---------------
7 : 7 B
9 : 7 9 C
4 : 4 9 B
8 : 4 8 9 C
6 : 6 8 9 B
0 : 0 6 9 A
3 : 3 6 .
2 : 2 3 6 .
5 : 3 5 6 B
1 : 1 3 5 A
As you can see, we had to change the job type 8 times: B C B C B A B A
When minimizing job changes, while still keeping maximum memory usage to 3, we now find that the optimal solution is: 9 8 7 4 6 0 3 1 2 5
Job : Results stored in memory : Job Type Change
--- ------------------------ : ---------------
9 : 9 C
8 : 8 9 .
7 : 7 8 9 B
4 : 4 8 9 .
6 : 6 8 9 .
0 : 0 6 9 A
3 : 3 6 .
1 : 1 3 .
2 : 1 2 3 .
5 : 1 3 5 B
This solution require only 4 job type changes (C B A B
), while still only needing memory to store 3 results.
The solutions to this example that I presented here was found using a brute force search, ie going through every permutation of job order. From doing this I can tell you that this example has 1485 solutions that satisfy the dependency requirements. Of these 1485 solutions only 15 solutions require memory for no more than 3 results. Of these 15 solutions only 2 require only 4 job changes. The one is shown above (9 8 7 4 6 0 3 1 2 5
). The other is: 9 8 7 4 6 0 3 2 1 5
.
Unfortunately, brute force is not viable when the total number of jobs increase beyond 10, eg 100. The challenge is to come up with an algorithm that can find the optimal solution (or any one of them if there are more than one) using an algorithm that significantly outperforms brute force.
Example Input and Output
The input to the algorithm is a list of N jobs numbered 0 to N-1, each with a job type (A-Z) followed by a list of zero or more dependencies, eg:
0, A, 8
1, A, 6
2, A
3, A, 0 6 9
4, B, 7 9
5, B, 2
6, B, 4 8
7, B
8, C, 9
9, C
The output of the algorithm is the optimal execution order, eg:
9 8 7 4 6 0 3 1 2 5
Answers may be presented in any programming language or even pseudo code. I will translate them all to C++ and test them for correctness against the brute force algorithm using randomly generated inputs. The winner will be the algorithm that is fastest (and correct).