前言
各种排序算法不光是面试的常问知识点
也在各种语言底层经常使用,如何在不同的场景下选择最优的排序算法?本文总结各种常用的排序,试图进一步深入理解排序算法
注:下面的描述中n代表数组长度,代码会封装一些在排序算法中常用的比较,交换方法
定义的排序算法抽象类,排序算法需继承该抽象类实现抽象方法
<?php
namespace SortAlgorithm;
/**
* 抽象排序类
* Class SortAlg
*/
abstract class SortAlg
{
/**
* 抽象方法-排序的具体实现
* @param $array
* @return mixed
*/
abstract static function sort($array);
/**
* 元素比较
* @param $param1
* @param $param2
* @return bool
*/
public static function compare($param1, $param2)
{
if ($param1 > $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次
代码实现
<?php
namespace SortAlgorithm;
/**
* 插入排序
* Class Select
* @package SortAlgorithm
*/
class Select extends SortAlg
{
/**
* 核心思想
* 1.找到最小元素和第一个元素交换
* 2.找到第二小的元素和第二个元素交换,以此类推。。。
* 需交换n-1 次
* 需比较(n-1)+(n-2)...+2+1=n(n-1)/2 ~ n*n/2 次
* @param $array
* @return mixed
*/
public static function sort($array)
{
$count = count($array);
for ($i = 0; $i < $count; $i++) {
$min = $i;
for ($j = $i; $j < $count; $j++) {
if (self::compare($array[$i], $array[$j])) {
$min = $j;
}
}
self::exchange($array, $i, $min);
}
return $array;
}
}
插入排序
概念
比如将8插入到有序数组[1,3,5,7,9]中,8先和9进行比较8<9则继续和7比较,8>7结束比较,将8插入到7后面
算法分析
- 插入排序只会将新元素和之前有序的数组进行比较,若新元素>最后一个元素会之间结束比较,选择排序每次都要遍历剩余数组找到最小元素,所以一般来说插入排序效率要高于选择排序
- 对于一个本来就部分有序的数组,会减少插入排序比较次数,提供插入排序效率
代码实现
<?php
namespace SortAlgorithm;
/**
* 插入排序
* Class Insert
* @package SortAlgorithm
*/
class Insert extends SortAlg
{
/**
* 核销思想
* 假设数组是有序的,新增的元素和有序的数组进行比较
* 比较次数:n*n/4
* 移动次数:n*n/4
* 对于一个部分的有序的数组,使用插入排序效率很高,移动次数 < n次
* @param $array
* @return mixed
*/
public static function sort($array)
{
$count = count($array);
for ($i = 1; $i < $count; $i++) {
for ($j = $i; $j > 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之间的数组性质,这里不再深究
- 希尔排序比快速排序快很多,数组越大优势越大
代码实现
<?php
namespace SortAlgorithm;
/**
* 希尔排序
* Class Shell
* @package SortAlgorithm
*/
class Shell extends SortAlg
{
/**
* 插入排序的优化方案
* 1.将数组拆成m个子数组,将子数组进行插入排序,实现数组的部分有序
* @param $array
* @return mixed
*/
public static function sort($array)
{
$count = count($array);
$h = 1;
// 间隔h的选择
while ($h < $count / 3) {
$h = $h * 3 + 1;
}
while ($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到大数组
- 自底向上的归并排序:先归并完最后一层的小数组,再归并倒数第二层的小数组。。。
代码实现
<?php
namespace SortAlgorithm;
/**
* 归并算法-自顶向下
* Class MergeToDown
* @package SortAlgorithm
*/
class MergeToDown extends SortAlg
{
/**
* @param $array
* @return mixed
*/
public static function sort($array)
{
self::sortLoop($array, 0, count($array)-1);
return $array;
}
/**
* @param $a
* @param $min
* @param $max
*/
public static function sortLoop(&$a, $min, $max)
{
if ($min >= $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)最优的排序,一个元素全部重复的子数组就不需要排序了,而快速排序依然会将它切分成子数组进行排序。当数组中有大量重复元素的数据,导致效率降低。简单的方案是将数组切分成三部分,分别对于>、 =、 <切分元素的数组元素,将=切分元素的元素归位。
代码实现
<?php
namespace SortAlgorithm;
/**
* 快速排序
* Class Quick
* @package SortAlgorithm
*/
class Quick extends SortAlg
{
/**
* @param $array
* @return mixed
*/
public static function sort($array)
{
shuffle($array);//打乱数组,消除对输入的影响
self::sortLoop($array, 0, count($array) - 1);
return $array;
}
/**
* @param $a
* @param $min
* @param $max
*/
public static function sortLoop(&$a, $min, $max)
{
if ($min >= $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;
}
}
<?php
namespace SortAlgorithm;
/**
* 快速排序优化之三向切分
* Class Quick
* @package SortAlgorithm
*/
class Quick3Way extends SortAlg
{
/**
* 三向切分的快速排序
* @param $array
* @return mixed
*/
public static function sort($array)
{
shuffle($array);//打乱数组,消除对输入的影响
self::sortLoop($array, 0, count($array) - 1);
return $array;
}
/**
* @param $a
* @param $min
* @param $max
*/
public static function sortLoop(&$a, $min, $max)
{
if ($min >= $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);
}
}
堆排序
概念
算法分析
代码实现
总结
参考:图灵设计层数,算法(第四版)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。