一,数组查找
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)是一种高效的查找算法,通常用于在一个已经排序的数组中查找某个元素。它通过不断地将查找范围一分为二,从而减少了查找的次数。
二分查找的基本原理
-
初始范围:开始时,查找范围是整个数组。
-
中间元素
:计算数组中间位置的元素,将其与目标值进行比较。
-
如果目标值等于中间元素,返回该元素的位置。
-
如果目标值小于中间元素,则目标值只能出现在中间元素左边的子数组中,更新查找范围为左半部分。
-
如果目标值大于中间元素,则目标值只能出现在中间元素右边的子数组中,更新查找范围为右半部分。
-
-
重复:继续在更新后的子数组中进行二分查找,直到找到目标值或者查找范围为空。
实例二,二分查找法之实现方法
<?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 + 1
和 end = $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. 代码改进
根据上述分析,可以对代码进行如下修改:
-
排序:对数组进行排序。
-
修正递归调用的
end
参数。 -
改进
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)是一种简单的排序算法,基本思想是通过重复遍历待排序的数组,每次比较相邻的元素,如果它们的顺序错误就交换它们的位置。每一轮遍历都能确保一个元素被排到它应该在的位置。这个过程就像水泡在水中逐渐向上冒一样,因此得名"冒泡排序"。
冒泡排序的基本思想
-
从数组的开始位置开始,比较相邻的元素。
-
如果前面的元素比后面的元素大,则交换它们的位置。
-
每一轮遍历都将最大的元素"冒泡"到数组的末尾。
-
重复上述过程,直到整个数组排序完成。
实例三,冒泡排序之实现方法
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);
代码解析
-
外层循环 :
for ($i = 0; $i < $n - 1; $i++)
,用于控制排序的轮数,每一轮都会将一个最大元素"冒泡"到数组的末尾。 -
内层循环 :
for ($j = 0; $j < $n - $i - 1; $j++)
,用于在每一轮中进行相邻元素的比较和交换。注意,内层循环的次数随着外层循环的进行而减少,因为每一轮结束后,数组末尾的元素已经排好序。 -
交换 :如果当前元素
$arr[$j]
大于下一个元素$arr[$j + 1]
,就交换它们的位置。 -
返回排序结果:排序完成后返回排序后的数组。
冒泡排序的时间复杂度
-
最坏时间复杂度: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²),即使在最好的情况下也没有优化。
选择排序的基本思想
-
从未排序的部分选择最小(或最大)元素:每次遍历数组,找到最小值并将其放到已排序部分的末尾。
-
交换:将找到的最小元素与未排序部分的第一个元素交换。
-
重复:继续对剩下的部分进行同样的操作,直到整个数组排序完成。
实例四,选择排序之实现方法
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);
代码解释
-
外层循环 :
for ($i = 0; $i < $n - 1; $i++)
,控制已排序部分的位置。- 每次外层循环都会选择一个元素,将其放到已排序部分的末尾。
-
内层循环 :
for ($j = $i + 1; $j < $n; $j++)
,遍历剩下的未排序部分,找到最小元素的索引minIndex
。- 比较
arr[$j]
和当前arr[$minIndex]
,如果arr[$j]
更小,就更新minIndex
。
- 比较
-
交换 :如果找到的最小元素不是当前
i
位置的元素,就交换它们。通过临时变量$temp
进行交换。 -
返回排序后的数组:最终,返回排序后的数组。
时间复杂度和空间复杂度
-
时间复杂度
-
最坏时间复杂度 :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²),但是通过合理的基准选择和优化策略,最坏情况可以得到一定程度的避免。
快速排序的基本思想
-
选择基准元素:从数组中选择一个元素作为基准(可以选择第一个元素、最后一个元素、随机选择等)。
-
分区操作:将数组分为两部分,一部分比基准小,另一部分比基准大。
-
递归排序:对这两部分分别递归进行排序。
实例五,快速排序之实现方法
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);
代码解析
-
基本情况:如果数组的长度小于 2,直接返回数组,因为已经是排序好的。
-
选择基准:我们选择数组的第一个元素作为基准元素。
-
分区操作 :通过遍历数组,将比基准小的元素放入
$left
数组,将比基准大的元素放入$right
数组。 -
递归排序 :递归地对
left
和right
部分进行排序,然后将它们与基准元素pivot
合并,得到排序后的数组。
时间复杂度和空间复杂度
-
最坏时间复杂度:O(n²),发生在基准元素的选择不均匀(例如,当基准元素选择不当时,数组每次只能分成一个非常小的部分)。例如,当数组已经是排序的或者逆序时,每次划分的子数组可能只减少一个元素。
-
最佳时间复杂度:O(n log n),当每次基准都能将数组大致平分时,快速排序的性能最好。
-
平均时间复杂度:O(n log n),通常情况下,快速排序的时间复杂度是 O(n log n)。
-
空间复杂度:O(log n),是由于递归调用所需的栈空间。如果数组非常大,栈空间也可能会达到 O(n)(在最坏情况下,递归的深度会很深),但一般情况下,它是 O(log n)。
快速排序的优化
-
选择合适的基准:基准的选择对排序的性能影响很大。常见的优化方式有:
-
随机选择基准:避免选择数组的第一个元素作为基准,防止数组已经有序或接近有序时出现最坏情况。
-
三数取中法:选择第一个元素、最后一个元素和中间元素的中值作为基准。
-
-
尾递归优化:在某些实现中,递归深度可以通过尾递归优化来减少栈空间的使用。
-
小数组使用插入排序:当子数组的元素数量较小时(如小于 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²),适用于数据量较小或者数据部分有序的情况。
插入排序的基本思想
-
已排序部分:初始时,假设数组的第一个元素是已排序部分。
-
从第二个元素开始:每次从未排序部分选择一个元素,并将其插入到已排序部分的合适位置。
-
移动已排序部分的元素:插入元素时,需要将已排序部分的元素移到右边,以腾出插入位置。
实例六,插入排序之实现方法
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);
代码解析
-
外层循环:从数组的第二个元素开始遍历(因为第一个元素已经是"已排序"部分)。
- 在每次迭代中,我们将当前元素(
$current
)与已排序部分进行比较。
- 在每次迭代中,我们将当前元素(
-
内层循环:将已排序部分中大于当前元素的元素依次向右移动,直到找到合适的位置。
- 这保证了数组前面部分始终是有序的。
-
插入当前元素:找到合适的位置后,将当前元素插入到该位置。
-
返回排序后的数组:最终返回排序完成的数组。
时间复杂度和空间复杂度
-
时间复杂度
-
最坏时间复杂度:O(n²),发生在数组是逆序时。每次插入元素时都需要将已排序部分的元素逐个移动。
-
最佳时间复杂度:O(n),发生在数组已经是有序时。每次插入的元素都大于已排序部分的最后一个元素,不需要移动元素。
-
平均时间复杂度:O(n²),大多数情况下,插入排序需要进行大约 n² 次比较和移动。
-
-
空间复杂度:O(1),插入排序是一种原地排序算法,不需要额外的存储空间。
优缺点
优点:
-
简单易实现:插入排序的实现非常简单,代码容易理解。
-
稳定排序:插入排序是稳定的,即相等元素的相对顺序不会改变。
-
适用于小数据量:对于小数据量或基本有序的数组,插入排序非常高效。
-
内存占用低:由于是原地排序,插入排序的空间复杂度是 O(1)。
缺点:
-
效率较低:时间复杂度为 O(n²),对于大数据集来说效率较低,不适合处理大量数据。
-
适用范围有限:插入排序适合小规模数据或数据已经接近有序的情况,数据量大时性能不佳。
应用场景
插入排序最适合用于数据量较小、或者数组部分有序的情况。比如:
-
小型数据集:例如,数据集的元素数量较少,插入排序的性能不逊色于其他排序算法。
-
数据接近有序时:例如,当数据集已经接近排序好的状态时,插入排序的效率极高。
改进插入排序:二分查找优化
在插入排序的过程中,可以使用二分查找来找到合适的插入位置,从而减少比较的次数。这样虽然仍然需要移动元素,但插入位置的查找会更快。
二分查找优化的插入排序代码示例:
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²) 的时间复杂度,但查找插入位置的效率得到了提升。
总结
-
插入排序是一个简单、直观的排序算法,特别适合小数据集或接近有序的数据。
-
它具有稳定性、低空间复杂度的特点,适合内存受限的环境。
-
对于大数据量或逆序数据,插入排序效率较低,通常会选择更高效的排序算法,如快速排序、归并排序等。