PHP常见排序算法学习

题记:

  • 常见的排序算法有:冒泡排序法,快速排序法,选择排序法,插入排序法,此处作为自己最近面试准备进行的学习笔记,同时也希望能帮到你。
  • 需求:将一个有多个数字的数组进行从小到大的排序.

排序算法

【一】.冒泡排序
  • 思路分析:
    想象一个大水池里有N多还未排好的序列的氢气球,较大的先冒出来,然后依次是较小的往上冒。即,每次比较相邻的两个数,小的在前大的在后,否则进行位置互换。
冒泡排序-动态效果图
  • 代码实现:(举例几种写法,注意循环体的判断条件)建议使用第一、二种。
    /**
     * 交换方法
     * @param array $arr 目标数组
     * @param $a 索引a
     * @param $b 索引b
     * @param bool $flag 交换标志
     * @return bool
     */
    function swap(array &$arr,$a,$b,$flag = false){
        // 遍历i后面的元素,只要该元素小于当前元素,就把较小的往前冒泡
        if($arr[$a] > $arr[$b]){
            $temp = $arr[$a];
            $arr[$a] = $arr[$b];
            $arr[$b] = $temp;
            $flag = true;
        }
        return $flag;
    }
    /**
     * 第一种写法
     * @param $arr 所要排序的数组
     * @return mixed 返回的数组
     */
    function bubbleSort($arr) {
        $len = count($arr);
        if ($len <= 1) {return $arr;}
        //该层循环控制 需要冒泡的轮数
        for ($i = 0; $i < $len-1; $i++) {
            //该层循环用来控制每轮 冒出一个数 需要比较的次数
            for ($j = $i + 1; $j < $len; $j++) {
                // 或者 $this->swap($arr,$j,$j+1);
                $this->swap($arr,$i,$j);
            }
        }
        return $arr;
    }
    //第二种写法
    public function BubbleSort2($arr){
        $len = count($arr);
        if ($len <= 1) {return $arr;}
        for ($i = 0;$i < $len-1;$i++){
            //TODO 本趟排序开始前,交换标志应为假
            $flag = false;
            for ($j = 0;$j <= $len-2;$j++){
                $flag = $this->swap($arr,$j,$j+1,$flag);
            }
            if(!$flag) return $arr;
        }
        return $arr;
    }
    //第三种写法
    function BubbleSort3(array &$arr){
        $len = count($arr);
        if ($len <= 1) {return $arr;}
        for($i = 0;$i < $len-1;$i++){
            //从后往前逐层上浮小的元素 $j >= 0
            for($j = $len - 2;$j >= $i ;$j --){
                $this->swap($arr,$j,$j+1);
            }
        }
        return $arr;
    }
    //第四种写法
    function bubbleSort4($arr)
    {
        $len = count($arr);
        if ($len <= 1) {return $arr;}
        for($i = 0;$i < $len-1;$i++) {
            for($j = 0;$j < $len-$i-1;$j++) {
                $this->swap($arr,$j,$j+1);
            }
        }
        return $arr;
    }
  • 小结:
    • 时间复杂度:O(n^2)
    • 补充:可使用PHP内置函数 sort()rsort().
    • 上述函数对索引数组按照键值进行排序,为 array 中的单元赋予新的键名,这将删除原有的键名而不仅是重新排序。如果成功则返回 TRUE,否则返回 FALSE
【二】.选择排序
  • 思路分析:
    每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完
选择排序-动态效果图
  • 代码实现
    /*
    * @param 选择排序法
    * 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完
    * */
    function selectSort($arr){
        //双重循环完成,外层控制轮数,内层控制比较次数
        $len = count($arr);
        if ($len <= 1) {return $arr;}
        for ($i = 0; $i < $len-1; $i++) {
            $minIndex = $i;
            // 找出i后面最小的元素与当前元素交换
            for ($j = $i + 1; $j < $len; $j++) {
                if ($arr[$minIndex] > $arr[$j]){
                    $minIndex = $j;
                }
            }
            if ($minIndex != $i) {
                $temp = $arr[$i];
                $arr[$i] = $arr[$minIndex];
                $arr[$minIndex] = $temp;
            }
        }
        return $arr;
    }
  • 小结:
    • 时间复杂度:O(n^2)
    • 不稳定的排序方法(比如序列[5, 5, 3]第一次就将第一个[5]与[3]交换,导致第一个5挪动到第二个5后面)。
    • 在一趟选择,如果一个元素比当前元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了
    • 最好情况是,已经有序,交换0次;最坏情况交换n-1次,逆序交换n/2次。交换次数比冒泡排序少多了,由于交换所需CPU时间比比较所需的CPU时间多,n值较小时,选择排序比冒泡排序快
【三】.插入排序
  • 思路分析:
    • 每步将一个待排序的纪录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。(从而得到一个新的、个数加一的有序数据)
  • 描述:
    • ⒈ 从第一个元素开始,该元素可以认为已经被排序
    • ⒉ 取出下一个元素,在已经排序的元素序列中从后向前扫描
    • ⒊ 如果该元素(已排序)大于新元素,将该元素移到下一位置
    • ⒋ 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
    • ⒌ 将新元素插入到下一位置中
    • ⒍ 重复步骤 2~5
插入排序-动态效果图
  • 代码实现
    • 此处提供两种写法,主要是循环的写法稍有不同,可作参考.
    /*
    * 插入排序法
    * 每步将一个待排序的记录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。
    * */
    function insertSort($arr){
        $len = count($arr);
        if ($len <= 1) {return $arr;}
        //先默认$array[0],已经有序,是有序表
        for($i = 1;$i < $len;$i++){
            if ($arr[$i] < $arr[$i-1]){
                $insertVal = $arr[$i]; //$insertVal是准备插入的数
                $insertIndex = $i - 1; //有序表中准备比较的数的下标
                while($insertIndex >= 0 && $insertVal < $arr[$insertIndex]){
                    $arr[$insertIndex + 1] = $arr[$insertIndex]; //将数组往后挪
                    $insertIndex--; //将下标往前挪,准备与前一个进行比较
                }
                if($insertIndex + 1 !== $i){
                    $arr[$insertIndex + 1] = $insertVal;
                }
            }
        }
        return $arr;
    }
    function insertSort2($arr){
        $len = count($arr);
        if ($len <= 1) {return $arr;}
        //先默认$array[0],已经有序,是有序表
        for($i = 1;$i < $len;$i++){
            if ($arr[$i] < $arr[$i-1]){
                $insertVal = $arr[$i]; //$insertVal是准备插入的数
                //$j 有序表中准备比较的数的下标
                //$j-- 将下标往前挪,准备与前一个进行比较
                for ($j = $i-1;$j >= 0 && $insertVal < $arr[$j];$j--){
                    $arr[$j+1]= $arr[$j];//将数组往后挪
                }
                $arr[$j + 1] = $insertVal;
            }
        }
        return $arr;
    }
  • 小结:
    • 时间复杂度:O(n^2)
    • 空间复杂度:O(1) (用于记录需要插入的数据)
    • 稳定的排序方法
    • 算法适用于少量数据的排序
    • 如果比较操作的代价比交换操作大的话,可以采用二分查找法来减少比较操作的数目。该算法可以认为是插入排序的一个变种,称为二分查找排序。
【四】.快速排序
  • 思路分析:
    • 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,
    • 然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
快速排序-动态效果图
  • 代码实现
    • 注:网上多数为quick_sort2()这类的写法,感觉并非原算法的描述,建议可做参考.
    • 或许代码quick_sort()有所欠缺,并未发现能有较快的排序效果,尴尬了.
    /**
     * @param $arr 目标数组
     * @param int $l 左起坐标
     * @param $r 右起坐标 初始化传入数组时,$r = count($arr)-1
     * @return mixed
     */
    public  function quick_sort(&$arr, $l=0, $r)
    {
        $length = count($arr);
        //先判断是否需要继续进行 递归出口:数组长度为1,直接返回数组
        if(!is_array($arr)||$length <= 1) {return $arr;}
        if ($l < $r)
        {
            $i = $l;
            $j = $r;
            $baseVal = $arr[$l];
            while ($i < $j)
            {
                // 从右向左找第一个小于$baseVal的数
                while($i < $j && $arr[$j] >= $baseVal)
                    $j--;
                if($i < $j)
                    $arr[$i++] = $arr[$j];
                // 从左向右找第一个大于等于$baseVal的数
                while($i < $j && $arr[$i] < $baseVal)
                    $i++;
                if($i < $j)
                    $arr[$j--] = $arr[$i];
            }
            $arr[$i] = $baseVal;
            $this->quick_sort($arr, $l, $i - 1); // 递归调用
            $this->quick_sort($arr, $i + 1, $r);
            return $arr;
        }
    }
    /*
    * 快速排序法
    * */
    public function quick_sort2($arr) {
        $length = count($arr);
        //先判断是否需要继续进行 递归出口:数组长度为1,直接返回数组
        if(!is_array($arr)||$length <= 1) {return $arr;}
        //选择第一个元素作为基准
        $baseValue = $arr[0];
        //遍历除了标尺外的所有元素,按照大小关系放入两个数组内
        //初始化两个数组
        $leftArr = array();  //小于基准的
        $rightArr = array();  //大于基准的
        //使用for循环进行遍历,把选定的基准当做比较的对象
        for($i = 1; $i<$length; $i++) {
            if( $arr[$i] < $baseValue) {
                //放入左边数组
                $leftArr[] = $arr[$i];
            } else {
                //放入右边数组
                $rightArr[] = $arr[$i];
            }
        }
        //再分别对左边和右边的数组进行相同的排序处理方式递归调用这个函数
        $leftArr = $this->quick_sort2($leftArr);
        $rightArr = $this->quick_sort2($rightArr);
        //合并 左边 标尺 右边, 注意:array($baseValue),关联着重复数据
        return array_merge($leftArr, array($baseValue), $rightArr);
    }
  • 小结:
  • 既不浪费空间又可以快一点的排序算法
  • 最差时间复杂度O(N^2),平均时间复杂度为O(NlogN)
  • 推荐文章-坐在马桶上看算法:快速排序(注:其中的算法逻辑并非标准逻辑,建议阅读底部的评论,可做参考)
【五】.计数排序
  • 思路分析
    • 计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序
  • 算法描述:
    • 找出待排序的数组中最大和最小的元素;
    • 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
    • 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
    • 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
计数排序算法-动态实现图
  • 代码实现
    /**
     * 计数排序
     * @param $arr
     * @return array
     */
    function countingSort($arr)
    {
        $len = count( $arr );
        if( $len <= 1 ) return $arr;
        // 找出待排序的数组中最大值和最小值
        $min = min($arr);
        $max = max($arr);
        // 计算待排序的数组中每个元素的个数
        $countArr = array();
        for($i = $min; $i <= $max; $i++)
        {
            $countArr[$i] = 0;
        }
        foreach($arr as $v)
        {
            $countArr[$v] +=  1;
        }
        $resArr = array();
        foreach ($countArr as $k=>$c) {
            for($i = 0; $i < $c; $i++)
            {
                $resArr[] = $k;
            }
        }
        return $resArr;
    }
  • 小结:
    • 计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。
      作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
    • 计数排序不是比较排序,排序的速度快于任何比较排序算法
    • 最佳情况:T(n) = O(n+k)
      最差情况:T(n) = O(n+k)
      平均情况:T(n) = O(n+k)
    • 限制条件很多 注意
【六】.桶排序
  • 思路分析
    • 假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)
  • 算法描述
    • 设置一个定量的数组当作空桶;
    • 遍历输入数据,并且把数据一个一个放到对应的桶里去;
    • 对每个不是空的桶进行排序;
    • 从不是空的桶里把排好序的数据拼接起来。
桶排序-动态实现图
  • 代码实现
    /**
     * 木桶排序设计
     * @param $arr 目标数组
     * @param int $bucketCount 分配的木桶数目(整数)
     * @return array
     */
    public function bucketSort($arr,$bucketCount = 10)
    {
        $len = count($arr);
        $max = max($arr)+1;
        if ($len <= 1) {return $arr;}
        //填充木桶
        $arrFill = array_fill(0, $bucketCount, []);
        //开始标示木桶
        for($i = 0; $i < $len ; $i++)
        {
            $key = intval($arr[$i]/($max/$bucketCount));
            array_push($arrFill[$key] , $arr[$i]);
            //TODO 测试发现:如果此处调用,耗时翻倍
            /*if(count($arrFill[$key])){
                $arrFill[$key] = $this->insertSort($arrFill[$key]);
            }*/
        }
        //对每个不是空的桶进行排序
        foreach ($arrFill as $key=>$f){
            if (count($f)){
                $arrFill[$key] = $this->insertSort($f);
            }
        }
        //开始从木桶中拿出数据
        for($i = 0; $i < count($arrFill); $i ++)
        {
            if($arrFill[$i]){
                for($j = 0; $j <= count($arrFill[$i]); $j++)
                {   //这一行主要用来控制输出多个数字
                    if ($arrFill[$i][$j]){
                        $arrBucket[] = $arrFill[$i][$j];
                    }
                }
            };
        }
        return $arrBucket;
    }
    • 上述代码是我根据对木桶排序的定义进行的设计,因为网上多数的PHP代码感觉不合规范,其中的insertSort()为借用的文中所写的插入排序
    • 通过测试发现,此方法耗时比countingSort()要长好多,此处仅做参考不做推荐。
  • 总结
    • 当输入的元素是n 个0到k之间的整数时,它的运行时间是 O(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。
    • 稳定的排序方法
    • 桶排序是计数排序的升级版
    • 最佳情况:T(n) = O(n+k)
      最差情况:T(n) = O(n^2)
      平均情况:T(n) = O(n+k)

附录

【1】排序算法总结
排序算法总结
【2】自行分析
  • 此处提供一个网上多数作为“桶排序”的类似代码段,个人认为并非描述中的排序算法,倒是与文中涉及到的“计数排序”更为契合.
   /**
     * @param $arr 目标数组
     * @return array 返回的已排序数组
     */
    public function bOrCSort($arr)
    {
        $len = count($arr);
        $max = max($arr);
        if ($len <= 1) {return $arr;}
        //填充木桶
        $arrFill = array_fill(0, $max, 0);
        for($i = 0; $i < $len ; $i++)
        {
            $arrFill[$arr[$i]] ++;
        }
        //开始从木桶中拿出数据
        for($i = 0; $i <= $max; $i ++)
        {
            for($j = 1; $j <= $arrFill[$i]; $j++)
            { //这一行主要用来控制输出多个数字
                $arrRes[] = $i;
            }
        }
        return $arrRes;
    }
【3】用时测试
  • 为了简单比较几种算法的用时大小,本人随机生成了数量为10000,数值在300以内的测试数组,文中介绍的算法用时如下:
bucketsort 用时:1013.6640071869 ms
countingSort 用时:5.6858062744141 ms
quick_sort 用时:66540.108919144 ms
selectSort 用时:15234.955072403 ms
bubbleSort 用时:162055.89604378 ms
insertSort 用时:12029.093980789 ms
内置sort 用时:3.0169486999512 ms
  • 所以,简单需求的数组排序处理还是建议使用内置的sort()函数.
【4】参考文章
【5】提示
  • 本文主要为了学习而生
  • 累了,就写这些吧
  • 如果大家看出错误和意见,希望能给予提醒,共同进步,Thanks ...
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容