PHP的Big-O列表


345

在使用PHP一段时间之后,我注意到并不是所有内置的PHP函数都能达到预期的速度。考虑函数的这两种可能的实现,该函数使用缓存的素数数组查找数字是否为素数。

//very slow for large $prime_array
$prime_array = array( 2, 3, 5, 7, 11, 13, .... 104729, ... );
$result_array = array();
foreach( $prime_array => $number ) {
    $result_array[$number] = in_array( $number, $large_prime_array );
}

//speed is much less dependent on size of $prime_array, and runs much faster.
$prime_array => array( 2 => NULL, 3 => NULL, 5 => NULL, 7 => NULL,
                       11 => NULL, 13 => NULL, .... 104729 => NULL, ... );
foreach( $prime_array => $number ) {
    $result_array[$number] = array_key_exists( $number, $large_prime_array );
}

这是因为in_array使用线性搜索O(n)来实现,线性搜索O(n)随$prime_array增长线性降低。该array_key_exists函数是用哈希查找O(1)来实现的,除非哈希表被填充得足够多(在这种情况下,它只是O(n)),否则它将不会减慢速度。

到目前为止,我不得不通过反复试验来发现big-O,并偶尔查看源代码。现在问题...

是否有所有*内置PHP函数的理论(或实践)大O时间列表?

*或至少是有趣的

举例来说,我觉得很难预测的上市功能的大O,因为可能的实现依赖于PHP的核心未知数据结构:array_mergearray_merge_recursivearray_reversearray_intersectarray_combinestr_replace(与阵列输入)等。


31
完全没有话题,但是1不是质数。
杰森·普尼扬

24
PHP中的数组是哈希表。那应该告诉您所有您需要知道的。在哈希表中搜索键是O(1)。搜索值是O(n)-您不能在未排序的集合上胜过它。您好奇的大多数功能可能都是O(n)。:当然,如果你真的想知道,你可以阅读源cvs.php.net/viewvc.cgi/php-src/ext/standard/...
弗兰克农民

11
作为记录,最快的实现您要执行的操作是(而不是使用NULL作为值)使用true,然后使用进行测试isset($large_prime_array[$number])。如果我没记错的话,它的速度要比该in_array函数快数百倍。
mattbasta 2010年

3
Big O标记与速度无关。这是关于限制行为。
Gumbo 2010年

3
@Kendall我不比较array_key_exists,我要比较in_arrayin_array迭代数组中的每个项目,并将该值与传递给它的指针进行比较。如果将值翻转到键上(并且仅将每个值替换为虚拟值true,则使用的isset速度会提高很多倍。这是因为数组的键已被PHP索引(如哈希表)。以这种方式的阵列可以以速度有显著改善。
mattbasta

Answers:


649

由于似乎没有人做过此事,因此我认为最好在某个地方提供参考。我已经通过基准测试或代码掠过来表征这些array_*功能。我试图将更有趣的Big-O放在顶部。此列表不完整。

注意:假设是哈希查找,所有计算出来的Big-O都是O(1),即使它实际上是O(n)。n的系数是如此之低,在查找Big-O的特性开始生效之前,存储足够大的数组的内存开销会伤害您。例如,array_key_exists在N = 1和N = 1,000,000时的通话时间差为〜50%。

有趣的地方

  1. isset/ array_key_existsin_array和快得多array_search
  2. +(联盟)比array_merge(看起来更好)快一点。但是它的工作方式有所不同,因此请记住这一点。
  3. shuffle 在与Big-O相同的层 array_rand
  4. array_pop/ array_push比重新索引罚款更快array_shift/array_unshift

查询

array_key_existsO(n)但实际上接近O(1)-这是由于碰撞中的线性轮询,但是由于碰撞的机会非常小,因此系数也非常小。我发现您将哈希查找视为O(1)来给出更现实的big-O。例如,N = 1000和N = 100000之间的差异仅减慢了50%。

isset( $array[$index] )O(n)但实际上接近O(1)-它使用与array_key_exists相同的查找。由于它是语言构造,因此如果密钥是硬编码的,将缓存查找,从而在重复使用同一密钥的情况下加快了查找速度。

in_array O(n)-这是因为它将对数组进行线性搜索,直到找到该值为止。

array_search O(n)-它使用与in_array相同的核心功能,但返回值。

队列功能

array_push O(∑ var_i,对于所有i)

array_pop O(1)

array_shift O(n)-必须重新索引所有键

array_unshift O(n + ∑ var_i,对于所有i)-必须重新索引所有键

数组相交,并集,减法

array_intersect_key 如果交集100%进行O(Max(param_i_size)* ∑param_i_count,对于所有i),如果交集0%相交O(∑param_i_size,对于所有i)

array_intersect 如果交集100%对所有i都执行O(n ^ 2 * ∑param_i_count,),如果交集0%与O(n ^ 2)相交

array_intersect_assoc 如果交集100%进行O(Max(param_i_size)* ∑param_i_count,对于所有i),如果交集0%相交O(∑param_i_size,对于所有i)

array_diff O(πparam_i_size,for all i)-那是所有param_sizes的乘积

array_diff_key O(∑ param_i_size,for i!= 1)-这是因为我们不需要遍历第一个数组。

array_merge O(∑ array_i,i!= 1)-不需要遍历第一个数组

+ (联合)O(n),其中n是第二个数组的大小(即array_first + array_second)-比array_merge少的开销,因为它不必重新编号

array_replace O(∑ array_i,对于所有i)

随机

shuffle 上)

array_rand O(n)-需要线性轮询。

明显的Big-O

array_fill 上)

array_fill_keys 上)

range 上)

array_splice O(偏移量+长度)

array_slice O(偏移量+长度)或O(n)如果长度= NULL

array_keys 上)

array_values 上)

array_reverse 上)

array_pad O(pad_size)

array_flip 上)

array_sum 上)

array_product 上)

array_reduce 上)

array_filter 上)

array_map 上)

array_chunk 上)

array_combine 上)

我要感谢Eureqa使得找到函数的Big-O很容易。这是一个了不起的免费程序,可以为任意数据找到最佳拟合函数。

编辑:

对于那些怀疑PHP数组查找是的人O(N),我编写了一个基准测试(O(1)对于大多数实际值它们仍然有效)。

php数组查找图

$tests = 1000000;
$max = 5000001;


for( $i = 1; $i <= $max; $i += 10000 ) {
    //create lookup array
    $array = array_fill( 0, $i, NULL );

    //build test indexes
    $test_indexes = array();
    for( $j = 0; $j < $tests; $j++ ) {
        $test_indexes[] = rand( 0, $i-1 );
    }

    //benchmark array lookups
    $start = microtime( TRUE );
    foreach( $test_indexes as $test_index ) {
        $value = $array[ $test_index ];
        unset( $value );
    }
    $stop = microtime( TRUE );
    unset( $array, $test_indexes, $test_index );

    printf( "%d,%1.15f\n", $i, $stop - $start ); //time per 1mil lookups
    unset( $stop, $start );
}

5
@Kendall:谢谢!我做了一些阅读,结果发现PHP使用“嵌套”哈希表进行冲突。也就是说,它不使用冲突登录结构,而只是使用另一个哈希表。而且我确实了解到,实际上讲PHP哈希表可提供O(1)性能,或平均至少达到O(1)-这就是哈希表的用途。我只是想知道为什么您说它们是“真正的O(n)”而不是“真正的O(logn)”。好的文章!
凸轮

10
时间复杂度应包含在文档中!选择正确的功能可以节省您很多时间,或者告诉您避免执行您打算做的事情:p已经感谢您的清单!
塞缪尔

41
我知道这很旧了...但是呢?该曲线根本不显示O(n),而是显示O(log n),en.wikipedia.org / wiki / Logarithm。对于嵌套哈希图,这也是正确的。
安德里亚斯(Andreas)

5
数组元素上未设置的Big-O是什么?
钱德鲁2014年

12
尽管哈希表确实具有最坏情况下的O(n)查找复杂性,但平均情况为O(1),基准测试的特定情况甚至可以保证为 O(1),因为它是从零开始的,连续的,数字索引的数组,永远不会发生哈希冲突。您仍然看到对数组大小的依赖性与算法复杂性无关的原因,这是由CPU缓存效应引起的。数组越大,随机访问查找就越有可能导致缓存未命中(并且层次结构中的缓存未命中率更高)。
NikiC

5

您具体描述的情况的解释是,关联数组被实现为哈希表-因此按键查找(相应地,array_key_exists)为O(1)。但是,数组不是按值索引的,因此通常情况下,发现数组中是否存在值的唯一方法是线性搜索。那里并不奇怪。

我认为没有关于PHP方法算法复杂性的具体综合文档。但是,如果您有足够大的忧虑需要付出努力,则可以随时查看源代码


这不是真正的答案。正如我在问题中所述,我已经尝试研究PHP源代码。由于PHP是用复杂的宏用C语言编写的,因此有时很难“看清”底层的函数。
肯德尔·霍普金斯

@Kendall我忽略了您对深入源代码的引用。但是,我的答复中有一个答案:“我认为没有关于PHP方法算法复杂性的具体综合文档。” “否”是一个完全有效的答案。(c:
Dathan

4

您几乎总是想使用isset而不是array_key_exists。我没有看内部结构,但是我很确定这array_key_exists是O(N),因为它遍历数组的每个键,而isset尝试使用与访问时使用的哈希算法相同的哈希算法访问元素数组索引。那应该是O(1)。

需要注意的一个“陷阱”是这样的:

$search_array = array('first' => null, 'second' => 4);

// returns false
isset($search_array['first']);

// returns true
array_key_exists('first', $search_array);

我很好奇,因此我对差异进行了基准测试:

<?php

$bigArray = range(1,100000);

$iterations = 1000000;
$start = microtime(true);
while ($iterations--)
{
    isset($bigArray[50000]);
}

echo 'is_set:', microtime(true) - $start, ' seconds', '<br>';

$iterations = 1000000;
$start = microtime(true);
while ($iterations--)
{
    array_key_exists(50000, $bigArray);
}

echo 'array_key_exists:', microtime(true) - $start, ' seconds';
?>

is_set:0.132308959961秒
array_key_exists:2.33202195168秒

当然,这并没有显示时间的复杂性,但确实显示了这两个功能之间的比较。

要测试时间复杂度,请比较在第一个键和最后一个键上运行这些功能之一所花费的时间。


9
错了 我100%确信array_key_exists不必遍历每个键。如果您不相信,请查看下面的链接。isset之所以这么快,是因为它是一种语言构造。这意味着它没有进行函数调用的开销。另外,由于这个原因,我认为它可能正在缓存查询。另外,这也不是问题的答案!我想要一个PHP函数的Big(O)列表(如问题所述)。我的示例没有一个基准。 svn.php.net/repository/php/php-src/branches/PHP_5_3/ext/...
肯德尔·霍普金斯

如果您仍然不相信我,我会创建一个小型基准来说明这一点。pastebin.com/BdKpNvkE
Kendall Hopkins 2010年

基准测试的问题是必须禁用xdebug。=)
Guilherme Blanco 2013年

3
为什么要在array_key_exists上使用isset有两个关键原因。首先,isset是一种语言构造,可减轻函数调用的成本。这类似于$arrray[] = $appendvs array_push($array, $append)参数。其次,array_key_exists还区分非设置值和空值。对于$a = array('fred' => null); array_key_exists('fred', $a)将返回true,而isset($['fred'])将返回false。这个额外的步骤并非易事,将大大增加执行时间。
orca 2013年

0

如果人们在实践中遇到按键冲突的麻烦,他们将使用辅助哈希查找或平衡树来实现容器。平衡树将给出O(log n)最坏情况的行为和O(1)平均。大小写(哈希本身)。在大多数实际的内存应用程序中,开销是不值得的,但也许有些数据库将这种混合策略的形式作为默认情况来实现。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.