递归分解(数组求和)

本文介绍分治法基本思想,即把大问题划分为小问题,分而治之再合并解。以数组求和为例,分析递归拆分时线程数限制导致的问题,指出线程并非越多越好。还提到Java 1.7引入的Fork/Join Framework,用于实现分治算法,可分割大任务汇总结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分治法 (Divide and Conquer)

  • 基本思想:把一个规模大的问题划分为规模较小的子问题,然后分而治之,最后合并子问题的解得到原问题的解。
  • 步骤:
  1. 分割原问题
  2. 求解子问题
  3. 并子问题的解为原问题的解
  • 在分治法中,子问题一般是相互独立的,因此,经常通过递归调用算法来求解子问题。


    一道数组求和:

public class SumRecursiveMT {
    public static class RecursiveSumTask implements Callable<Long> {
        public static final int SEQUENTIAL_CUTOFF = 10;
        int lo;
        int hi;
        int[] arr; // arguments
        ExecutorService executorService;

        RecursiveSumTask( ExecutorService executorService, int[] a, int l, int h) {
            this.executorService = executorService;
            this.arr = a;
            this.lo = l;
            this.hi = h;
        }

        @Override
        public Long call() throws Exception { // override
            System.out.format("%s range [%d-%d] begin to compute %n",
                    Thread.currentThread().getName(), lo, hi);
            long result = 0;
            if (hi - lo <= SEQUENTIAL_CUTOFF) {
                for (int i = lo; i < hi; i++)
                    result += arr[i];

                System.out.format("%s range [%d-%d] begin to finished %n",
                        Thread.currentThread().getName(), lo, hi);
            }
            else {
                RecursiveSumTask left = new RecursiveSumTask(executorService, arr, lo, (hi + lo) / 2);
                RecursiveSumTask right = new RecursiveSumTask(executorService, arr, (hi + lo) / 2, hi);
                Future<Long> lr = executorService.submit(left);
                Future<Long> rr = executorService.submit(right);

                result = lr.get() + rr.get();
                System.out.format("%s range [%d-%d] finished to compute %n",
                        Thread.currentThread().getName(), lo, hi);
            }

            return result;
        }
    }


    public static long sum(int[] arr) throws Exception {
        int nofProcessors = Runtime.getRuntime().availableProcessors();
        ExecutorService executorService = Executors.newFixedThreadPool(4);
//        ExecutorService executorService = Executors.newCachedThreadPool();

        RecursiveSumTask task = new RecursiveSumTask(executorService, arr, 0, arr.length);
        long result =  executorService.submit(task).get();
        return result;
    }

    public static void main(String[] args) throws Exception {
        int[] arr = Utils.buildRandomIntArray(50);
        System.out.printf("The array length is: %d\n", arr.length);

        long result = sum(arr);

        System.out.printf("The result is: %d\n", result);

    }
}

 


  • 效果图如下:

  • 但经过运行发现,程序到这里就进行不下去了

  • 简单分析之后发现4个线程一直处于waiting状态,那他究竟在等待什么?因为线程池只有4个线程,而递归拆分50大小的数组需要1+2+4+8,一共15个线程,没有线程去完成接下来的任务。

  •  就下图的情况,没有线程去执行接下来的程序,所以线程都结束不了,都处于等待状态。


  • 当你不在限制它的线程数的时候
ExecutorService executorService = Executors.newCachedThreadPool();
The array length is: 50
pool-1-thread-1 range [0-50] begin to compute 
pool-1-thread-2 range [0-25] begin to compute 
pool-1-thread-3 range [25-50] begin to compute 
pool-1-thread-4 range [0-12] begin to compute 
pool-1-thread-7 range [37-50] begin to compute 
pool-1-thread-5 range [12-25] begin to compute 
pool-1-thread-6 range [25-37] begin to compute 
pool-1-thread-8 range [37-43] begin to compute 
pool-1-thread-8 range [37-43] begin to finished 
pool-1-thread-10 range [0-6] begin to compute 
pool-1-thread-10 range [0-6] begin to finished 
pool-1-thread-9 range [43-50] begin to compute 
pool-1-thread-9 range [43-50] begin to finished 
pool-1-thread-7 range [37-50] finished to compute 
pool-1-thread-11 range [12-18] begin to compute 
pool-1-thread-11 range [12-18] begin to finished 
pool-1-thread-12 range [6-12] begin to compute 
pool-1-thread-12 range [6-12] begin to finished 
pool-1-thread-13 range [18-25] begin to compute 
pool-1-thread-13 range [18-25] begin to finished 
pool-1-thread-4 range [0-12] finished to compute 
pool-1-thread-14 range [25-31] begin to compute 
pool-1-thread-14 range [25-31] begin to finished 
pool-1-thread-5 range [12-25] finished to compute 
pool-1-thread-2 range [0-25] finished to compute 
pool-1-thread-15 range [31-37] begin to compute 
pool-1-thread-15 range [31-37] begin to finished 
pool-1-thread-6 range [25-37] finished to compute 
pool-1-thread-3 range [25-50] finished to compute 
pool-1-thread-1 range [0-50] finished to compute 
The result is: 23980
  • 按照预期执行成功,但随之出现了一个问题,如果我的数组很大很大,岂不是需要很多线程来参与。
  • 10W大小的数组,参与的线程数已经上W了,再大一点不敢想象,这样搞还不如一个线程跑。
  • 线程不是越多越好,拆分的话也不是越细越好。
pool-1-thread-15992 range [42802-42808] begin to finished 
pool-1-thread-12490 range [42796-42802] begin to compute 
pool-1-thread-12490 range [42796-42802] begin to finished 
pool-1-thread-12491 range [41375-41381] begin to compute 
pool-1-thread-12491 range [41375-41381] begin to finished 
pool-1-thread-16079 range [41369-41375] begin to compute 
pool-1-thread-16079 range [41369-41375] begin to finished 
pool-1-thread-11157 range [65807-65820] finished to compute 
pool-1-thread-10080 range [71239-71251] finished to compute 
pool-1-thread-10082 range [71190-71202] finished to compute 
pool-1-thread-8334 range [56469-56481] finished to compute 
pool-1-thread-12919 range [8134-8140] begin to compute 
pool-1-thread-3544 range [56469-56493] finished to compute 
pool-1-thread-12230 range [8128-8134] begin to compute 
pool-1-thread-12230 range [8128-8134] begin to finished 

 


  • 针对上述问题,在Java 1.7 引入了一种新的并发框架—— Fork/Join Framework

  • 主要用于实现“分而治之”的算法,特别是分治之后递归调用的函数

  • 提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架

  • 与ThreadPool共存,并不是要替换ThreadPool

  • ForkJoin实现数组求和例子
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值