数据结构和算法分析PHP经验分享PHP开发

【比较类排序算法】冒泡排序、选择排序、快速排序、插入排序、希尔排

2022-01-26  本文已影响0人  Bryanz

常见的经典比较类排序算法有冒泡排序选择排序快速排序插入排序希尔排序。这几种排序中快速排序和希尔排序的平均时间复杂度都突破了O(n^2),主要得益于这两种排序每轮排序都对下一轮产生影响,而其余的排序如选择排序每轮排序只是单纯从数组中找出最值,而没有对下一次的数组排序做出贡献。
<输入的最好方式就是输出>,本着学习的态度表达一下自己浅显的理解。下面主要介绍每种算法的中心思想,具体的代码实现(PHP),并分析对应的时间复杂度和空间复杂度。

1.冒泡排序

以升序为例(后面同理),冒泡排序的思路是:相邻两个元素进行比较实现左小右大,交换完一轮得到最大值位于最后一位,按这个方式交换n-1轮(n为元素总数,后面同理)即完成排序。

代码如下:

function bubbleSort(array $arr)
{
    $len = count($arr);
    for ($i = 0; $i < $len - 1; $i++) {
        for ($k = 0; $k < $len - 1 - $i; $k++) {
            //交换位置条件
            if ($arr[$k] > $arr[$k + 1]) {
                $tmp = $arr[$k];
                $arr[$k] = $arr[$k + 1];
                $arr[$k + 1] = $tmp;
            }
        }
    }
    return $arr;
}

记忆关键词: 左右交换、每轮最值、n-1轮
性能分析:时间复杂度O(n^2),空间复杂度O(1)

2.选择排序

选择排序的思路是:从左边第1个元素开始,与右边剩下的数组元素进行比较,每一轮得到最小值位于左侧,总共比较n-1轮完成排序。

代码如下:

function selectSort(array $arr)
{
    $len = count($arr);
    for ($i = 0; $i < $len - 1; $i++) {
        $min = $arr[$i];//最小值
        $minIndex = $i;//最小值下标
        for ($k = $i + 1; $k < $len; $k++) {
            if ($min > $arr[$k]) {
                $min = $arr[$k];
                $minIndex = $k;
            }
        }
        //比较完一轮后,交换左侧位置的最小值
        $tmp = $arr[$i];
        $arr[$i] = $arr[$minIndex];
        $arr[$minIndex] = $tmp;
    }
    return $arr;
}

记忆关键词: 左元素、右数组、左侧累加最值
性能分析:时间复杂度O(n^2),空间复杂度O(1)

3.快速排序

快速排序的核心思想是递归和二分,思路是:先将待排数组分为左右分区,并获得中间基准,使得左分区元素均小于中间基准,右分区元素均大于中间基准。同样地,左右两分区按照相同的方式,递归划分各自的左右分区,最后完成排序。
快速排序的关键在于中间基准的确定,方法是:取数组左边第一位作为初始基准,把它与右边剩下的所有元素比较,依次将小于该基准的元素进行替换并记录位置,最后把获取到的位置元素与初始基准元素交换,完成左右分区,返回该中间基准位置。

代码如下:

function quickSort(array $arr, $left = null, $right = null)
{
    $len = count($arr);
    //初始化左右下标位置
    $left = $left ?? 0;
    $right = $right ?? $len - 1;

    //递归条件
    if ($left < $right) {
        //获取中心基准
        $partitionIndex = getPartitionIndex($arr, $left, $right);
        //左分区递归
        $arr = quickSort($arr, $left, $partitionIndex - 1);
        //右分区递归
        $arr = quickSort($arr, $partitionIndex + 1, $right);
    }

    return $arr;
}

/**
 * 将数组进行左右分区,并返回中心基准位置
 * @param array $arr
 * @param int $left
 * @param int $right
 * @return int
 */
function getPartitionIndex(array &$arr, int $left, int $right)
{
    $pivot = $left;//初始基准
    $index = $left + 1;//初始化游标位置,中心基准交换位置的下一位
    for ($i = $index; $i <= $right; $i++) {
        //将元素与初始基准比较
        if ($arr[$i] < $arr[$pivot]) {
            //小于基准,与游标元素交换
            $tmp = $arr[$i];
            $arr[$i] = $arr[$index];
            $arr[$index++] = $tmp;//移动游标
        }
    }

    //比较结束获得中心基准位置,与初始基准交换
    $resultIndex = $index - 1;
    $tmp = $arr[$pivot];
    $arr[$pivot] = $arr[$resultIndex];
    $arr[$resultIndex] = $tmp;
    return $resultIndex;
}

记忆关键词: 二分、递归、左右分区,中间基准
性能分析:时间复杂度O(nlog2n),因为利用二分递归进行排序,时间复杂度突破n^2;空间复杂度O(log2n),与递归次数相关。

4.插入排序

插入排序的思路是:左边先构建有序数组(初始为左边第一个元素),右边元素从其下一位开始与其比较,若大于其末位元素则直接在末尾插入,否则继续往左遍历比较,确认插入位置,最后交换插入,构建新的有序数组。右边元素依次按照该方式插入有序数组,完成排序。

代码如下:

function insertSort(array $arr)
{
    $len = count($arr);
    for ($i = 1; $i < $len; $i++) {
        $index = $i - 1;//左边数组末位元素
        $current = $arr[$i];//右边第一个元素
        //当前值遍历左边数组,进行比较
        while ($index >= 0 && $current < $arr[$index]) {
            $arr[$index + 1] = $arr[$index];//左边数组扩大,最大值右移
            $index--;//左边数组比较位往前移
        }
        //插入左边数组结果位置
        $arr[$index + 1] = $current;
    }
    return $arr;
}

记忆关键词: 左有序数组、右元素遍历、右移插入
性能分析:时间复杂度O(n^2),由代码可看出右移次数越少,右元素扫描遍历的次数就越少(由此引出插入排序的改良版希尔排序),最好的时间复杂度是O(n); 空间复杂度O(1)。

5.希尔排序

希尔排序是插入排序的改良版,也称缩小增量排序。思路是:将待排数组,按照指定的增量进行分组,每组分别进行插入排序,由于每个分组都经过排序,从而导致整个数组宏观上相对有序。最后将整个数组再进行一次插入排序。因为前面分组排序对最后一次排序产生了积极影响,所以整体的排序速度得到提升。

代码如下:

function shellSort(array $arr)
{
    $len = count($arr);
    //增量确定,这里取数组长度一半;
    //增量gap即为分组数,每趟下来分组数减半,直到分组数为1进行最后一次插入排序
    for ($gap = intval($len / 2); $gap > 0; $gap = intval($gap / 2)) {
        //对gap个数组分别进行插入排序
        for ($i = $gap; $i < $len; $i++) {
            $current = $arr[$i];//当前比较值
            $index = $i;//游标位置
            //当前值与上一个值比较(因为按照gap分组,所以分组内上一个值为index-gap)
            while ($index - $gap >= 0 && $current < $arr[$index - $gap]) {
                $arr[$index] = $arr[$index - $gap];
                $index = $index - $gap;
            }
            $arr[$index] = $current;
        }
    }
    return $arr;
}

记忆关键词: 确定增量、分组插入排序、缩小增量
性能分析:时间复杂度:O(nlogn)~O(n^2), 空间复杂度O(1)

非比较类排序算法传送门:https://www.jianshu.com/p/8de11b38ff18

上一篇下一篇

猜你喜欢

热点阅读