标记未排序数组的前N个元素

时间:2009-12-11 03:17:07

标签: php algorithm

我有一个未排序的数组(生成的掷骰子),我想选择它的前N个元素(这对于sort-and-snip是微不足道的),但是在保持它们的顺序的同时标记它们。

E.g:

mark([1,2,3,4], 3) ==> [[1, false], [2, true], [3, true], [4, true]]

mark([3,5,2,6,2], 3) ==> [[3, true], [5, true], [2, false], [6, true], [2, false]]

数组可以包含1个以上的任何值,并且可以是任意长度,并且标记元素的数量是可变的。

我可以和

一起生活
mark([3,5,2,6,2], 3) ==> [[3, true], [5, true], 2, [6, true], [2, true]]

(即,那些被标记为假的数字没有标记),但我宁愿避免使用它。

强制要求数组的顺序保持不变。

如果重复顶部元素(例如:[6,6,6,6,6,6]的前3个),请标记前3个元素。

(N对于复杂性来说足够小而不重要。)

编辑:加分点:添加一个参数以在“顶部”和“底部”模式之间切换。

5 个答案:

答案 0 :(得分:2)

我假设我们在这里谈论PHP,因为问题是标记为PHP。您尝试实现的任何聪明算法都比使用内置函数慢。这就是PHP的工作方式,它不擅长处理用户空间中的数字。

所以你需要做的是对数组的副本进行排序并保存前N个元素的键,然后遍历数组并标记所述元素。但有一个问题:PHP的排序并不稳定。这意味着如果你想在关系中使用元素的位置,你必须自己做。因此,您不必使用asort()arsort()等功能,而是使用array_multisort()

结果如下:

function mark(array $arr, $n, $order = SORT_DESC)
{
    $keys = $values = $position = array();

    $i = 0;
    foreach ($arr as $k => $v)
    {
        $keys[]     = $k;
        $values[]   = $v;
        $position[] = $i;

        ++$i;
    }

    // sort values in given $order, use their position as tiebreaker (always in ascending order)
    array_multisort($values, $order, $position, SORT_ASC, $keys);
    $mark = array_flip(array_slice($keys, 0, $n));

    $ret = array();
    foreach ($arr as $k => $v)
    {
        $ret[] = array($v, isset($mark[$k]));
    }

    return $ret;
}

哪个产生

SORT_DESC
[3,6,6,6,6,6,2] => [[3,false],[6,true],[6,true],[6,true],[6,false],[6,false],[2,false]]
[3,5,2,6,2]     => [[3,true],[5,true],[2,false],[6,true],[2,false]]

SORT_ASC
[3,6,6,6,6,6,2] => [[3,true],[6,true],[6,false],[6,false],[6,false],[6,false],[2,true]]
[3,5,2,6,2]     => [[3,true],[5,false],[2,true],[6,false],[2,true]]

答案 1 :(得分:1)

当前接受的答案扫描输入列表m次。这个只扫描两次。 O(n)vs O(n * m)。您需要堆数据结构。这是在Python中。

import heapq

def mark(data, n):
    top = heapq.nlargest(n, ((value, index) for index, value in enumerate(data)))
    indexes = set(value[1] for value in top)
    return [[value, index in indexes] for index, value in enumerate(data)]

print mark([1, 2, 3, 4], 3)
print mark([3, 5, 2, 6, 2], 3)

输出:

[[1, False], [2, True], [3, True], [4, True]]
[[3, True], [5, True], [2, False], [6, True], [2, False]]

答案 2 :(得分:0)

如果它足够小以致复杂性无关紧要:(伪代码)

for(int m = 0; m < mark_count; m++) {
    highest = MIN_INT;
    highestindex = -1;
    foreach i in array:
        if array[i] > highest && is_unmarked(i)
            highest = array[i]
            highestindex = i;
    mark(i)
}

编辑:如果你想找到底部的,请在MAX_INT开始我们的计数器并检查数组中的值是否小于它。

如果您想要mark()is_unmarked的示例实现:

function mark(i) {
    array[i] = [array[i], true];
}

function is_unmarked(i) {
    if (array[i] is array & array[i][1] == true)
        return false;
    return true;
}

(不确定is是否按预期工作 - 但意思很明确,我希望如此)

答案 3 :(得分:0)

您可以将quick select算法与索引数组一起使用。你不是操纵传入的数组,而是操纵索引的顺序,然后标记顶部的N.这将需要线性时间。

答案 4 :(得分:0)

如果需要,我会以下列方式对其进行优化。如果不需要进行优化,那就按照自己的意愿行事。

  1. 如果array小于N“top N”值,则标记为TRUE。 O(N)
  2. 浏览项目的 arr 并标记所有false,同时维护前N个项目的 arr 位置和值列表。此外,您只需要检查/比较第N项,最低,更大(topN [N-1],arr [i])。当在 arr [0] arr [N-1] 上进行迭代时,topN正在填充N,而应该有条件将其添加到前N个列表中,直到N被填充,仍然保持最低的底部。 O( arr.size()
  3. 现在所有都被标记为错误循环超过前N个列表并返回并在 arr 中标记与在顶部N.O(N)
  4. 中一样真实的位置

    所以整体成本是O( N + arr.size()