Java用线程排序单词数组

时间:2014-05-11 22:25:50

标签: java arrays multithreading sorting

我有一个名称的txt文件,我需要能够按字母顺序排序。然后我的程序获取数组,分成在终端中作为参数传递的线程数量,并为每个线程提供一个数组进行排序,然后将所有线程存储在一个数组中。现在,我需要一些帮助的是: 我现在想要一旦它们完成就接受线程(即如果两个在其他之前完成,它们开始合并然后等待更多)。把它想象成编织。我知道如何为合并编写排序代码,但我希望你能帮助我的是:我如何管理线程?我知道wait()和notify()会做什么,但我似乎无法将我的ead包装成我需要做什么才能将它们合并到一个数组中。我应该:

  1. 在合并数组的线程类中创建一个方法吗?
  2. 为每个完成的其他线程创建一个新线程,将两个排序的word-arrays作为参数传递,然后让该线程进行排序?
  3. 我还没有想到的其他事情。
  4. 我希望这个问题足够清楚,而且质量应该足够好。

3 个答案:

答案 0 :(得分:2)

我认为您应该使用Merge Sort算法,并将其实现基于ForkJoinPool(当然,如果您正在使用Java 7)。

此算法非常适合,因为作业可以拆分为独立任务,可以由不同的线程处理。现在,ForkJoinPool为您提供了易于使用的池,您可以在其中提交排序任务。

实施应该这样做:

  • 每个任务都会对给定的一个数组/列表进行排序;
  • 如果数组很小( small 的确切含义可以通过常量配置) - 它使用标准.sort()方法进行排序,否则它分成两半,这两半是提交到池进行排序;
  • 然后任务等待两个子任务完成,并将两个已排序的数组/列表合并为一个,并返回它;

以下是算法的示例实现。请注意,这个距离最佳,因为它消耗了大量的额外内存。我实现了它,以实现方法。使用-Xmx1024m运行它。

public class ForkJoinSort {

    private static final int LIST_SIZE = 10000;

    private static final int SORT_THRESHOLD = 10; //the minimal length of the list to use standard java sort rather than mergesort

    private static ForkJoinPool forkJoinPool = new ForkJoinPool();

    public static class MergeSortTask extends RecursiveTask<List<Integer>> {

        private final List<Integer> victim;

        public MergeSortTask(List<Integer> victim) {
            this.victim = victim;
        }

        @Override
        protected List<Integer> compute() {
            if (victim.size() < SORT_THRESHOLD) {
                Collections.sort(victim);
                return victim;
            }

            //sorting left and right parts of the list separately in separate threads
            MergeSortTask leftTask = new MergeSortTask(victim.subList(0, victim.size() / 2));
            MergeSortTask rightTask = new MergeSortTask(victim.subList(victim.size() / 2, victim.size()));
            forkJoinPool.submit(leftTask);
            forkJoinPool.submit(rightTask);

            //do merge
            return merge(leftTask.join(), rightTask.join());
        }

        public List<Integer> merge(List<Integer> left, List<Integer> right) {
            List<Integer> result = new ArrayList<Integer>(left.size() + right.size());

            Iterator<Integer> leftIterator = left.iterator();
            Iterator<Integer> rightIterator = right.iterator();

            Integer fromLeft = null;
            Integer fromRight = null;

            while (leftIterator.hasNext() || rightIterator.hasNext()) {
                //if current value taken from the iterator is null - take new one if possible, otherwise do nothing
                fromLeft = fromLeft == null ? leftIterator.hasNext() ? leftIterator.next() : null : fromLeft;
                fromRight = fromRight == null ? rightIterator.hasNext() ? rightIterator.next() : null : fromRight;

                if (fromLeft != null && (fromRight == null || fromLeft <= fromRight)) {
                    result.add(fromLeft);
                    fromLeft = null; //this is done to indicate that value from left iterator already passed to result list
                } else if (fromRight != null && (fromLeft == null || fromRight <= fromLeft)) {
                    result.add(fromRight);
                    fromRight = null;
                }
            }

            return result;
        }
    }

    public static void main(String[] args) throws Exception {
        SecureRandom random = new SecureRandom();

        //generate array of random numbers
        List<Integer> victim = new ArrayList<Integer>(LIST_SIZE);
        for (int i = 0; i < LIST_SIZE; ++i) {
            victim.add(random.nextInt());
        }

        //do some benchmarking as long as we're here
        long timeMark = System.currentTimeMillis();
        MergeSortTask task = new MergeSortTask(victim);
        forkJoinPool.submit(task);
        List<Integer> probablySorted = task.get();
        timeMark = System.currentTimeMillis() - timeMark;

        //asserting that array is sorted
        for (int i = 0; i < probablySorted.size() - 1; ++i) {
            if (probablySorted.get(i) > probablySorted.get(i + 1)) {
                throw new IllegalStateException("Sorting failed :(");
            }
        }

        System.out.println("Sorting " + LIST_SIZE + " random numbers using merge sort algorithm in " + Runtime.getRuntime().availableProcessors() + " threads took " + timeMark + " ms.");
    }
}

我试图使代码易于阅读。如果我在某个地方失败了,请不要犹豫。

答案 1 :(得分:1)

正如@Alexey正确地指出进行并行排序的最简单方法肯定是使用fork / join框架并合并排序。这很容易做,看起来像(伪代码):

def mergesort(a, i0, i1):
    if i0 == i1:
        return
    im = i0 + (i1 - i0) / 2
    fork mergesort(a, i0, im)
    fork mergesort(a, im, i1)
    join
    merge(a, i0, im, i1) # serial merge

如果我们分析这个,我们看到我们有(很容易通过主定理展示):

Work: T_1(n) = 2T_1(n / 2) + O(n) = O(n lg n)
Span: T_inf(n) = 1 T_inf(n / 2) + O(n) = O(n)

其中work表示完成的工作总量,span表示如果我们有无限多个线程可用的时间(基本上是树的深度)需要多长时间。

算法所具有的并行性基本上是Work / Span,在这种情况下给我们O(lg n) - 这实际上是无关紧要的,尽管如果我们使用一个好的串行排序算法来获得足够小的叶子大小,这仍然可以很好地工作。

我们可以通过并行化合并做得更好。这个可以在没有辅助阵列的情况下完成,但我会将其作为练习给读者留下(意思是:不容易,我不得不查看如何实际操作)。

并行合并:假设我们在[i0,i1]和[j0,j1]中有一个带有两个排序数组的辅助数组aux,我们希望将合并后的子数组放入k0,k1之间的数组a中。我们再次递归地执行此操作:

  1. 计算im = i0 +(i1 - i0)/ 2 - aux [im]是左半部分的中位数
  2. 找到jm,以便在aux [jm]
  3. 之前直接插入aux [im]
  4. 将aux [im]插入a中的正确位置,我们知道是否有im和jm。
  5. 合并两个子阵列。
  6. 困惑?好吧,下面的例子(我在CS而不是艺术......)应该有所帮助:Helpful illustration

    在代码中,这看起来像

    def merge(a, aux, i0, i1, j0, j1, k0, k1):
        if i0 == i1:
            copy aux[j0, j1] to a[k0, k1]
            return
        if j0 == j1:
            copy aux[i0, i1] to a[k0, k1]
            return       
        im = im = i0 + (i1 - i0) / 2 
        jm = find(aux, j0, j1, aux[im]) 
        km = k0 + (im - i0) + 1 + (jm - j0 )
        a[km] = aux[im]
        fork merge(a, aux, i0, im, j0, jm, k0, km)
        fork merge(a, aux, im + 1, i1, jm, j1, km + 1, k1)
        join
    

    重要的是要注意,find必须使用O(lg n)中的简单串行二进制搜索来完成,因为我们知道右侧已经排序。

    使用这样的并行合并为我们提供了相同的工作,但是将跨度减小到O(lg ^ 3 n),这转换为O(n / lg ^ 2 n)的并行性 - 这是一个很大的改进。

    Nota bene:对于实际中的任何并行算法,如果问题规模太小(快速排序或其他),您将需要使用简单的串行版本 - 必须通过单独评估每个体系结构的叶片大小最佳实验

答案 2 :(得分:1)

我是您所关注的大学课程的助教(以及有关作业的考官)。您已经给出了问题的答案很棒,并且可能描述了与完全顺序排序+合并相比,解决此问题以获得最佳性能和加速的最佳方法。但是,你应该记住,这是面向对象编程的初学者课程,也是你第一次接触并行和多线程的任务。

由于距离截止日期还有14个小时,我不建议您采用先进的方法解决问题,例如扩展诸如ForkJoinPool等库类,并行化双枢轴快速排序等等。这个问题的最简单的解决方案,也是我们给你的任务时我们想到的解决方案,可以按照以下步骤实现:

<强> 算法:

n =线程数

  1. 从文件中将单词读入原始字符串数组。
  2. 将数组拆分为 n 大致相等的长片,或者创建一个函数,将初始数组中的索引分配给您的线程。
  3. 创建线程,为监视器对象提供引用(&#34; Java指针&#34;)。
  4. 一旦为线程分配了初始数组的部分,就可以使用您想要的任何算法开始对它们进行排序。插入排序可能是最简单的&#34;如果你的时间短,可以实施算法。
  5. 在某个线程中完成排序后,让线程报告回监视器,让它知道排序已经完成。
  6. 当两个线程报告回来时,让其中一个线程对按线程排序的数组执行初始排序的合并排序(进一步描述)。
  7. 将结果写回系统上的新文件
  8. &#34;最初排序的合并排序&#34;

    1. 创建两个整数i和j,让它们代表两个最初排序的数组中的索引。
    2. 只要两个阵列创建一个数组即可。长度加在一起。
    3. 从0到result.length迭代,并检查array1 [i]是否小于array2 [j]。 3.1如果是,将array1 [i]添加到结果数组中,并递增i。 3.2如果不是,请将array2 [j]添加到结果数组中,然后递增j。
    4. 一旦您到达任一初始数组的末尾,只需将其他数组的其余部分添加到结果数组中。
    5. 在for循环结束时,对结果数组进行排序,并且array1和array2中的所有字符串都包含在结果数组中。
    6. 祝你好运!