【问题标题】:Factorial using Multithreading使用多线程的阶乘
【发布时间】:2016-04-02 00:00:21
【问题描述】:

我在使用多线程解决阶乘问题时遇到了一个噩梦,我有一个简单的想法,比如为什么我们不使用快速排序技术来解决这个问题,我说的是分而治之......

例如我的代码格式的解释...

假设如果我要找到 8 的阶乘,让我们将一个分区限制为只有 2 个部分

thread1 求解阶乘 n 到 n/2

8*fact(7)
8*7*fact(6)
8*7*6*fact(5)
8*7*6*5*fact(4)

thread2计算fact(4)

现在我不知道如何解决的问题是如何从 thread1 中删除 fact(4) 以便我完成我的答案

 result=thread1output*thread2output

请提供cmets代码希望得到您的积极响应..感谢您一直坚持到最后

【问题讨论】:

  • 我认为这不会像您期望的那样使用多个线程,而没有多个线程它是memoization
  • @ElliottFrisch 能否请您解释一下解决这个问题的最快方法,而不是我们使用递归的传统方法...谢谢您的评论
  • 没有人愿意为你做功课。
  • 抱歉,“请为我写一些代码”的问题不起作用。不管任务是否是家庭作业。但是,我们将回答有关 >>您的
  • 通过编写代码或尝试编写代码比阅读别人的示例可以学到更多/更好的知识。

标签: java multithreading


【解决方案1】:

如果你想用多个线程来攻击阶乘,你需要将系列划分为工作单元。对于 2 个线程,一个简单的方法是将系列分成 2 个。

例如8!

  • 线程 1 8x7x6x5
  • 线程 2 4x3x2x1

一种方法是在外部方法中分配工作,将范围传递给每个线程,然后将结果相乘。

实际上,您可以使用流 API 做到这一点:

long factorial = LongStream.rangeClosed(1, n).parallel().reduce((a, b) -> a * b).get();

这可以并行处理计算。

对于大于20的n,结果将超过long所能容纳的最大值;使用BigInteger 代替算术:

BigInteger factorial = IntStream.rangeClosed(1, n)
.parallel()
.mapToObj(String::valueOf)
.map(BigInteger::new)
.reduce((a, b) -> a.multiply(b))
.get();

【讨论】:

  • 是的......但这并不能帮助您了解幕后发生的事情。或者如何直接使用线程来完成它,用于学习目的。
【解决方案2】:

当一个计算依赖于先前计算的结果时,一个普遍适用的优化是memoization。而不是简单地递归,检查值是否已经被计算;如果是,则返回先前计算的值。类似的,

private static Map<Integer, BigInteger> memo = new HashMap<>();
static {
    memo.put(1, BigInteger.ONE);
}

public static BigInteger fact(int i) {
    if (memo.containsKey(i)) {
        return memo.get(i);
    }
    BigInteger result = BigInteger.valueOf(i).multiply(fact(i - 1));
    memo.put(i, result);
    return result;
}

【讨论】:

  • @ElliotFrisch 感谢您的代码,但在递归中,我们使用相同的东西,因为我们没有得到相同的值,因此无需稍后检查,因为我们继续 4*fact(3)....可以你请提供带有线程的代码这就是我在这里发帖的原因......谢谢你的努力。
  • @SmashCode - 你错了。记忆与递归结合使用。事实上,如果你仔细看一下 Elliott 的代码,你会发现它>>是
【解决方案3】:

Elliott 在他的评论中是正确的,即多线程不是优化阶乘计算的好方法。当然,除非您计算出非常大的阶乘,否则它不会有效(例如与记忆化相比)。

在简单的递归分治策略中,伪代码为

factorial(N) = 
    return factorialHelper(set_of(1, N))

factorialHelper(set) = 
    if set.size == 1 
       return set.first
    else
       let set1, set2 = split_into_subsets(set, 2)
       return factorialHelper(set1) * factorialHelper(set2)

现在我们可以像这样天真地使用线程:

factorialHelper(set) = 
    if set.size == 1 
       return set.first
    else
       let set1, set2 = split_into_subsets(set, 2)
       return fork(lambda factorialHelper(set1)).join() *
              fork(lambda factorialHelper(set2)).join()

(解释:fork(lambda factorialHelper(set1)).join() 应该意味着我们是: - 创建一个新线程来计算factorialHelper(set1), - 启动线程, - 等待它提供结果。)

这就是问题所在。每个线程都在做一堆家务(例如拆分集合),然后是一个乘法。如果你看大图,那意味着 N!大约需要 N 个线程。

我们可以做得更好。而不是等待两个子线程完成。 “当前”线程可以完成其中之一的工作;例如

factorialHelper(set) = 
    if set.size == 1 
       return set.first
    else
       let set1, set2 = split_into_subsets(set, 2)
       let child = fork(lanbda factorialHelper(set1))
       return factorialHelper(set2) * child.join()

但这仍然需要大约 N / 2 个线程。还有更大的问题:

  • 创建线程非常昂贵。
  • 线程切换/等待另一个线程完成的成本相对较高。
  • 在任何给定时间,您的计算机将只运行与其物理(或超线程)内核一样多的线程。

因此,如果您要在 Java 中按字面意思编写上述代码,它会像狗一样运行。线程太多。每个线程的线程创建/切换开销过多而有用工作量太少。

如果您使用Java Fork / Join Pool,您可以做得更好,但您还需要对split_into_subsets(set, 2) 步骤做一些事情。这会做很多多余的工作复制集合元素。

经典方法是使用单个共享数组(1 到 N)并传递“低”和“高”索引。但在这种情况下,我们甚至不需要数组,因为我们知道array[i]i 相同。 (忽略一个。我还在谈论伪代码!)

但接下来我们会遇到最后一个问题。平衡工作量。将两个大数(例如BigInteger)相乘的工作不是一个常数。这实际上取决于数字的大小。 (我认为对于M * N 它是 O(log2M * log2N)。如果你使用天真的分治法,乘法“左端”比“右端”快得多。解决这个问题很棘手,我怀疑分而治之是处理它的错误方法。

我实际上会考虑一种混合方法:

  • 创建T工作线程,其中T对应于可用内核的数量。
  • 以循环方式将数字 1N 分配到 T 子集中。
  • 让每个线程(串行)计算其子集中所有数字的乘积。
  • 使用尽可能多的线程对子集产品进行最终乘法。

在计算结束时,无论你怎么做,都会有一个使用一个线程完成的最终乘法。而之前的两次计算最多可以由2个线程完成。所以最后,一些线程“饥饿”将是不可避免的。

最后是乘法本身。显然,如果我们计算阶乘 (N) 以使 N 足够大以使多个线程值得,那么结果将大于 Long.MAX_VALUE。使用BigInteger 将是一个显而易见的选择。但是,BigInteger.multiply(BigInteger) 上有一点问号。研究高性能 N 位乘法算法和可以并行化的乘法算法可能是值得的。从这里开始:

郑重声明,BigInteger.multiply(BigInteger) 对于两个 n 位数字的复杂度如下:

  • Java 6 使用“学童”算法;即复杂度是 O(n*n) 对于 n 位

  • Java 8 使用“小学生”算法、Karatsuba 算法 (O(n*1.585)) 或 3 路 Toom–Cook 乘法 (O(n*1.465)),具体取决于 n 的大小。所以最终,复杂度是 O(n*1.465)。

【讨论】:

    【解决方案4】:

    这段代码怎么样:- 1.获取输入数字并创建对(开始,结束)。 2. 创建一个将给定数字相乘并返回结果的任务; 3. 提交任务。 4. 使用future获取结果。

    公共类 TestFactorial {

    public static void main(String[] args) throws InterruptedException,ExecutionException {
    
    
        Scanner scanner = new Scanner(new InputStreamReader(System.in));
        System.out.println(" Please enter the number for which you want to calculate Factorial");
        String input = scanner.nextLine();
        int number =0;
        int answer =1;
        scanner.close();
        if(input!=null)
                number = Integer.valueOf(input);
        if(number==0 || number ==1){
                answer = 1;
        }
        calcuateFactorial(number, answer);
    
    }
    
    private static void calcuateFactorial(int number, int answer) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        List<Future<Integer>> totalTaskResults = new ArrayList<Future<Integer>>();
        for(int start =1;start<=number ;start++){
            int end = start+1;
            Future<Integer> taskResult;
            if(end>start)
                taskResult = executor.submit(new Multiply(start, 1));
            else 
                 taskResult=    executor.submit(new Multiply (start, end));
            totalTaskResults.add(taskResult);
    
        }
    
        for (Future<Integer> future :totalTaskResults){
            answer = answer* future.get();
        }
        executor.shutdown();
        System.out.println("Answer "+ answer);
    }
    

    }

    这是可调用的类(任务):-

    public class Multiply implements Callable<Integer>{
    private int start ;
    private int end;
    
    
    public Multiply (int start, int end){
        this.start= start;
        this.end = end;
    }
    
    @Override
    public Multiply  call() throws Exception {
        return start*end;
    }
    

    }

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-11-26
      • 1970-01-01
      • 1970-01-01
      • 2016-01-20
      • 2019-10-22
      • 1970-01-01
      • 1970-01-01
      • 2014-12-16
      相关资源
      最近更新 更多