用于 PHP 函数的 Big-O 列表

2022-08-30 05:57:53

在使用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 );
}

这是因为它是用线性搜索O(n)实现的,它会随着增长而线性减慢。其中函数是用哈希查找O(1)实现的,除非哈希表被非常填充(在这种情况下,它只是O(n)),否则它不会变慢。in_array$prime_arrayarray_key_exists

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

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

*或至少是有趣的

例如,我发现很难预测列出的函数的大O,因为可能的实现取决于PHP的未知核心数据结构:,,,,,(带有数组输入)等。array_mergearray_merge_recursivearray_reversearray_intersectarray_combinestr_replace


答案 1

由于在我认为在某个地方进行参考之前似乎没有人这样做过,因此最好将其用于参考。我已经通过基准测试或代码略读来表征函数。我试图把更有趣的Big-O放在顶部附近。此列表不完整。array_*

注意:假设哈希查找计算的所有 Big-O 都是 O(1),即使它实际上是 O(n)。n的系数非常低,在查找Big-O的特征开始生效之前,存储足够大的数组的RAM开销会伤害您。例如,在 N=1 和 N=1,000,000 时调用之间的差异约为 50% 的时间增加。array_key_exists

有趣的要点

  1. isset/array_key_exists比和快得多in_arrayarray_search
  2. +(union)比(看起来更好)快一点。但它确实以不同的方式工作,所以请记住这一点。array_merge
  3. shuffle与 Big-O 层相同array_rand
  4. array_pop/array_push比 / 由于重新索引惩罚而快array_shiftarray_unshift

查找

array_key_existsO(n)但非常接近O(1) - 这是因为碰撞中的线性轮询,但由于碰撞的几率非常小,系数也非常小。我发现你把哈希查找当作O(1)来给出一个更现实的大O。例如,N=1000 和 N=100000 之间的差异仅减慢约 50%。

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

in_arrayO(n) - 这是因为它通过数组进行线性搜索,直到找到值。

array_searchO(n) - 它使用与in_array相同的核心函数,但返回值。

队列函数

array_pushO(∑ var_i, for all i)

array_popO(1)

array_shiftO(n) - 它必须重新索引所有键

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

数组交集、并集、减法

array_intersect_key如果相交 100% 做 O(最大值(param_i_size)*∑param_i_count,对于所有 i),如果相交点 0% 与 O(∑param_i_size,对于所有 i)

array_intersect如果相交 100% do O(n^2*∑param_i_count,对于所有 i),如果相交点 0% 与 O(n^2) 相交

array_intersect_assoc如果相交 100% 做 O(最大值(param_i_size)*∑param_i_count,对于所有 i),如果相交点 0% 与 O(∑param_i_size,对于所有 i)

array_diffO(π param_i_size,对于所有 i) - 这是所有param_sizes

array_diff_keyO(∑ param_i_size, for i != 1) - 这是因为我们不需要迭代第一个数组。

array_mergeO( ∑ array_i, i != 1 ) - 不需要迭代第一个数组

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

array_replaceO( ∑ array_i, for all i )

随机

shuffleO(n)

array_randO(n) - 需要线性轮询。

明显的大O

array_fillO(n)

array_fill_keysO(n)

rangeO(n)

array_spliceO(偏移量 + 长度)

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

array_keysO(n)

array_valuesO(n)

array_reverseO(n)

array_padO(pad_size)

array_flipO(n)

array_sumO(n)

array_productO(n)

array_reduceO(n)

array_filterO(n)

array_mapO(n)

array_chunkO(n)

array_combineO(n)

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

编辑:

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

php array lookup graph

$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 );
}

答案 2

对于您具体描述的情况的解释是,关联数组是作为哈希表实现的 - 因此按键查找(以及相应的)是O(1)。但是,数组不按值编制索引,因此在一般情况下,发现数组中是否存在值的唯一方法是线性搜索。这并不奇怪。array_key_exists

我不认为有关于PHP方法的算法复杂性的具体全面文档。但是,如果这是一个足够大的问题,需要付出努力,你可以随时查看源代码