数组可以比排序更有效地分组吗?

时间:2016-06-01 21:33:07

标签: algorithm performance sorting language-agnostic

在处理算法问题的示例代码时,我遇到了我正在对输入数组进行排序的情况,即使我只需要将相同的元素组合在一起,但不是按任何特定的顺序排列,例如:

  

{1,2,4,1,4,3,2}→{1,1,2,2,4,4,3}或{1,1,2,2,3,4,4}或{3,1,1,2,2,4,4}或......

让我想知道:是否可以比排序数组更有效地将数组中的相同元素组合在一起?

一方面,元素不需要移动到特定位置的事实意味着更自由地找到需要更少交换的订单。另一方面,跟踪组中每个元素的位置以及最佳最终位置是什么,可能需要比简单排序数组更多的计算。

逻辑候选者将是计数排序的一种类型,但是如果数组长度和/或值范围不切实际大怎么办?

为了论证,我们假设数组很大(例如一百万个元素),包含32位整数,每个值的相同元素数可以是1到1之间的任何值万美元。

更新:对于支持词典的语言,萨尔瓦多·达利的答案显然是要走的路。我仍然有兴趣听听老式的比较和交换方法,或者使用更少空间的方法,如果有的话。

4 个答案:

答案 0 :(得分:6)

由于您询问了基于比较的方法,我将做出通常的假设:(1)元素可以进行比较而不是散列(2)唯一感兴趣的资源是三向操作。

从绝对意义上说,分组比分类更容易。这是一个使用一个比较的三个元素的分组算法(排序需要三个)。给定输入x, y, z,如果x = y,则返回x, y, z。否则,请返回x, z, y

然而,渐近地,分组和排序都需要进行Omega(n log n)比较。下界技术是信息论的:我们证明,对于表示为决策树的每个分组算法,都有3^Omega(n log n)个叶子,这意味着树的高度(因此最坏情况下的运行时间为算法)是Omega(n log n)

修复决策树的任意叶子,其中未发现输入元素相等。输入位置由发现的不等式部分排序。

相反,假设i, j, k是成对无比的输入位置。让x = input[i], y = input[j], z = input[k],可能性x = y < zy = z < x以及z = x < y都与算法观察到的一致。这不可能,因为叶子选择的一个订单不可能将x放在y旁边的z旁边x。我们得出结论,部分顺序没有基数三的反共。

Dilworth's theorem,部分订单有两条链覆盖整个输入。通过考虑将这些链合并为总顺序的所有可能方法,最多可以映射到每个叶子的n choose m ≤ 2^n个排列。因此,叶子的数量至少为n!/2^n = 3^Omega(n log n)

答案 1 :(得分:3)

是的,您需要做的就是创建一个字典并计算每次有多少元素。之后,只需迭代该字典中的键,并输出与该键值相同的时间。

快速python实现:

from collections import Counter
arr = [1,2,4,1,4,3,2]
cnt, grouped = Counter(arr), []  # counter create a dictionary which counts the number of each element
for k, v in cnt.iteritems():
    grouped += [k] * v # [k] * v create an array of length v, which has all elements equal to k

print grouped

这将使用O(n)个额外空格对O(n)时间内的所有元素进行分组。哪个(在时间复杂度方面)比在O(n logn)时间内实现这一目标的排序更有效,并且可以在现场完成。

答案 2 :(得分:1)

任何排序算法,即使是最有效的排序算法,都要求您多次遍历数组。另一方面,分组可以在一次迭代中完成,具体取决于您坚持将结果格式化为两种:

groups = {}
for i in arr:
    if i not in groups:
        groups[i] = []
    groups[i].append(i)

这是一个非常原始的循环,忽略了您选择的语言中可能提供的许多优化和习惯用法,但只需一次迭代即可实现这一点:

{1: [1, 1], 2: [2, 2], 3: [3], 4: [4, 4]}

如果您有复杂的对象,您可以选择任意属性作为字典键进行分组,因此这是一种非常通用的算法。

如果您坚持将结果列为单一列表,则可以轻松实现:

result = []
for l in groups:
    result += l

(再次,忽略特定的语言优化和习语。)

因此,您有一个恒定时间解决方案,最多需要一次输入的完整迭代和一次中间分组数据结构的较小迭代。空间要求取决于语言的具体情况,但通常只是字典和列表数据结构产生的一点点开销。

答案 3 :(得分:1)

如何使用二维数组,第一维是每个值的频率,第二维是值本身。我们可以利用布尔数据类型和索引。这也允许我们立即对原始数组进行排序,同时在原始数组上循环一次,为我们提供 getProducts(): Observable<IProduct[]> { return this._http.get(this._productUrl) .map((response: Response) => <IProduct[]> response.json()) .publishReplay(1) .refCount() .do(data => console.log('All: ' + JSON.stringify(data))) .catch(this.handleError); } 解决方案。我认为这种方法很好地转化为其他语言。观察以下基本R代码(N.B.R中有比下面更有效的方法,我只是给出一种更通用的方法)。

O(n)

上述代码的小例子:

GroupArray <- function(arr.in) {

    maxVal <- max(arr.in)

    arr.out.val <- rep(FALSE, maxVal)  ## F, F, F, F, ...
    arr.out.freq <- rep(0L, maxVal)     ## 0, 0, 0, 0, ... 

    for (i in arr.in) {
        arr.out.freq[i] <- arr.out.freq[i]+1L
        arr.out.val[i] <- TRUE
    }

    myvals <- which(arr.out.val)   ## "which" returns the TRUE indices

    array(c(arr.out.freq[myvals],myvals), dim = c(length(myvals), 2), dimnames = list(NULL,c("freq","vals")))
}

更大的例子:

set.seed(11)
arr1 <- sample(10, 10, replace = TRUE)

arr1                                    
[1]  3  1  6  1  1 10  1  3  9  2     ## unsorted array

GroupArray(arr1)    
     freq vals       ## Nicely sorted with the frequency
[1,]    4    1
[2,]    1    2
[3,]    2    3
[4,]    1    6
[5,]    1    9
[6,]    1   10