前言
各种排序算法不光是面试的常问知识点
也在各种语言底层经常使用,如何在不同的场景下选择最优的排序算法?本文总结各种常用的排序,试图进一步深入理解排序算法
注:下面的描述中n代表数组长度,代码会封装一些在排序算法中常用的比较,交换方法
定义的排序算法抽象类,排序算法需继承该抽象类实现抽象方法
$param2) {
return true;
}
return false;
}
/**
* 元素交换
* @param $a
* @param $i
* @param $j
*/
public static function exchange(&$a, $i, $j)
{
$temp = $a[$i];
$a[$i] = $a[$j];
$a[$j] = $temp;
}
/**
* 打印排序数组
* @param $a
*/
public static function show($a)
{
var_dump($a);
}
}
选择排序
概念
step1:在数组中找到最小的元素
step2:最小的元素和第第一个元素交换
step3:在剩下的数组的中再找最小的元素和第二个元素交换
... 如此往复,直到数组有序
算法分析
- 为了在剩余元素中找到最小的元素,需要都需要扫描剩余元素进行比较,比较次数=(n-1)+(n-2)...+1=n(n-1)/2 ~ n*n/2
- 需要交换n-1次
代码实现
插入排序
概念
比如将8插入到有序数组[1,3,5,7,9]中,8先和9进行比较8<9则继续和7比较,8>7结束比较,将8插入到7后面
算法分析
- 插入排序只会将新元素和之前有序的数组进行比较,若新元素>最后一个元素会之间结束比较,选择排序每次都要遍历剩余数组找到最小元素,所以一般来说插入排序效率要高于选择排序
- 对于一个本来就部分有序的数组,会减少插入排序比较次数,提供插入排序效率
代码实现
0; $j--) {
if (self::compare($array[$j - 1], $array[$j])) {
self::exchange($array, $j - 1, $j);
}
}
}
return $array;
}
}
希尔排序
概念
对于大规模乱序数组插入排序很慢,因为它只会交换相邻元素,因此元素需要从数组的一端一点点的移动到另一端,如果最小的元素的在数组的尽头,把它移动到正确的位置就需要n-1次移动
希尔排序交换不相邻的元素对数组进行局部排序,提高了插入排序效率
算法分析
- 希尔排序使任意间隔h的元素就使有序的,这样的数组也被成为h有序数组,如果h很大的话,就可以把元素移动到很远的地方,节省了移动次数
- 如果选择h?算法的性能不仅取决h,还取决h之间的数组性质,这里不再深究
- 希尔排序比快速排序快很多,数组越大优势越大
代码实现
= 1) {
for ($i = $h; $i < $count; $i++) {
for ($j = $i; $j >= $h && self::compare($array[$j - $h], $array[$j]); $j -= $h) {
self::exchange($array, $j, $j - $h);
}
}
$h = $h / 3;
}
return $array;
}
}
归并排序
概念
将一个数组进行排序,可以将它分成2个数组,分别进行排序,然后将2个数组结果归并起来,这也是一种分治思想
算法分析
- 自顶向下的归并排序:将大数组分割成小数组,小数组一层层merge到大数组
- 自底向上的归并排序:先归并完最后一层的小数组,再归并倒数第二层的小数组。。。
代码实现
= $max) return;
$mid = floor(($max + $min) / 2);
self::sortLoop($a, $min, $mid);
self::sortLoop($a, $mid + 1, $max);
self::merge($a, $min, $mid, $max);//归并
}
/**
* 将两个数组归并
* @param $a
* @param $min
* @param $mid
* @param $max
*/
public static function merge(&$a, $min, $mid, $max)
{
// 将[$min,$mid],[$mid+1,$max]归并
$i = $min;
$j = $mid + 1;
// 临时数组
$temp = $a;
for ($k = $min; $k <= $max; $k++) {
if ($i > $mid) $a[$k] = $temp[$j++]; // 左边用尽,取右边数据
elseif ($j > $max) $a[$k] = $temp[$i++];//右边用尽,取左边数据
elseif (self::compare($temp[$i], $temp[$j])) $a[$k] = $temp[$j++];//左边元素数据大于右边元素数据,取右边
else $a[$k] = $temp[$i++];
}
}
}
快速排序
概念
快速排序同样用了分治思想,将数组切分成子数组,两部分独立排序
基准元素如何选择,简单的可以将数组第一个元素选为基准元素
但如何遇到遇到一个已经排序好的逆序数组[5,4,3,1],选择第一个元素,将会导致每次排序不会将数组分成一半一半,每次排序只能将确定基准元素位置,复杂度变成o(n*n)
解决方法:随机选择一个基准元素
算法分析
快速排序的优化方案
- 由于快递排序中小数组也会递归进行排序,所以对于小数组来说,插入排序更快,修改代码
if ($min >= $max) return;
为if ($min + M >= $max) {Insert.sort($a,$min,$max);return;}
,转换参数M和最佳值和系统相关,但是5-15之间的任意值大多数情况下都令人满意。 - 熵(shang)最优的排序,一个元素全部重复的子数组就不需要排序了,而快速排序依然会将它切分成子数组进行排序。当数组中有大量重复元素的数据,导致效率降低。简单的方案是将数组切分成三部分,分别对于>、 =、 <切分元素的数组元素,将=切分元素的元素归位。
代码实现
= $max) return;
$mid = self::getMid($a, $min, $max);
self::sortLoop($a, $min, $mid - 1);
self::sortLoop($a, $mid + 1, $max);
}
/**
* 指针交换法
* @param $a
* @param $min
* @param $max
* @return int
*/
public static function getMid(&$a, $min, $max)
{
$i = $min;
$j = $max;
$v = $a[$min];
while ($i != $j) {
while ($i < $j && $a[$j] > $v) {
$j--;
}
while ($i < $j && $a[$i] <= $v) {
$i++;
}
if ($i < $j) {
self::exchange($a, $i, $j);
}
}
self::exchange($a, $min, $j);
return $j;
}
}
= $max) return;
$lt = $min;
$gt = $max;
$i = $min + 1;
$v = $a[$min];
while ($i <= $gt) {
$res = $a[$i] - $v;
if ($res > 0) {
// 若 $i元素 > 基准元素 则将$i和最后元素交换
self::exchange($a, $i, $gt--);
} elseif ($res < 0) {
// 若 $i元素 < 基准元素 $i和最左元素交换位置
self::exchange($a, $i++, $lt++);
} else {
$i++;
}
}
self::sortLoop($a, $min, $i - 1);
self::sortLoop($a, $gt + 1, $max);
}
}
堆排序
概念
算法分析
代码实现
总结
参考:图灵设计层数,算法(第四版)