查找未排序列表的第 N 项而不对列表进行排序

Posted

技术标签:

【中文标题】查找未排序列表的第 N 项而不对列表进行排序【英文标题】:Finding Nth item of unsorted list without sorting the list 【发布时间】:2010-11-05 07:28:42 【问题描述】:

嘿。我有一个非常大的数组,我想找到第 N 个最大值。很简单,我可以对数组进行排序,然后取第 N 个元素,但我只对一个元素感兴趣,所以可能有比对整个数组进行排序更好的方法......

【问题讨论】:

【参考方案1】:

堆是此操作的最佳数据结构,Python 有一个出色的内置库来执行此操作,称为 heapq。

import heapq

def nth_largest(n, iter):
    return heapq.nlargest(n, iter)[-1]

示例用法:

>>> import random
>>> iter = [random.randint(0,1000) for i in range(100)]
>>> n = 10
>>> nth_largest(n, iter)
920

通过排序确认结果:

>>> list(sorted(iter))[-10]
920

【讨论】:

如果你想要第 n 个最大或最小的项目,这很好用(线性时间),其中 n 是一个常数。如果 n 是列表长度的一半(即你想要中位数),这仍然是 O(nlogn) 时间。 这不是就地解决方案,Quickselect 不会像此解决方案那样添加 O(n) 额外内存。因此,对于问题所问的非常大的数组,这可能不是最有效的。【参考方案2】:

排序至少需要 O(nlogn) 运行时间 - 有非常高效的 selection algorithms 可以在线性时间内解决您的问题。

Partition-based selection(有时是Quick select),它基于快速排序(递归分区)的思想,是一个很好的解决方案(参见伪代码链接+Another example)。

【讨论】:

不错的链接。我相信这是最好的。 不幸的是,“另一个示例”链接现在指向 MIT 的受保护网页,您必须拥有访问权限。 NumPy has this built-in,虽然如果你还没有使用它的 ndarray 功能,那么它是一种奇怪的依赖。【参考方案3】:

一个简单的修改过的快速排序在实践中效果很好。它的平均运行时间与 N 成正比(尽管最坏情况下的运行时间是 O(N^2))。

像快速排序一样进行。随机选择一个枢轴值,然后通过您的值流式传输并查看它们是否高于或低于该枢轴值,并根据该比较将它们放入两个箱中。 在快速排序中,您将递归地对这两个箱中的每一个进行排序。但是对于第 N 个最高值的计算,您只需要对一个 bin 进行排序。每个 bin 的数量会告诉您哪个 bin 拥有您的第 n 个最高值。因此,例如,如果您想要第 125 个最高值,并且您将其分类为两个箱,其中 75 在“高”箱中,150 在“低”箱中,您可以忽略高箱并继续查找 125-75 =仅在低箱中的第 50 个最高值。

【讨论】:

【参考方案4】:

您可以迭代整个序列,维护您找到的 5 个最大值的列表(这将是 O(n))。话虽如此,我认为对列表进行排序会更简单。

【讨论】:

但是当它不是第 5 个而是第 n 个元素时,你会得到 O(n²),这比排序还要糟糕。 我想您的意思是维护一个包含 N 个最大值的列表。但在这种情况下,N 不能太大。【参考方案5】:

您可以尝试 Median of Medians 方法 - 它的速度是 O(N)。

【讨论】:

【参考方案6】:

使用堆排序。它只是对列表进行部分排序,直到您将元素绘制出来。

【讨论】:

尝试找到第 n/2 个元素 - 需要 O(nlogn)!【参考方案7】:

您实际上想要生成一个“top-N”列表并选择该列表末尾的那个。

因此,您可以扫描一次数组并在 largeArray 项目大于前 N 列表的最后一项时插入一个空列表,然后删除最后一项。

完成扫描后,选择前 N 个列表中的最后一项。

整数和 N = 5 的示例:

int[] top5 = new int[5]();
top5[0] = top5[1] = top5[2] = top5[3] = top5[4] = 0x80000000; // or your min value

for(int i = 0; i < largeArray.length; i++) 
    if(largeArray[i] > top5[4]) 
       // insert into top5:
       top5[4] = largeArray[i];

       // resort:
       quickSort(top5);
    

【讨论】:

【参考方案8】:

正如人们所说,只要跟踪 K 个最大值,您就可以遍历列表。如果 K 很大,这个算法将接近 O(n2)。

但是,您可以将第 K 个最大值存储为二叉树,操作变为 O(n log k)。

根据***,这是最好的选择算法:

 function findFirstK(list, left, right, k)
     if right > left
         select pivotIndex between left and right
         pivotNewIndex := partition(list, left, right, pivotIndex)
         if pivotNewIndex > k  // new condition
             findFirstK(list, left, pivotNewIndex-1, k)
         if pivotNewIndex < k
             findFirstK(list, pivotNewIndex+1, right, k)

它的复杂度是O(n)

【讨论】:

我相信锦标赛算法(参见 Dario 的链接)是您的目标。它的运算时间为 O(n + k*log(n))。 我的错误,虽然我有兴趣在 Python 中看到它的完整实现。【参考方案9】:

如果这是在生产代码中,您应该做的一件事是使用数据样本进行测试。 例如,您可能会考虑 1000 或 10000 个元素的“大”数组,并从配方中编写快速选择方法。

sorted 的编译特性,以及它有些隐藏和不断发展的优化,使其在中小型数据集(

因此,即使 quickselect 是 O(n) 与 sorted 的 O(nlogn),这也没有考虑处理每个 n 元素需要多少实际机器代码指令,对流水线的任何影响,处理器缓存的使用和sorted 的创建者和维护者会在 python 代码中添加其他内容。

【讨论】:

【参考方案10】:

您可以为每个元素保留两个不同的计数 - 大于该元素的元素数和小于该元素的元素数。

然后做一个 if 检查 N == 比每个元素大的元素数 -- 满足上述条件的元素就是你的输出

检查以下解决方案

def NthHighest(l,n):
    if len(l) <n:
        return 0

    for i in range(len(l)):
        low_count = 0
        up_count = 0

        for j in range(len(l)):
            if l[j] > l[i]:
                up_count = up_count + 1
            else:
                low_count = low_count + 1

        # print(l[i],low_count, up_count)
        if up_count == n-1:
            #print(l[i])
            return l[i]

# # find the 4th largest number 

l = [1,3,4,9,5,15,5,13,19,27,22]
print(NthHighest(l,4))  

-- 使用上述解决方案,您可以找到两者 - Nth highest as well as Nth Lowest

【讨论】:

【参考方案11】:

如果你不介意使用 pandas,那么:

import pandas as pd
N = 10
column_name = 0
pd.DataFrame(your_array).nlargest(N, column_name)

上面的代码将显示N个最大值以及每个值的索引位置。

希望对您有所帮助。 :-)

Pandas Nlargest Documentation

【讨论】:

以上是关于查找未排序列表的第 N 项而不对列表进行排序的主要内容,如果未能解决你的问题,请参考以下文章

查找功能不适用于未排序的列表

在 Python 中查找逗号分隔列表中的第 N 个项目

Python二分搜索类函数,用于查找排序列表中大于特定值的第一个数字

选择排序算法

归并排序

这是对多个链接列表进行排序的有效方法吗?