更新:为了帮助澄清我的要求,我已经发布了一些可以实现这个想法的java代码。
不久之前,我问question如何获取算法来分解一组数字,我的想法是给它一个数字列表(1,2,3,4,5)
和一个总数({{1}并且它将计算出每个数字的所有倍数,这些数字将加起来总计(10
或“'1*10'
”或“1*1,1*2,1*3,1*4
”等等。)。这是我做过的第一次编程练习,所以它花了我一段时间才开始工作,但现在我想试着看看是否可以扩展它。原问题中的人说它是可扩展的,但我对如何做到这一点感到困惑。递归部分是我在缩放组合所有结果的部分时遇到的区域(它所指的表不可扩展但是应用缓存我能够使它快速)
我有以下算法(伪代码):
2*5
我真的不知道如何对RecursivelyListAllThatWork函数进行线程/多处理。我知道如果我发送一个较小的K(它是列表中项目总数的int),它将处理该子集但我不知道如何组合整个子集的结果。例如,如果列表是//generates table
for i = 1 to k
for z = 0 to sum:
for c = 1 to z / x_i:
if T[z - c * x_i][i - 1] is true:
set T[z][i] to true
//uses table to bring all the parts together
function RecursivelyListAllThatWork(k, sum) // Using last k variables, make sum
/* Base case: If we've assigned all the variables correctly, list this
* solution.
*/
if k == 0:
print what we have so far
return
/* Recursive step: Try all coefficients, but only if they work. */
for c = 0 to sum / x_k:
if T[sum - c * x_k][k - 1] is true:
mark the coefficient of x_k to be c
call RecursivelyListAllThatWork(k - 1, sum - c * x_k)
unmark the coefficient of x_k
并且我发送它K = 3那么只有1,2,3得到处理这是好的但是如果我需要包含1和10的结果呢?我试图修改表(变量T),所以只有我想要的子集在那里,但仍然不起作用,因为,像上面的解决方案,它做了一个子集但不能处理需要更宽范围的答案。
我不需要任何代码,只要有人可以解释如何在概念上打破这个递归步骤,以便可以使用其他核心/机器。
更新:我似乎还无法弄清楚如何将RecursivelyListAllThatWork转变为runnable(我在技术上知道如何做到这一点,但我不明白如何更改RecursivelyListAllThatWork算法,以便它可以并行运行。其他部分只是为了使示例工作,我只需要在RecursivelyListAllThatWork方法上实现runnable)。这是java代码:
[1,2,3,4,5,6,7,8,9,10]
答案 0 :(得分:8)
多线程的一般答案是通过堆栈(LIFO或FIFO)去递归递归实现。在实现这样的算法时,线程数是算法的固定参数(例如,核的数量)。
为了实现它,当测试条件结束递归时,语言调用堆栈被存储最后一个上下文的堆栈替换为检查点。在您的情况下,k=0
或coeff
值与目标总和相匹配。
在去再生之后,第一个实现是运行多个线程来使用堆栈但是堆栈访问成为争用点,因为它可能需要同步。
更好的可扩展解决方案是为每个线程专用堆栈,但需要在堆栈中初始生成上下文。
我提出了一种混合方法,第一个线程以递归方式处理有限数量的k
作为最大递归深度:对于示例中的小数据集为2,但我推荐3如果更大。然后,第一部分将生成的中间上下文委托给一个线程池,该线程池将使用非递归实现处理剩余的k
。此代码不是基于您使用的复杂算法,而是基于相当“基本”的实现:
import java.util.Arrays;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MixedParallel
{
// pre-requisite: sorted values !!
private static final int[] data = new int[] { 5, 10, 20, 25, 40, 50 };
// Context to store intermediate computation or a solution
static class Context {
int k;
int sum;
int[] coeff;
Context(int k, int sum, int[] coeff) {
this.k = k;
this.sum = sum;
this.coeff = coeff;
}
}
// Thread pool for parallel execution
private static ExecutorService executor;
// Queue to collect solutions
private static Queue<Context> solutions;
static {
final int numberOfThreads = 2;
executor =
new ThreadPoolExecutor(numberOfThreads, numberOfThreads, 1000, TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>());
// concurrent because of multi-threaded insertions
solutions = new ConcurrentLinkedQueue<Context>();
}
public static void main(String[] args)
{
int target_sum = 100;
// result vector, init to 0
int[] coeff = new int[data.length];
Arrays.fill(coeff, 0);
mixedPartialSum(data.length - 1, target_sum, coeff);
executor.shutdown();
// System.out.println("Over. Dumping results");
while(!solutions.isEmpty()) {
Context s = solutions.poll();
printResult(s.coeff);
}
}
private static void printResult(int[] coeff) {
StringBuffer sb = new StringBuffer();
for (int i = coeff.length - 1; i >= 0; i--) {
if (coeff[i] > 0) {
sb.append(data[i]).append(" * ").append(coeff[i]).append(" ");
}
}
System.out.println(sb.append("from ").append(Thread.currentThread()));
}
private static void mixedPartialSum(int k, int sum, int[] coeff) {
int x_k = data[k];
for (int c = sum / x_k; c >= 0; c--) {
coeff[k] = c;
int[] newcoeff = Arrays.copyOf(coeff, coeff.length);
if (c * x_k == sum) {
//printResult(newcoeff);
solutions.add(new Context(0, 0, newcoeff));
continue;
} else if (k > 0) {
if (data.length - k < 2) {
mixedPartialSum(k - 1, sum - c * x_k, newcoeff);
// for loop on "c" goes on with previous coeff content
} else {
// no longer recursive. delegate to thread pool
executor.submit(new ComputePartialSum(new Context(k - 1, sum - c * x_k, newcoeff)));
}
}
}
}
static class ComputePartialSum implements Callable<Void> {
// queue with contexts to process
private Queue<Context> contexts;
ComputePartialSum(Context request) {
contexts = new ArrayDeque<Context>();
contexts.add(request);
}
public Void call() {
while(!contexts.isEmpty()) {
Context current = contexts.poll();
int x_k = data[current.k];
for (int c = current.sum / x_k; c >= 0; c--) {
current.coeff[current.k] = c;
int[] newcoeff = Arrays.copyOf(current.coeff, current.coeff.length);
if (c * x_k == current.sum) {
//printResult(newcoeff);
solutions.add(new Context(0, 0, newcoeff));
continue;
} else if (current.k > 0) {
contexts.add(new Context(current.k - 1, current.sum - c * x_k, newcoeff));
}
}
}
return null;
}
}
}
您可以检查哪个线程找到了输出结果并检查所有被调用的内容:递归模式下的主线程和上下文堆栈模式下池中的两个线程。
现在,当data.length
为高时,此实现可以扩展:
numberOfThreads
和maxRecursionDepth
所以答案是肯定的,你的算法可以并行化。这是一个基于代码的完全递归实现:
import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class OriginalParallel
{
static final int numberOfThreads = 2;
static final int maxRecursionDepth = 3;
public static void main(String[] args)
{
int target_sum = 100;
int[] data = new int[] { 50, 40, 25, 20, 10, 5 };
List T = tableGeneator(target_sum, data);
int[] coeff = new int[data.length];
Arrays.fill(coeff, 0);
RecursivelyListAllThatWork(data.length, target_sum, T, coeff, data);
executor.shutdown();
}
private static void printResult(int[] coeff, int[] data) {
StringBuffer sb = new StringBuffer();
for (int i = coeff.length - 1; i >= 0; i--) {
if (coeff[i] > 0) {
sb.append(data[i]).append(" * ").append(coeff[i]).append(" ");
}
}
System.out.println(sb.append("from ").append(Thread.currentThread()));
}
// Thread pool for parallel execution
private static ExecutorService executor;
static {
executor =
new ThreadPoolExecutor(numberOfThreads, numberOfThreads, 1000, TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>());
}
private static void RecursivelyListAllThatWork(int k, int sum, List T, int[] coeff, int[] data) {
if (k == 0) {
printResult(coeff, data);
return;
}
Integer x_k = data[k-1];
// Recursive step: Try all coefficients, but only if they work.
for (int c = 0; c <= sum/x_k; c++) { //the c variable caps the percent
if (T.contains(new Point((sum - c * x_k), (k-1)))) {
// mark the coefficient of x_k to be c
coeff[k-1] = c;
if (data.length - k != maxRecursionDepth) {
RecursivelyListAllThatWork((k - 1), (sum - c * x_k), T, coeff, data);
} else {
// delegate to thread pool when reaching depth 3
int[] newcoeff = Arrays.copyOf(coeff, coeff.length);
executor.submit(new RecursiveThread(k - 1, sum - c * x_k, T, newcoeff, data));
}
// unmark the coefficient of x_k
coeff[k-1] = 0;
}
}
}
static class RecursiveThread implements Callable<Void> {
int k;
int sum;
int[] coeff;
int[] data;
List T;
RecursiveThread(int k, int sum, List T, int[] coeff, int[] data) {
this.k = k;
this.sum = sum;
this.T = T;
this.coeff = coeff;
this.data = data;
System.out.println("New job for k=" + k);
}
public Void call() {
RecursivelyListAllThatWork(k, sum, T, coeff, data);
return null;
}
}
public static List tableGeneator(int target_sum, int[] data) {
List T = new ArrayList();
T.add(new Point(0, 0));
float max_percent = 1;
int R = (int) (target_sum * max_percent * data.length);
for (int i = 0; i < data.length; i++) {
for (int s = -R; s < R + 1; s++) {
int max_value = (int) Math.abs((target_sum * max_percent) / data[i]);
for (int c = 0; c < max_value + 1; c++) {
if (T.contains(new Point(s - c * data[i], i))) {
Point p = new Point(s, i + 1);
if (!T.contains(p)) {
T.add(p);
}
}
}
}
}
return T;
}
}
答案 1 :(得分:1)
1)而不是
if k == 0:
print what we have so far
return
你可以查看有多少系数是非零的;如果该计数大于某个阈值(在您的示例中为3),则只是不打印它。 (提示:这与
密切相关 mark the coefficient of x_k to be c
线。)
2)递归函数本质上通常是指数函数,这意味着当你扩展得更高时,运行时会急剧增大。
考虑到这一点,您可以应用多线程来计算表和递归函数。
在考虑表格时,请考虑循环的哪些部分相互影响,并且必须按顺序进行;相反,当然,发现哪些部分不会相互影响,并且可以并行运行。
至于递归函数,你最好的选择可能是将多线程应用于分支部分。
答案 2 :(得分:1)
制作这个多线程的关键只是为了确保你没有不必要的全局数据结构,比如系数上的“标记”。
假设你的表中有K个数n [0] ... n [K-1],你要达到的总和是S.我假设数组n []从最小到最大排序号。
这里有一个简单的枚举算法。 i是数字列表的索引,s是已经构建的当前总和,cs是数字0的系数列表.i - 1:
function enumerate(i, s, cs):
if (s == S):
output_solution(cs)
else if (i == K):
return // dead end
else if ((S - s) < n[i]):
return // no solution can be found
else:
for c in 0 .. floor((S - s) / n[i]): // note: floor(...) > 0
enumerate(i + 1, s + c * n[i], append(cs, c))
运行流程:
enumerate(0, 0, make_empty_list())
现在这里没有全局数据结构了,除了表n [](常量数据),'enumerate'也没有返回任何内容,因此您可以随意更改递归调用以在其自己的线程中运行。例如。你可以将一个新线程生成一个递归的enumerate()调用,除非你已经运行了太多的线程,在这种情况下你等待。