【问题标题】:Finding Median WITHOUT Data Structures在没有数据结构的情况下寻找中位数
【发布时间】:2015-08-03 08:05:48
【问题描述】:

(我的代码是用 Java 编写的,但问题是不可知的;我只是在寻找算法思路)

所以问题来了:我做了一个方法,可以简单地找到数据集的中位数(以数组的形式给出)。这是实现:

public static double getMedian(int[] numset) {
    ArrayList<Integer> anumset = new ArrayList<Integer>();
    for(int num : numset) {
        anumset.add(num);
    }
    anumset.sort(null);

    if(anumset.size() % 2 == 0) {
        return anumset.get(anumset.size() / 2);
    } else {
        return (anumset.get(anumset.size() / 2)
                   + anumset.get((anumset.size() / 2) + 1)) / 2;
    }
}

然后我去的学校的一位老师挑战我写一个方法来再次找到中位数,但不使用任何数据结构。这包括任何可以保存多个值的东西,包括字符串、任何形式的数组等。我花了很长时间试图构思一个想法,但我被难住了。有什么想法吗?

【问题讨论】:

标签: language-agnostic median


【解决方案1】:

Sort 数组就位。就像你已经在做的那样,取数组中间的元素。无需额外的存储空间。

在 Java 中这将花费 n log n 时间左右。最佳可能时间是线性的(您必须至少检查每个元素一次以确保您得到正确的答案)。出于教学目的,额外的复杂性降低是不值得的。

如果您无法就地修改数组,则必须牺牲大量额外的时间复杂度,以避免使用与输入大小的一半成比例的额外存储空间。 (如果你愿意接受近似值,情况并非如此。)

【讨论】:

  • 需要引用“如果没有与输入大小的一半成比例的额外存储,就没有其他方法可以找到它。”我相信我的回答正是这样做的(尽管速度很慢)。
  • 耸耸肩,我很高兴放松我的主张,因为我不在乎,但是,我认为您的算法目前并不正确。
【解决方案2】:

一些不是很有效的想法:

对于数组中的每个值,通过数组计算低于当前值的值的数量。如果该计数是数组长度的“一半”,则您有中位数。 O(n^2)(需要一些思考才能弄清楚如何处理中值的重复。)

您可以通过跟踪迄今为止的最小值和最大值来稍微提高性能。例如,如果您已经确定 50 太高而不能作为中位数,那么您可以跳过数组中每个大于或等于 50 的计数。同样,如果您已经确定 25太低,您可以跳过每个小于或等于 25 的值的计数。

在 C++ 中:

    int Median(const std::vector<int> &values) {
        assert(!values.empty());
        const std::size_t half = values.size() / 2;
        int min = *std::min_element(values.begin(), values.end());
        int max = *std::max_element(values.begin(), values.end());
        for (auto candidate : values) {
            if (min <= candidate && candidate <= max) {
                const std::size_t count =
                    std::count_if(values.begin(), values.end(), [&](int x)
                                    { return x < candidate; });
                if (count == half)     return candidate;
                else if (count > half) max = candidate;
                else                   min = candidate;
            }
        }
        return min + (max - min) / 2;
    }

性能很差,但它不使用数据结构,也不修改输入数组。

【讨论】:

  • 我不喜欢这种现代 C++ 的东西,所以也许我在编译这个时搞砸了......但是当我问这个 {5, 6, 6, 6} 的中位数时,我得到了1073741826。我把它翻译成球拍,那个代码给了我同样的答案。我认为它大约关闭了 1073741820?
  • @Jay Kominek:哎呀!我错过了一些测试用例。错误已修复。如果您发现另一个失败的案例,请告诉我。
【解决方案3】:

该任务的常用算法是 Hoare 的 Select 算法。这很像快速排序,除了在快速排序中,您在分区后递归地对 两个 半部分进行排序,但对于 select,您只需在包含感兴趣项目的分区中执行递归调用。

例如,让我们考虑这样一个输入,我们将在其中找到第四个元素:

[ 7, 1, 17, 21, 3, 12, 0, 5 ]

我们将任意使用第一个元素 (7) 作为我们的枢轴。我们最初将其拆分为(用 * 标记的枢轴:

[ 1, 3, 0, 5, ] *7, [ 17, 21, 12]

我们正在寻找第四个元素,而 7 是第五个元素,所以我们然后(仅)分割左侧。我们将再次使用第一个元素作为我们的枢轴,给出(使用{} 来标记我们现在只是忽略的输入部分)。

[ 0 ] 1 [ 3, 5 ] { 7, 17, 21, 12 }

1 已成为第二个元素,因此我们需要将项目划分到其右侧(3 和 5):

{0, 1} 3 [5] {7, 17, 21, 12}

使用3 作为枢轴元素,我们最终左边什么都没有,右边是53 是第三个元素,所以我们需要向右看。这只是一个元素,所以 (5) 是我们的中位数。

通过忽略未使用的一面,这将排序的复杂性从 O(n log n) 降低到仅 O(N) [尽管我有点滥用符号——在这种情况下,我们正在处理预期的行为,而不是最坏的情况,就像 big-O 通常做的那样]。

如果您想确保良好的行为,还有中位数算法(以平均速度稍慢为代价)。

这保证了 O(N) 复杂度。

【讨论】:

  • 尚不清楚该问题是否允许对数组进行部分重新排序。请注意,最初的解决方案是对数组的副本进行排序,而不是对数组本身进行排序。
猜你喜欢
  • 2012-07-06
  • 2012-08-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多