中位数java实现的中位数

时间:2016-04-04 15:35:49

标签: java algorithm quicksort binary-search median-of-medians

我使用algs4 quickselect基于Wikipedia article实现了中位数选择算法中位数,但我的代码效果不佳:

1)据说中位数的中位数找到kth 最大元素。但是,我的代码找到了第k个最小元素。

2)我的实现比quickselect慢了1-20倍,但中位数算法的中位数应该渐近更快。

我已多次检查过所有内容,但我找不到问题。

public class MedianOfMedians {
    public static Comparable medianOfMedians(Comparable[] nums, int k) {
        return nums[select(nums, 0, nums.length - 1, k)];
    }

    private static int select(Comparable[] nums, int lo, int hi, int k) {
        while (lo < hi) {
            int pivotIndex = pivot(nums, lo, hi);
            int j = partition(nums, lo, hi, pivotIndex);
            if (j < k) {
                lo = j + 1;
            } else if (j > k) {
                hi = j - 1;
            } else {
                return j;
            }
        }
        return lo;
    }

    private static int pivot(Comparable[] list, int left, int right) {
        // for 5 or less elements just get median
        if (right - left < 5) {
            return partition5(list, left, right);
        }

        // otherwise move the medians of five-element subgroups to the first n/5 positions
        for (int i = left; i <= right; i += 5) {
            // get the median of the i'th five-element subgroup
            int subRight = i + 4;
            if (subRight > right) {
                subRight = right;
            }

            int median5 = partition5(list, i, subRight);

            exch(list, median5, (int) (left + Math.floor((i - left) / 5d)));
        }

        // compute the median of the n/5 medians-of-five
        return select(list,
                left,
                (int) (left + Math.ceil((right - left) / 5d) - 1),
                (int) (left + (right - left) / 10d));
    }

    private static int partition5(Comparable[] list, int lo, int hi) {
        for (int i = lo; i <= hi; i++) {
            for (int j = i; j > lo; j--) {
                if (less(list[j - 1], list[j])) {
                    exch(list, j, j - 1);
                }
            }
        }
        return (hi + lo) / 2;
    }

    private static int partition(Comparable[] a, int lo, int hi, int pivotIndex) {
        exch(a, lo, pivotIndex);
        int i = lo;
        int j = hi + 1;
        Comparable v = a[lo];
        while (true) {
            while (less(a[++i], v) && i != hi) { }
            while (less(v, a[--j]) && j != lo) { }
            if (j <= i) break;
            exch(a, i, j);
        }
        exch(a, j, lo);
        return j;
    }

    private static void exch(Comparable[] nums, int i, int j) { }

    private static boolean less(Comparable v, Comparable w) { }
}

JUnit测试:

public class MedianOfMediansTest {
    private final static int TESTS_COUNT = 100;

    @org.junit.Test
    public void test() {
        // generate TESTS_COUNT arrays of 10000 entries from 0..Integer.MAX_VALUE
        Integer[][] tests = generateTestComparables(TESTS_COUNT, 10000, 10000, 0, Integer.MAX_VALUE);

        for (int i = 0; i < tests.length; i++) {
            Integer[] array1 = Arrays.copyOf(tests[i], tests[i].length);
            Integer[] array2 = Arrays.copyOf(tests[i], tests[i].length);
            Integer[] array3 = Arrays.copyOf(tests[i], tests[i].length);

            long time = System.nanoTime();
            final int a = (Integer) MedianOfMedians.medianOfMedians(array1, 0);
            long nanos_a = System.nanoTime() - time;

            time = System.nanoTime();
            final int b = (Integer) Quick.select(array2, 0);
            long nanos_b = System.nanoTime() - time;

            time = System.nanoTime();
            Arrays.sort(array3);
            final int c = array3[0];
            long nanos_c = System.nanoTime() - time;

            System.out.println("MedianOfMedians: " + a + " (" + nanos_a + ") " +
                    "QuickSelect: " + b + " (" + nanos_b + ") " +
                    "Arrays.sort: " + c + " (" + nanos_c + ")");

            System.out.println(((double) nanos_a) / ((double) nanos_b));

            Assert.assertEquals(c, a);
            Assert.assertEquals(b, a);
        }
    }

    public static Integer[][] generateTestComparables(int numberOfTests,
                                                      int arraySizeMin, int arraySizeMax,
                                                      int valueMin, int valueMax) {
        Random rand = new Random(System.currentTimeMillis());
        Integer[][] ans = new Integer[numberOfTests][];
        for (int i = 0; i < ans.length; i++) {
            ans[i] = new Integer[randInt(rand, arraySizeMin, arraySizeMax)];
            for (int j = 0; j < ans[i].length; j++) {
                ans[i][j] = randInt(rand, valueMin, valueMax);
            }
        }
        return ans;
    }

    public static int randInt(Random rand, int min, int max) {
        return (int) (min + (rand.nextDouble() * ((long) max - (long) min)));
    }
}

1 个答案:

答案 0 :(得分:4)

  

1)据说中位数的中位数找到第k个最大元素。   但是,我的代码找到第k个最小元素。

这并非严格属实。任何选择算法都可以找到最小或最大的元素,因为它基本上是相同的任务。这取决于你如何比较元素以及如何对它们进行分区(以后你总是可以做length - 1 - result之类的事情)。您的代码确实似乎找到了 k 最小的元素,这是实现选择算法的最典型和最直观的方式。

  

2)我的实现比quickselect慢了1-20倍,但是   中位数算法的中位数应该渐近更快。

不仅渐近更快。在最坏的情况下渐进式渐近。在一般情况下,两者都是线性的,但MoM具有更高的常数因子。由于您随机生成测试,因此您不太可能遇到最坏的情况。如果您使用随机化的quickselect,那么对于任何输入,它不太可能遇到最坏的情况,否则概率将取决于所使用的枢轴选择算法。

考虑到这一点,以及中位数的中位数具有高常数因素的事实,你不应该期望它比快速选择表现更好!然而,它可能胜过排序,但即便如此 - 排序中的那些对数因子对于小输入而言并不大(lg 10000约为13-14)。

my MoM solution for a LeetCode problem为例。对于 5亿个元素的数组,Arrays.sort有时优于MoM。在最好的情况下,它运行速度快了两倍。

因此,MoM主要是理论上的兴趣。当你需要100%保证不超过一些时间限制时,我可以想象一个实际的用例。比如,飞机,航天器或核反应堆上的某些实时系统。时间限制不是很紧,但即使超过1纳秒,也是灾难性的。但这是一个非常人为的例子,我怀疑它实际上是它的运作方式。

即使您可以找到MoM的实际用例,也可以使用类似Introselect的内容。它基本上从quickselect开始,然后切换到MoM,如果事情看起来不好。但测试它将是一场噩梦 - 你会如何提出一个实际上迫使算法切换(因此测试MoM部分)的测试,特别是如果它是随机的?

你的代码整体上看起来很好,但是我将一些帮助方法包装为私有,或者甚至将它们移到另一个类中进行单独测试,因为这样的事情很难做到。如果结果正确,您可能不会注意到效果。例如,我不确定您的五人组代码是否100%正确。有时您会使用right - left我希望看到的元素数量应为right - left + 1

另外,我会用纯整数算术等价替换那些ceil / floor调用。也就是说,Math.floor((i - left) / 5d)) =&gt; (i - left) / 5Math.ceil((right - left) / 5d) =&gt; (right - left + 4) / 5(顺便说一句,这是我不喜欢right - left的部分,但我不确定它是否错了。