查找int数组中是否包含某个数字的最快方法

7
这是一个奇怪的问题。我在Java中有一个整数数组,其中每个int表示一种颜色。它们将是0xFFFFFFFF或0x0。查找此数组是否包含任何等于0xFFFFFFFF的值最快的方法是什么?
这是我的当前代码:
int length = w * h;
for (int i = 0; i < length; i++) {
    if (pixels[i] == 0xFFFFFFFF) {
        return true;
    }
}

我不知道是否有更快的方法来做这件事。但我想你们这些资深人士可能有一两个诀窍。

编辑:既然它只是从Bitmap.getPixels()获取的一组像素,没有办法对其进行排序或转换为其他存储结构。谢谢大家的意见,看起来在这种情况下循环遍历是最好的方法。


出于好奇,你存储像素在位图中是否有任何原因,因为有更好的格式可用?你能否通过将每个位图转换为更有效的数据结构来进行预处理步骤?似乎做预处理的一次成本可以节省程序后面大量的时间和空间。 - templatetypedef
这实际上是PNG格式。我只是使用Android内置的Bitmap类。如果有更好的方法,我想知道,但我不确定还能做什么。 - Kyle Emmerich
9个回答

13

不,除非整数数组已经排好序,否则没有更快的方法,鉴于它是颜色数组,我对此表示怀疑。

在未排序的数组中进行扫描需要线性时间“O(n)”。这就是你所做的,如果找到匹配项,则立即退出该方法,这也很好。


这正是我所想的,如果可能的话,找到一些新知识从来不会有坏处。谢谢! - Kyle Emmerich

11

不切换到其他数据结构的情况下,没有更好的方法可以查找数组中是否包含该值。因为如果不检查某个特定位置,您可能会错过该像素颜色的一个副本,所以必须查看所有数组元素才能确定是否存在。

尽管如此,有一些替代方法可以解决这个问题。以下是如何加速此过程的几个想法:

  • 如果每个值都保证是白色或黑色,您可以在表示是否存在白色或黑色像素的数组旁边存储两个额外的布尔值。这样,一旦运行了一次扫描,您就可以直接读取布尔值。您还可以存储白色和黑色像素数量的计数器,然后在写入像素时通过将原始颜色的像素数减少并将新颜色的像素数增加来更新计数器。这将使您能够通过查看正确的计数器是否非零来在O(1)时间内检查给定颜色的像素是否存在。

  • 或者,如果您了解图像的某些信息(例如白色和黑色像素应该在哪里),则可以考虑以不同的顺序进行迭代。例如,如果您正在寻找的像素倾向于聚集在图像的中心,那么重写循环以首先检查那里可能是个好主意,因为如果存在该类型的任何像素,您将更快地找到它们。这仍然具有相同的最坏情况行为,但对于“实际”的图像可能会更快。

  • 如果您有多个线程可用且数组非常庞大(数百万个元素),则可以考虑让每个线程在数组的一部分中搜索该值。只有当您有理由怀疑大部分图像不是白色时,才可行。

  • 由于在大多数现实图像中,您可能会假设图像是由多种颜色混合而成的,而您只是在寻找某种颜色的东西,因此您可能希望将图像存储为稀疏数组,其中您存储一个像素列表,这些像素恰好是某种颜色(比如白色),然后假定其他所有像素都是黑色的。如果您预计大多数图像都是由少量离群点组成的单一颜色,那么这可能是一种非常好的表示方式。此外,它可以让您在常数时间内查找是否存在任何黑色或白色像素 - 只需检查设置像素的列表是否为空或包含整个图像。

  • 如果顺序不重要,您还可以将元素存储在某些容器中,例如哈希表,这可以使您在O(1)时间内查找元素是否存在。您还可以对数组进行排序,然后只需检查端点即可。

  • 作为一种微观优化,您可以考虑始终将一个白色像素和一个黑色像素附加到实际图像中,以便您始终可以迭代直到找到值。这可以从循环中消除一个比较(用于检查是否在边界内),对于非常大的数组,一些作者建议使用这种方法

  • 如果您假设大多数图像都是白色和黑色的良好混合,并且可以接受偶尔出现错误的结果,那么您可以考虑探查几个随机位置并检查其中是否有正确的颜色。如果有,那么显然存在正确颜色的像素,您就可以完成任务。否则,请运行完整的线性扫描。对于颜色混合良好的图像,这可以节省大量时间,因为您可以探查一些小数量的位置(比如O(log n)),从而在许多情况下避免进行巨大的线性扫描。这比以前快得多。

  • 如果每个像素值都是白色或黑色,可以考虑使用位向量存储图像。这将使数组的大小压缩为机器字长的因子(可能在32-128倍压缩)。然后,您可以遍历压缩的数组,并查看任何值是否不等于0,以查看是否有任何像素是白色。这也可以节省大量空间,我实际上建议这样做,因为它还使得许多其他操作变得简单易行。

  • 希望这可以帮到您!


    2

    在字节码级别上并不重要,但在本地代码级别上是有影响的。

    if (pixels[i] != 0)
    

    如果你确定只有这两个值出现,那么它可能会更快一些。


    1

    这是一个简单的优化方法,适用于大型数组:将请求的值放置在数组末尾,从而消除数组边界检查。(templatetypedef已经提到了这种优化。) 这个解决方案可以节省25%的循环运行时间,对于大型数组非常有效:

    tmp = a[n - 1]
    a[n - 1] = 0xFFFFFFFF
    
    pos = 0
    while a[pos] != 0xFFFFFFFF
        pos = pos + 1
    
    a[n - 1] = tmp
    
    if a[pos] = 0xFFFFFFFF then
        return pos
    return -1
    

    这是关于C#实现的内容,附有运行时间分析,链接地址为this

    1
    如果你的数组非常大,将其分割成若干段交于多个线程处理可能会更有价值(通常为t个线程,其中t为可用处理器核心数)。对于足够大的数据集,多线程并行处理有可能抵消启动线程所需的成本。

    0
    唯一提高性能的范围就是比较。我觉得位运算符会比条件运算符快一点。
    你可以这样做。
    int length = w * h;
    for (int i = 0; i < length; i++) {
        if (pixels[i] & 0xFFFFFFFF) {
            return true;
        }
    }
    

    2
    虽然这种方法在C语言中是合理的,但我认为这段代码在Java中不会编译。javac不允许从intboolean的隐式转换。在任何情况下,if语句中测试子句的表达式结果都将与静态常量零进行比较。唯一的区别是,在C语言中(缺少boolean类型),这种比较不需要显式地进行。 - Connor Doyle

    0

    你不能在将颜色插入数组时进行检查吗?如果可以的话,你可以存储包含0xFFFFFFFF颜色的数组元素的索引。由于你想要具有此值的“任何”条目,因此应该可以解决这个问题:D

    如果不能,请注意你的答案具有O(n)的复杂度,这是最好的情况,因为该数组不是(并且不能像你所说的那样)有序的。


    -1
    使用内置的 foreach 循环比索引 for 循环稍微快一些,因为它消除了边界检查。
    for(int pix:pixels){
        if(pix!=0)
            return true;
    }
    

    1
    这实际上会慢很多,因为它会自动装箱每个像素值。此外,for each 循环也无法消除边界检查。 - sbridges

    -1
    Arrays.asList(...).contains(...)
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接