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²) 的时间复杂度,但查找插入位置的效率得到了提升。

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

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

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

相关推荐
qincjun几秒前
Qt仿音乐播放器:动画类
开发语言·qt
L.S.V.9 分钟前
Java 溯本求源之基础(三十)——封装,继承与多态
java·开发语言
游客52017 分钟前
设计模式-创建型-工厂方法模式
开发语言·python·设计模式·工厂方法模式
m0_7482349020 分钟前
Hmsc包开展群落数据联合物种分布模型分析通用流程(Pipelines)
开发语言·python
WongKyunban29 分钟前
bash shell脚本while循环
开发语言·bash
想成为高手49932 分钟前
华为仓颉编程语言的函数与结构类型分析
开发语言·华为
神经美学_茂森36 分钟前
【自由能系列(初级),论文解读】神经网络中,熵代表系统的不确定性,自由能则引导系统向更低能量的状态演化,而动力学则描述了系统状态随时间的变化。
人工智能·神经网络·php
EdwardYange37 分钟前
LeetCode 83 :删除排链表中的重复元素
数据结构·算法·leetcode·链表
lly2024061 小时前
Ruby 数据库访问 - DBI 教程
开发语言
m0_748254091 小时前
100天精通Python(爬虫篇)——第113天:爬虫基础模块之urllib详细教程大全
开发语言·爬虫·python