PHP 数组数据结构

一,数组查找

PHP 数组查找常用函数

函数 说明 示例
in_array() 检查值是否存在于数组中 in_array(3, $arr)
array_key_exists() 检查数组中是否存在某个键 array_key_exists("b", $arr)
array_search() 查找值在数组中的键 array_search(3, $arr)
array_filter() 筛选符合条件的数组元素 array_filter($arr, $callback)
array_keys() 获取符合条件的键 array_keys(array_filter($arr))
max() / min() 查找数组中的最大值或最小值 max($arr) / min($arr)
array_values() 获取数组中所有值的列表(没有键) array_values($arr)

1,顺序查找法

顺序查找法(也称为线性查找法)是一种简单的查找方式,它逐个检查数组中的每个元素,直到找到目标元素或遍历完所有元素。它的时间复杂度为 O(n),即随着数组的大小增加,查找的时间也线性增加。

顺序查找法适用于任何类型的数组(无论是索引数组还是关联数组),特别适用于那些没有经过排序的数组,或者在数组规模不大的情况下。

实例一,顺序查找法之实现方法

复制代码
<?php
function search($arr, $find)
{
    foreach ($arr as $key => $value) {
        if ($value == $find) {
            return "{$find}在数组中的键名为:$key";
        }
    }
    return "查找失败";                           
}
// 测试
$arr = [109, 24, 53, 4, 95];
echo search($arr, 53) . "<br>";  // 在每个 echo 后添加 <br> 标签
echo search($arr, 95) . "<br>";
echo search($arr, 4) . "<br>";
echo search($arr, 24) . "<br>";
echo search($arr, 109) . "<br>";
echo search($arr, 100) . "<br>";
?>

输入: 输入是一个数组 $arr 和一个要查找的值 $find

输出: 如果数组中找到目标值 $find,则返回目标值的键名;否则,返回"查找失败"。

操作: 代码通过 foreach 循环遍历数组,逐个元素进行比较,直到找到目标值或遍历结束。

2. 数据结构:

这段代码操作的数组是 PHP 中的索引数组 (也可以是关联数组)。PHP 数组实际上是哈希表实现的,虽然它可以通过整数索引作为下标,但它的查找并不总是像传统的线性数组那样简单(例如 C++ 中的 std::vector)。

  • PHP 数组内部实现: PHP 数组本质上是哈希表。在哈希表中,每个键(key)都会映射到一个位置(值)。因此,查找时会根据键的哈希值直接跳转到位置,而不需要遍历整个数组。不过在 foreach 中遍历数组时,它实际上是线性地遍历所有元素的,因为 PHP 仍然需要依次访问每个元素。

  • 数组的查找: 在该代码中,数组的查找是基于数组的值进行的,而非键。因此,尽管 PHP 数组有哈希表优化(针对键的查找),但该代码通过遍历每个元素来查找值,不能利用哈希表的优势。

3. 性能分析:

时间复杂度(Time Complexity):

  • 最坏情况: 线性查找需要遍历整个数组。当查找值位于数组的末尾,或者数组中不存在该值时,foreach 需要遍历所有元素。因此,在最坏的情况下,时间复杂度是 O(n),其中 n 是数组的元素个数。

  • 最佳情况: 最佳情况是目标值位于数组的开头,此时只需要遍历第一个元素就能找到目标值。最佳时间复杂度是 O(1)。

  • 平均情况: 如果目标值在数组中随机分布,平均情况下,查找操作的时间复杂度为 O(n/2),即 O(n)。

空间复杂度(Space Complexity):

  • 空间复杂度为 O(1),因为该算法只使用了常数空间(除了输入的数组和输出的结果)。没有使用额外的存储结构,因此空间复杂度是常数级别。
4. 性能瓶颈:
  • 线性查找的效率问题: 当数组很大时,线性查找的效率较低,尤其是在数组规模较大时,查找时间随着数组的增大而线性增长。对于一个包含数百万元素的数组,这种查找方法可能非常低效。

  • 数组的遍历: 在每次查找时,代码都需要遍历整个数组,这对于查找频繁的情况会造成很大的性能开销。如果你频繁执行多次查找,或者数组规模很大,性能问题会更加明显。


2,二分查找法

二分查找(Binary Search)是一种高效的查找算法,通常用于在一个已经排序的数组中查找某个元素。它通过不断地将查找范围一分为二,从而减少了查找的次数。

二分查找的基本原理

  1. 初始范围:开始时,查找范围是整个数组。

  2. 中间元素

    :计算数组中间位置的元素,将其与目标值进行比较。

    • 如果目标值等于中间元素,返回该元素的位置。

    • 如果目标值小于中间元素,则目标值只能出现在中间元素左边的子数组中,更新查找范围为左半部分。

    • 如果目标值大于中间元素,则目标值只能出现在中间元素右边的子数组中,更新查找范围为右半部分。

  3. 重复:继续在更新后的子数组中进行二分查找,直到找到目标值或者查找范围为空。

实例二,二分查找法之实现方法

复制代码
<?php
function bSearch($arr,$start, $len,$find)
{
    $end = $len - 1;
    $mid = floor(($start + $end) / 2);
    if ($start > $end) {
        return 'not found';
    } elseif ($arr[$mid] > $find){
        return bSearch($arr, $start, $mid, $find);
    } elseif ($arr[$mid] < $find){
        return bSearch($arr, $mid + 1, $len, $find);
    } else {
        return $mid;
    }    
}
$arr = [111,22,37,42,99,128];
$len = count($arr);
echo bSearch($arr, 0, $len, 37) . "\n"; 
echo bSearch($arr, 0, $len, 99) . "\n"; 
echo bSearch($arr, 0, $len, 128) . "\n"; 
echo bSearch($arr, 0, $len, 100) . "\n"; 
1. 函数实现分析

函数签名

复制代码
function bSearch($arr, $start, $len, $find)
  • $arr:待查找的数组。

  • $start:查找的起始索引。

  • $len:数组的总长度(在递归中作为边界使用)。

  • $find:要查找的目标值。

二分查找的递归逻辑

在每一次递归中,都会通过计算 mid(中间元素的下标)来将搜索范围缩小一半。具体步骤如下:

  • 基线条件 :当 $start > $end 时,意味着查找范围已为空,因此返回 'not found',表示目标值不在数组中。

  • 递归条件

    • 如果 arr[$mid] > $find,目标值位于中间值的左边,递归左半部分:bSearch($arr, $start, $mid, $find)

    • 如果 arr[$mid] < $find,目标值位于中间值的右边,递归右半部分:bSearch($arr, $mid + 1, $len, $find)

    • 如果 arr[$mid] == $find,则返回中间元素的索引 $mid

2. 问题分析

2.1 数组未排序

二分查找的前提条件是数组必须是已排序 的。如果数组未排序,这段代码可能会产生错误的结果。在你给出的代码中,数组 [111, 22, 37, 42, 99, 128] 是无序的,所以这段代码在查找时是不可靠的。

改进建议 :首先需要对数组进行排序,例如使用 PHP 内建的 sort() 函数:

复制代码
sort($arr); // 对数组进行升序排序

2.2 递归中的边界问题

在二分查找的递归实现中,尤其是在递归的右半部分,传递给下一层递归的 $start$end 参数可能会出现问题。递归时,如果查找右半部分,应该是 start = $mid + 1end = $len - 1,而不是 end = $len,这样会使得下次查找的数组范围不正确。

  • 当前代码的问题 :在递归调用右半部分时,bSearch($arr, $mid + 1, $len, $find)$len 仍然传递给递归调用,但这会导致递归继续查找从 $mid + 1$len - 1 范围内的元素。$len 应该传递给递归时作为数组的实际长度,而不应该作为查找范围的上限。

  • 改进建议 :递归时正确更新右半部分的 end 参数。可以将 bSearch($arr, $mid + 1, $len, $find) 改为 bSearch($arr, $mid + 1, $end, $find)

2.3 mid 计算问题

在每次递归时,计算中间值时,使用了 floor(($start + $end) / 2) 来避免可能的浮动结果。理论上这没有问题,但如果数组长度非常大,可能会出现 ($start + $end) 的加法溢出。在大多数语言中,使用这种加法方法不会有问题,但从代码的健壮性角度,建议使用下面的公式来避免溢出:

复制代码
$mid = $start + intdiv($end - $start, 2);

intdiv($end - $start, 2) 的意思是计算 (end - start) / 2 的整数部分,这种方式更加健壮。

3. 代码改进

根据上述分析,可以对代码进行如下修改:

  1. 排序:对数组进行排序。

  2. 修正递归调用的 end 参数

  3. 改进 mid 计算的方式

修改后的代码:

复制代码
function bSearch($arr, $start, $end, $find) {
    if ($start > $end) {
        return 'not found';  // 基线条件,查找结束
    }
​
    $mid = $start + intdiv($end - $start, 2);  // 计算中间位置,避免溢出
​
    if ($arr[$mid] == $find) {
        return $mid;  // 找到目标,返回索引
    } elseif ($arr[$mid] > $find) {
        return bSearch($arr, $start, $mid - 1, $find);  // 查找左半部分
    } else {
        return bSearch($arr, $mid + 1, $end, $find);  // 查找右半部分
    }
}
​
// 示例用法
$arr = [111, 22, 37, 42, 99, 128];
sort($arr);  // 对数组进行排序
​
$len = count($arr);
echo bSearch($arr, 0, $len - 1, 37) . "\n";   // 查找 37
echo bSearch($arr, 0, $len - 1, 99) . "\n";   // 查找 99
echo bSearch($arr, 0, $len - 1, 128) . "\n";  // 查找 128
echo bSearch($arr, 0, $len - 1, 100) . "\n";  // 查找 100 (不存在)
4. 时间复杂度

二分查找的时间复杂度为 O(log n) ,其中 n 是数组的长度。在每次递归中,我们都将搜索范围缩小为原来的一半,因此算法效率很高,适合在大规模数据中查找。

5. 空间复杂度

由于使用了递归,二分查找的空间复杂度为 O(log n) 。每次递归调用都会占用栈空间,因此栈空间的深度与递归的次数成正比。最坏情况下,递归深度为 log n

总结
  • 这段代码实现了递归版的二分查找,存在一些细节问题,尤其是在递归参数的传递和数组未排序的情况下。

  • 改进后的代码修正了递归中的参数问题,并确保了数组是有序的。

  • 从数据结构的角度看,二分查找适用于有序数组,并且它能大大减少查找的时间复杂度。


二,数组排序

1,冒泡排序

冒泡排序(Bubble Sort)是一种简单的排序算法,基本思想是通过重复遍历待排序的数组,每次比较相邻的元素,如果它们的顺序错误就交换它们的位置。每一轮遍历都能确保一个元素被排到它应该在的位置。这个过程就像水泡在水中逐渐向上冒一样,因此得名"冒泡排序"。

冒泡排序的基本思想

  1. 从数组的开始位置开始,比较相邻的元素。

  2. 如果前面的元素比后面的元素大,则交换它们的位置。

  3. 每一轮遍历都将最大的元素"冒泡"到数组的末尾。

  4. 重复上述过程,直到整个数组排序完成。

实例三,冒泡排序之实现方法
复制代码
function bubbleSort($arr) {
    $n = count($arr); // 获取数组长度
    // 外层循环控制比较次数,每次外层循环都会把一个元素排到末尾
    for ($i = 0; $i < $n - 1; $i++) {
        // 内层循环进行相邻元素比较和交换
        for ($j = 0; $j < $n - $i - 1; $j++) {
            if ($arr[$j] > $arr[$j + 1]) {
                // 交换元素
                $temp = $arr[$j];
                $arr[$j] = $arr[$j + 1];
                $arr[$j + 1] = $temp;
            }
        }
    }
    return $arr;
}
​
// 示例
$arr = [5, 2, 9, 1, 5, 6];
$sortedArr = bubbleSort($arr);
print_r($sortedArr);
代码解析
  1. 外层循环for ($i = 0; $i < $n - 1; $i++),用于控制排序的轮数,每一轮都会将一个最大元素"冒泡"到数组的末尾。

  2. 内层循环for ($j = 0; $j < $n - $i - 1; $j++),用于在每一轮中进行相邻元素的比较和交换。注意,内层循环的次数随着外层循环的进行而减少,因为每一轮结束后,数组末尾的元素已经排好序。

  3. 交换 :如果当前元素 $arr[$j] 大于下一个元素 $arr[$j + 1],就交换它们的位置。

  4. 返回排序结果:排序完成后返回排序后的数组。

冒泡排序的时间复杂度
  • 最坏时间复杂度:O(n²),发生在数组完全倒序的情况下,需要进行 n(n-1)/2 次比较和交换。

  • 最好时间复杂度:O(n),发生在数组已经排好序的情况下,只需要进行一次遍历即可(如果优化的话)。

  • 平均时间复杂度:O(n²),通常情况下,比较和交换的次数接近于 n²。

冒泡排序的空间复杂度
  • 空间复杂度:O(1),因为冒泡排序是原地排序算法,只需要常数级别的额外空间来交换元素。
优化:提前终止排序

冒泡排序的一个常见优化方法是在某一轮遍历中,如果没有发生任何交换,就提前终止排序,因为这意味着数组已经是有序的,不需要再进行后续的遍历。优化后的代码如下:

复制代码
function optimizedBubbleSort($arr) {
    $n = count($arr);
    for ($i = 0; $i < $n - 1; $i++) {
        $swapped = false; // 用来标记是否有交换发生
        for ($j = 0; $j < $n - $i - 1; $j++) {
            if ($arr[$j] > $arr[$j + 1]) {
                // 交换元素
                $temp = $arr[$j];
                $arr[$j] = $arr[$j + 1];
                $arr[$j + 1] = $temp;
                $swapped = true;
            }
        }
        // 如果没有发生交换,提前终止排序
        if (!$swapped) {
            break;
        }
    }
    return $arr;
}
​
// 示例
$arr = [5, 2, 9, 1, 5, 6];
$sortedArr = optimizedBubbleSort($arr);
print_r($sortedArr);
优化后的时间复杂度
  • 最坏时间复杂度:O(n²)(与未优化的算法相同)。

  • 最好时间复杂度:O(n),当数组已经是有序的时,优化版本可以提前终止,避免不必要的遍历。

  • 平均时间复杂度:O(n²)。

总结
  • 冒泡排序是一种简单的排序算法,适用于小规模数据排序。

  • 它的最坏和平均时间复杂度为 O(n²),对于大数据量的排序效率较低。

  • 优化版本通过检测是否有交换发生来提前结束排序,减少不必要的遍历。


2,选择排序

选择排序(Selection Sort)是一种简单的排序算法,它的基本思想是每次从未排序的部分中选择最小(或最大)的元素,交换到已排序部分的末尾,直到所有元素都被排序完成。选择排序的时间复杂度始终为 O(n²),即使在最好的情况下也没有优化。

选择排序的基本思想

  1. 从未排序的部分选择最小(或最大)元素:每次遍历数组,找到最小值并将其放到已排序部分的末尾。

  2. 交换:将找到的最小元素与未排序部分的第一个元素交换。

  3. 重复:继续对剩下的部分进行同样的操作,直到整个数组排序完成。

实例四,选择排序之实现方法
复制代码
function selectionSort($arr) {
    $n = count($arr);  // 获取数组的长度
​
    // 外层循环,控制已排序部分的位置
    for ($i = 0; $i < $n - 1; $i++) {
        // 假设当前 i 位置的元素是最小值
        $minIndex = $i;
        
        // 内层循环,找到未排序部分的最小元素
        for ($j = $i + 1; $j < $n; $j++) {
            if ($arr[$j] < $arr[$minIndex]) {
                // 更新最小元素的索引
                $minIndex = $j;
            }
        }
​
        // 如果最小值不是当前 i 位置的元素,则交换它们
        if ($minIndex != $i) {
            // 交换元素
            $temp = $arr[$i];
            $arr[$i] = $arr[$minIndex];
            $arr[$minIndex] = $temp;
        }
    }
​
    return $arr;
}
​
// 示例使用
$arr = [64, 25, 12, 22, 11];
$sortedArr = selectionSort($arr);
print_r($sortedArr);
代码解释
  1. 外层循环for ($i = 0; $i < $n - 1; $i++),控制已排序部分的位置。

    • 每次外层循环都会选择一个元素,将其放到已排序部分的末尾。
  2. 内层循环for ($j = $i + 1; $j < $n; $j++),遍历剩下的未排序部分,找到最小元素的索引 minIndex

    • 比较 arr[$j] 和当前 arr[$minIndex],如果 arr[$j] 更小,就更新 minIndex
  3. 交换 :如果找到的最小元素不是当前 i 位置的元素,就交换它们。通过临时变量 $temp 进行交换。

  4. 返回排序后的数组:最终,返回排序后的数组。

时间复杂度和空间复杂度
  • 时间复杂度

    • 最坏时间复杂度 :O(n²),每次外层循环都会遍历剩下的元素进行比较,最多需要比较 n(n-1)/2 次。

    • 最佳时间复杂度:O(n²),无论数组是否已经排序,选择排序都要进行相同次数的比较。

    • 平均时间复杂度 :O(n²),大多数情况下,选择排序会进行约 n(n-1)/2 次比较。

  • 空间复杂度:O(1),选择排序是原地排序算法,不需要额外的存储空间,只需要常数级别的空间来进行元素交换。

优缺点
  • 优点

    • 实现简单,代码易懂。

    • 选择排序是一种原地排序算法,空间复杂度为 O(1),不需要额外的内存。

    • 无论数组是否已经排序,选择排序的时间复杂度始终为 O(n²),不会受到数组原始排列的影响。

  • 缺点

    • 时间复杂度较高,尤其是处理大量数据时不够高效。其性能相较于其他高效的排序算法(如快速排序、归并排序等)较差。

    • 不稳定排序(即相等元素的顺序可能会改变)。

总结

选择排序适用于小规模数据集,或者在内存空间非常受限的情况下使用。然而,对于大数据集,效率较低,不推荐使用。对于大部分实际应用,通常选择其他更高效的排序算法,如快速排序、归并排序等


3,快速排序

快速排序(Quick Sort)是一种常见的分治排序算法,它通过选择一个基准元素(pivot),将数组分成两部分:一部分包含所有比基准元素小的元素,另一部分包含所有比基准元素大的元素,然后递归地对这两部分进行排序。快速排序的平均时间复杂度为 O(n log n) ,最坏情况下为 O(n²),但是通过合理的基准选择和优化策略,最坏情况可以得到一定程度的避免。

快速排序的基本思想

  1. 选择基准元素:从数组中选择一个元素作为基准(可以选择第一个元素、最后一个元素、随机选择等)。

  2. 分区操作:将数组分为两部分,一部分比基准小,另一部分比基准大。

  3. 递归排序:对这两部分分别递归进行排序。

实例五,快速排序之实现方法
复制代码
function quickSort($arr) {
    // 基本情况:数组长度为1或0时,已经是排序好的
    if (count($arr) < 2) {
        return $arr;
    }
    
    // 选择基准元素,这里选择数组的第一个元素
    $pivot = $arr[0];
    
    // 分区:将小于基准的元素放到左边,将大于基准的元素放到右边
    $left = [];
    $right = [];
    
    // 从第二个元素开始遍历数组
    for ($i = 1; $i < count($arr); $i++) {
        if ($arr[$i] < $pivot) {
            $left[] = $arr[$i]; // 小于基准的元素
        } else {
            $right[] = $arr[$i]; // 大于基准的元素
        }
    }
    
    // 递归排序左边和右边的部分,并将基准元素放到合适的位置
    return array_merge(quickSort($left), [$pivot], quickSort($right));
}
​
// 示例使用
$arr = [10, 7, 8, 9, 1, 5];
$sortedArr = quickSort($arr);
print_r($sortedArr);
代码解析
  1. 基本情况:如果数组的长度小于 2,直接返回数组,因为已经是排序好的。

  2. 选择基准:我们选择数组的第一个元素作为基准元素。

  3. 分区操作 :通过遍历数组,将比基准小的元素放入 $left 数组,将比基准大的元素放入 $right 数组。

  4. 递归排序 :递归地对 leftright 部分进行排序,然后将它们与基准元素 pivot 合并,得到排序后的数组。

时间复杂度和空间复杂度
  • 最坏时间复杂度:O(n²),发生在基准元素的选择不均匀(例如,当基准元素选择不当时,数组每次只能分成一个非常小的部分)。例如,当数组已经是排序的或者逆序时,每次划分的子数组可能只减少一个元素。

  • 最佳时间复杂度:O(n log n),当每次基准都能将数组大致平分时,快速排序的性能最好。

  • 平均时间复杂度:O(n log n),通常情况下,快速排序的时间复杂度是 O(n log n)。

  • 空间复杂度:O(log n),是由于递归调用所需的栈空间。如果数组非常大,栈空间也可能会达到 O(n)(在最坏情况下,递归的深度会很深),但一般情况下,它是 O(log n)。

快速排序的优化
  1. 选择合适的基准:基准的选择对排序的性能影响很大。常见的优化方式有:

    • 随机选择基准:避免选择数组的第一个元素作为基准,防止数组已经有序或接近有序时出现最坏情况。

    • 三数取中法:选择第一个元素、最后一个元素和中间元素的中值作为基准。

  2. 尾递归优化:在某些实现中,递归深度可以通过尾递归优化来减少栈空间的使用。

  3. 小数组使用插入排序:当子数组的元素数量较小时(如小于 10),可以考虑使用插入排序来代替快速排序,以减少递归调用的开销。

改进版快速排序(随机选择基准)
复制代码
function quickSortOptimized($arr) {
    // 基本情况:数组长度为1或0时,已经是排序好的
    if (count($arr) < 2) {
        return $arr;
    }
​
    // 随机选择基准元素
    $pivotIndex = rand(0, count($arr) - 1);
    $pivot = $arr[$pivotIndex];
    
    // 将基准元素移到数组的第一个位置
    array_splice($arr, $pivotIndex, 1);
    array_unshift($arr, $pivot);
​
    // 分区:将小于基准的元素放到左边,将大于基准的元素放到右边
    $left = [];
    $right = [];
​
    // 从第二个元素开始遍历数组
    for ($i = 1; $i < count($arr); $i++) {
        if ($arr[$i] < $pivot) {
            $left[] = $arr[$i];
        } else {
            $right[] = $arr[$i];
        }
    }
​
    // 递归排序左边和右边的部分,并将基准元素放到合适的位置
    return array_merge(quickSortOptimized($left), [$pivot], quickSortOptimized($right));
}
​
// 示例使用
$arr = [10, 7, 8, 9, 1, 5];
$sortedArr = quickSortOptimized($arr);
print_r($sortedArr);
总结
  • 快速排序是一个高效的排序算法,平均时间复杂度为 O(n log n),适用于大多数排序任务。

  • 它使用了分治法,并通过选择基准元素和递归排序子数组来实现排序。

  • 快速排序虽然时间复杂度较低,但在最坏情况下(基准选择不当时)会退化为 O(n²),因此选择合适的基准元素非常重要。

  • 通过改进选择基准(如随机选择或三数取中法)可以有效避免最坏情况,进一步提高其性能。


4,插入排序

插入排序(Insertion Sort)是一种简单的排序算法,它的基本思想是将数组分为已排序部分和未排序部分,逐个将未排序部分的元素插入到已排序部分的正确位置。插入排序的时间复杂度为 O(n²),适用于数据量较小或者数据部分有序的情况。

插入排序的基本思想

  1. 已排序部分:初始时,假设数组的第一个元素是已排序部分。

  2. 从第二个元素开始:每次从未排序部分选择一个元素,并将其插入到已排序部分的合适位置。

  3. 移动已排序部分的元素:插入元素时,需要将已排序部分的元素移到右边,以腾出插入位置。

实例六,插入排序之实现方法
复制代码
function insertionSort($arr) {
    $n = count($arr);  // 获取数组的长度
​
    // 从第二个元素开始遍历(第一个元素默认是已排序的)
    for ($i = 1; $i < $n; $i++) {
        // 当前元素
        $current = $arr[$i];
        
        // 将已排序部分的元素逐个移到右边,直到找到合适的位置
        $j = $i - 1;
        while ($j >= 0 && $arr[$j] > $current) {
            // 将元素往右移动
            $arr[$j + 1] = $arr[$j];
            $j--;
        }
​
        // 插入当前元素到正确的位置
        $arr[$j + 1] = $current;
    }
​
    return $arr;
}
​
// 示例使用
$arr = [12, 11, 13, 5, 6];
$sortedArr = insertionSort($arr);
print_r($sortedArr);
代码解析
  1. 外层循环:从数组的第二个元素开始遍历(因为第一个元素已经是"已排序"部分)。

    • 在每次迭代中,我们将当前元素($current)与已排序部分进行比较。
  2. 内层循环:将已排序部分中大于当前元素的元素依次向右移动,直到找到合适的位置。

    • 这保证了数组前面部分始终是有序的。
  3. 插入当前元素:找到合适的位置后,将当前元素插入到该位置。

  4. 返回排序后的数组:最终返回排序完成的数组。

时间复杂度和空间复杂度
  • 时间复杂度

    • 最坏时间复杂度:O(n²),发生在数组是逆序时。每次插入元素时都需要将已排序部分的元素逐个移动。

    • 最佳时间复杂度:O(n),发生在数组已经是有序时。每次插入的元素都大于已排序部分的最后一个元素,不需要移动元素。

    • 平均时间复杂度:O(n²),大多数情况下,插入排序需要进行大约 n² 次比较和移动。

  • 空间复杂度:O(1),插入排序是一种原地排序算法,不需要额外的存储空间。

优缺点

优点:

  1. 简单易实现:插入排序的实现非常简单,代码容易理解。

  2. 稳定排序:插入排序是稳定的,即相等元素的相对顺序不会改变。

  3. 适用于小数据量:对于小数据量或基本有序的数组,插入排序非常高效。

  4. 内存占用低:由于是原地排序,插入排序的空间复杂度是 O(1)。

缺点:

  1. 效率较低:时间复杂度为 O(n²),对于大数据集来说效率较低,不适合处理大量数据。

  2. 适用范围有限:插入排序适合小规模数据或数据已经接近有序的情况,数据量大时性能不佳。

应用场景

插入排序最适合用于数据量较小、或者数组部分有序的情况。比如:

  • 小型数据集:例如,数据集的元素数量较少,插入排序的性能不逊色于其他排序算法。

  • 数据接近有序时:例如,当数据集已经接近排序好的状态时,插入排序的效率极高。

改进插入排序:二分查找优化

在插入排序的过程中,可以使用二分查找来找到合适的插入位置,从而减少比较的次数。这样虽然仍然需要移动元素,但插入位置的查找会更快。

二分查找优化的插入排序代码示例:
复制代码
function binaryInsertionSort($arr) {
    $n = count($arr);
​
    // 从第二个元素开始遍历(第一个元素默认是已排序的)
    for ($i = 1; $i < $n; $i++) {
        $current = $arr[$i];
        
        // 使用二分查找找到插入位置
        $left = 0;
        $right = $i - 1;
        while ($left <= $right) {
            $mid = floor(($left + $right) / 2);
            if ($arr[$mid] > $current) {
                $right = $mid - 1;
            } else {
                $left = $mid + 1;
            }
        }
        
        // 将已排序部分的元素往右移腾出空间
        for ($j = $i - 1; $j >= $left; $j--) {
            $arr[$j + 1] = $arr[$j];
        }
        
        // 插入当前元素到合适的位置
        $arr[$left] = $current;
    }
​
    return $arr;
}
​
// 示例使用
$arr = [12, 11, 13, 5, 6];
$sortedArr = binaryInsertionSort($arr);
print_r($sortedArr);

通过二分查找优化后,尽管仍然是 O(n²) 的时间复杂度,但查找插入位置的效率得到了提升。

总结
  • 插入排序是一个简单、直观的排序算法,特别适合小数据集或接近有序的数据。

  • 它具有稳定性、低空间复杂度的特点,适合内存受限的环境。

  • 对于大数据量或逆序数据,插入排序效率较低,通常会选择更高效的排序算法,如快速排序、归并排序等。

相关推荐
BingoGo20 分钟前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack22 分钟前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo1 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack1 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack2 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo2 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack3 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理4 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
feifeigo1234 天前
matlab画图工具
开发语言·matlab
dustcell.4 天前
haproxy七层代理
java·开发语言·前端