详解四大排序算法

作者: Dsir 分类: PHP 发布时间: 2018年04月20日 10时07分

详解四大排序算法

如何排序?

NBA总决赛正在如火如荼的进行,老詹也正朝着他的第5个总亚军前进着。假设骑士队队员在运动场上排列成一队,如图所示,所有队员已经站好,准备热身,现在需要按身高从低到高 为队员们排队(最矮的站在左边),给他们照一张集体照,应该怎么排队呢?
在排序这件事情上,人与计算机程序相比有以下优势:我可以同时看到所有的队员,并且可以立刻找出最高的一个,毫不费力得测量和比较每一个人的身高。而且队员们不一定要固守特定的空间,他们可以相互推推攘攘就腾出了位置,还能互相前后站立,经过一些具体的调整,毫不费力地给队员们排好队

这里写图片描述


计算机程序员却不能像人这样通览所有的数据,它只能根据计算机的“比较”操作,在同一时间内对两个队员进行比较,算法 将”比较”的行为视为一个反复出现的问题,在人类看来是非常简单的事情,但程序算法 却只能一步一步得解决具体问题 和遵循一些简单的规则。

算法的本质就是拆分问题,按照最简单的规则,把问题拆分为一步一步交给计算机执行。
看上去这么简单,对吗:

  1. 比较两个数据项

  2. 交换两个数据项,或复制其中一项


重复这两步循环执行,直到全部有序为止。
不要轻视算法,因每种算法具体实现的细节有所不同。

BUBBLE 冒泡排序



冒泡排序算法运行起来非常慢,但概念上它是排序算法中最简单的,适合刚开始研究算法技术时的入门。


首先由一组无序的数字:

这里写图片描述

我录制了一段冒泡排序的执行过程(需要1-2秒的缓冲):

这里写图片描述

耐心观看完后,我们可以总结出冒泡排序的规则:

  1. 比较两个数字

  2. 如果左边的数字大,则交换两个数字的位置

  3. 向右移动一个位置,比较下两个数字

    沿着这个顺序比较下去,一直比较到最右端,虽然没有把所有的数字排好序,但是,数字中最大的那一个已经排放在最右边了,这个是一轮比较下来 可以确定的结果。

    下一轮,我们继续从最左边开始。

    这也是这个算法被称为冒泡排序的原因:因为在算法执行的时候,最大的数据项 总是 “冒泡”到数组的顶端【数组的排列规则,从左到右0-N】。

代码实现:
$arr=array(1,43,54,62,21,66,32,78,36,76,39);  
function bubbleSort($arr)
{  
  $len=count($arr);
  //该层循环控制 需要冒泡的轮数
  for($i=1;$i<$len;$i++)
  { //该层循环用来控制每轮 冒出一个数 需要比较的次数
    for($k=0;$k<$len-$i;$k++)
    {
       if($arr[$k]>$arr[$k+1])
        {
            $tmp=$arr[$k+1];
            $arr[$k+1]=$arr[$k];
            $arr[$k]=$tmp;
        }
    }
  }
  return $arr;
}

SELECT 选择排序



理解一下选择排序的原理

一组数据,

这里写图片描述

选择排序的原理,

这里写图片描述

* 使用选择排序算法对老詹的队友们排序,*


在选择排序中,不再比较两个相邻的队员,因此,需要记录下某个指定队员的高;可以用记事本记下队员的身高,同时还需要准备好一条紫红色的毛巾(不是搞基)。

进行选择排序 就是把所有的队员扫描一遍,从中选择出最矮的一个队员,最矮的这个队员和站在队列最左端的队员交换位置,即占到0号位置,现在最左端的队员是有序的了,不再需要交换位置。注意,在这个算法中有序的队员都排在队列的最左边(数组中较小的下标值),而冒泡排序则是优先排列在队列的右边。

排序从最左边开始,记录本上写下最左端球员的身高,并且在他的脖子上挂上红色毛巾,于是开始用下一个球员的身高和记录本上记录的值比较,如果下一个球员更矮,则在本子上划掉第一个球员的身高,记录第二个队员的身高,同时把红色毛巾从第一个球员的脖子上拿下来,挂在第二个队员的脖子上,继续沿着队列走下去,

一轮下来,毛巾就会落在最矮的队员面前,接着,唯一拥有红毛巾的队员和队列最左边的队员交换位置,现在已经对一个队员排好序了,这期间做了N-1次比较,淡只进行了一次交换。
嗯,老詹对你的建议很满意。

选择排序的代码实现

//选择排序法
//实现思路 双重循环完成,外层控制轮数,当前的最小值。内层 控制的比较次数
function select_sort($arr) {    
    //$i 当前最小值的位置, 需要参与比较的元素    
    for($i=0, $len=count($arr); $i<$len-1; $i++) {   
         //先假设最小的值的位置    
               $p = $i;        
         //$j 当前都需要和哪些元素比较,$i 后边的。        
         for($j=$i+1; $j<$len; $j++) {            
             //$arr[$p] 是 当前已知的最小值            
             if($arr[$p] > $arr[$j]) {            
                         //比较,发现更小的,记录下最小值的位置;并且在下次比较时,应该采用已知的最小值进行比较。            
                         $p = $j;
            }
        }    
        //已经确定了当前的最小值的位置,保存到$p中。    
        //如果发现 最小值的位置与当前假设的位置$i不同,则位置互换即可    
        $tmp = $arr[$p];    
        $arr[$p] = $arr[$i];    
        $arr[$i] = $tmp;
    }
//返回最终结果
return $arr;
}

INSERT 插入排序



插入排序原理

一段数据:

这里写图片描述

我录制了一段原理展示:

这里写图片描述

用插入排序提醒老詹吧


插入排序之前,队员随机站好。从排序过程的中间开始,可以更好地理解插入排序,这时队列已经排好了一半。

局部有序

此时,队友中间有一个作为标记的队员,还是用紫红色毛巾作标记吧,这个作为标记的队员的左边的所有队员已经是局部有序了。这意味着这一部分人之间是按顺序排列的:每个人比他左边的人都搞,然而这些队员在队列中最终的位置还没有确定,因为,当没有被排过序的队员要插入到他们中间的时候,他们的位置还要变动。

注意,局部有序在冒泡排序和选择排序中是不会出现的。在这两个算法中,一组数据项在某个时刻是完全有序的:在插入排序中,一组数据仅仅是局部有序的。

被标记的队员

作为标记的队员,称为“被标记的队员”,他和他右边所有的队员都是未排序的

如图:
这里写图片描述


下面价格要做的是在局部的 有序组中适当的位置 插入被标记的队员,然而要做到这一点,需要把部分已排序的队员右移腾出空间,为了提供移动所需的空间,就先让被标记的队员出列(在程序中,这个出列的行为,是该数据项被存储在一个临时变量中)

现在移动已经排过序的队员来腾出空间,将局部有序中最高的队员移动到原来被标记队员所在位置,次高的队员移动到原来最高的队员所在位置,以此类推。

局部有序的部分里多了一个队员,而未排序的部分少了一个队员,作为标记的球员,向右移动一个位置,所以他仍然放在未排序部分的最左边的队员勉强,重复这个过程,直到所有未排序的队员都被插入到局部有序队列中的合适位置。

插入排序的核心代码:

function insertSort($arr) {
    $len=count($arr); 
    for($i=1, $i<$len; $i++) {
        $tmp = $arr[$i];
        //内层循环控制,比较并插入
        for($j=$i-1;$j>=0;$j--) {
            if($tmp < $arr[$j]) {
                //发现插入的元素要小,交换位置,将后边的元素与前面的元素互换
                $arr[$j+1] = $arr[$j];
                $arr[$j] = $tmp;
            } else {
                //如果碰到不需要移动的元素,由于是已经排序好是数组,则前面的就不需要再次比较了。
                break;
            }
        }
    }
    return $arr;
}

插入排序的效率(有趣)


这个算法需要多少次比较和复制呢?在第一轮排序中,它最多比较一次,在第二轮最多比较两次,以此类推

1+2+3+…+N-1=N*(N-1)/2 次比较;

复制的次数,大致等于比较的次数,然而,一次复制与一次交换的时间耗费不同,相对于 随机顺序的数据这个算法比冒泡排序快一倍,比选择排序略快,

在任意情况下,插入排序的时间复杂度也为O(N2)。

有趣的是

对于已经有序或者基本有序的数据来说,插入排序要好得多,当数据有序的时候,while循环的条件总是false吗所以它成为了外层循环中的一个简单语句,执行N-1此,算法运行只需要O(N)的时间。这对一个基本有序的文件进行排序是一个简单而有效的方法。


然而,对于逆序排列的数据,每次比较和移动都会执行,在这种情况下,插入排序 并不比冒泡排序快。

MERGE 归并排序


直接上手归并排序?NO!

感受归并排序

一组随机数据:

这里写图片描述

归并排序流程:

这里写图片描述

归并排序:时间复杂度为~O(nlogn)--又称合并排序

归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,

即把待排序序列分为若干个有序的子序列,再把有序的子序列合并为整体有序序列。

归并排序的一个缺点是它需要存储器有另一个大小等于数据项数目的数组。如果初始数组几乎占满整个存储器,那么归并排序将不能工作,但是如果有足够的空间,归并排序会是一个很好的选择。

<?php  
$arrStoreList = array(3,2,4,1,5);  
$sort = new Merge_sort();  
$sort->stableSort($arrStoreList, function ($a, $b) {    // function ($a, $b)匿名函数  
            return $a < $b;  
});  
//静态调用方式也行  
/*Merge_sort:: stableSort($arrStoreList, function ($a, $b) { 
            return $a < $b; 
});*/  
print_r($arrStoreList);  
  
class Merge_sort{  
  
 public static function stableSort(&$array, $cmp_function = 'strcmp') {  
        //使用合并排序  
        self::mergeSort($array, $cmp_function);  
        return;  
    }  
 public static function mergeSort(&$array, $cmp_function = 'strcmp') {  
        // Arrays of size < 2 require no action.  
        if (count($array) < 2) {  
            return;  
        }  
        // Split the array in half  
        $halfway = count($array) / 2;  
        $array1 = array_slice($array, 0, $halfway);  
        $array2 = array_slice($array, $halfway);  
        // Recurse to sort the two halves  
        self::mergeSort($array1, $cmp_function);  
        self::mergeSort($array2, $cmp_function);  
        // If all of $array1 is <= all of $array2, just append them.  
//array1 与 array2 各自有序;要整体有序,需要比较array1的最后一个元素和array2的第一个元素大小  
        if (call_user_func($cmp_function, end($array1), $array2[0]) < 1) {    
            $array = array_merge($array1, $array2);  
  
            return;  
        }  
        // 将两个有序数组合并为一个有序数组:Merge the two sorted arrays into a single sorted array  
        $array = array();  
        $ptr1 = $ptr2 = 0;  
        while ($ptr1 < count($array1) && $ptr2 < count($array2)) {  
            if (call_user_func($cmp_function, $array1[$ptr1], $array2[$ptr2]) < 1) {  
                $array[] = $array1[$ptr1++];  
            } else {  
                $array[] = $array2[$ptr2++];  
            }  
        }  
        // Merge the remainder  
        while ($ptr1 < count($array1)) {  
            $array[] = $array1[$ptr1++];  
        }  
        while ($ptr2 < count($array2)) {  
            $array[] = $array2[$ptr2++];  
        }  
        return;  
    }  
}  
?>


算法原理分析:关键是理解递归调用及其返回函数的原理

如果觉得我的文章对您有用,请随意打赏、分享。您的支持将鼓励我继续创作!

微博分享
7条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注