Parallelism is the ability to divide a task into smaller sub-tasks, and execute those sub-tasks simultaneously on different processors or cores to improve overall performance. Dynamic programming algorithms often exhibit task parallelism, which means that different stages of the algorithm can be executed independently and concurrently.
In order to parallelize a dynamic programming algorithm, we must identify sub-problems that can be executed in parallel, and ensure that the execution of one sub-problem does not depend on the result of another sub-problem.
One example of a problem that can be solved using parallel dynamic programming is the Longest Common Subsequence (LCS) problem. Given two strings, we want to find the longest subsequence that is present in both strings. A subsequence is a sequence that can be derived from another sequence by deleting some or no elements without changing the order of the remaining elements.
The LCS problem can be solved using dynamic programming by building a matrix where each cell represents the length of the LCS between two prefixes of the strings. The final result is the value in the bottom-right cell of the matrix.
To parallelize this algorithm, we can divide the matrix into smaller sub-matrices and assign each sub-matrix to a separate processor. Each processor can independently compute the values in its sub-matrix using the dynamic programming formula, without needing to communicate with other processors. Once all processors have finished computing their sub-matrices, we can combine the results to obtain the final answer.
Here’s an example Java code for the parallel LCS algorithm:
public static int parallelLCS(String s1, String s2, int numProcessors) {
int n = s1.length();
int m = s2.length();
int[][] matrix = new int[n+1][m+1];
int blockSize = n / numProcessors;
List<ParallelLCSWorker> workers = new ArrayList<>();
for (int i = 0; i < numProcessors; i++) {
int startRow = i * blockSize;
int endRow = i == numProcessors-1 ? n : startRow + blockSize - 1;
ParallelLCSWorker worker = new ParallelLCSWorker(s1, s2, matrix, startRow, endRow);
workers.add(worker);
worker.start();
}
for (ParallelLCSWorker worker : workers) {
try {
worker.join();
}
catch (InterruptedException ex) {}
}
return matrix[n][m];
}
private static class ParallelLCSWorker extends Thread {
private String s1;
private String s2;
private int[][] matrix;
private int startRow;
private int endRow;
public ParallelLCSWorker(String s1, String s2, int[][] matrix, int startRow, int endRow) {
this.s1 = s1;
this.s2 = s2;
this.matrix = matrix;
this.startRow = startRow;
this.endRow = endRow;
}
public void run() {
int m = s2.length();
for (int i = startRow; i <= endRow; i++) {
for (int j = 1; j <= m; j++) {
if (s1.charAt(i-1) == s2.charAt(j-1)) {
matrix[i][j] = matrix[i-1][j-1] + 1;
}
else {
matrix[i][j] = Math.max(matrix[i-1][j], matrix[i][j-1]);
}
}
}
}
}
In this code, the ‘parallelLCS‘ function divides the input strings into ‘numProcessors‘ blocks, and creates a ‘ParallelLCSWorker‘ instance for each block. Each worker is assigned a sub-matrix of the LCS matrix, and computes the values in that sub-matrix using the ‘run‘ method. Once all workers have finished executing, the ‘parallelLCS‘ function combines the results to obtain the final LCS value.
Note that this code assumes that the length of the input strings is divisible by the number of processors. A more general solution would be to pad the input strings with dummy characters as needed to ensure divisibility.