常用排序

排序算法经过了很长时间的演变,产生了很多种不同的方法。对于初学者来说,对它们进行整理便于理解记忆显得很重要。每种算法都有它特定的使用场合,很难通用。因此,我们很有必要对所有常见的排序算法进行归纳。

排序大的分类可以分为两种:内排序和外排序。在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序。下面讲的排序都是属于内排序。

内排序有可以分为以下几类:

  (1)、插入排序:直接插入排序、shell排序、折半插入排序。

  (2)、选择排序:直接选择排序、堆排序。

  (3)、交换排序:冒泡排序、快速排序。

  (4)、归并排序

       (5)、桶式排序

       (6)、基数排序

表格版

排序方法时间复杂度(平均)时间复杂度(最坏)时间复杂度(最好)空间复杂度稳定性复杂性
直接插入排序O(n2)O(n2)O(n2)O(n2)O(n)O(n)O(1)O(1)稳定简单
希尔排序O(nlog2n)O(nlog2n)O(n2)O(n2)O(n)O(n)O(1)O(1)不稳定较复杂
直接选择排序O(n2)O(n2)O(n2)O(n2)O(n2)O(n2)O(1)O(1)不稳定简单
堆排序O(nlog2n)O(nlog2n)O(nlog2n)O(nlog2n)O(nlog2n)O(nlog2n)O(1)O(1)不稳定较复杂
冒泡排序O(n2)O(n2)O(n2)O(n2)O(n)O(n)O(1)O(1)稳定简单
快速排序O(nlog2n)O(nlog2n)O(n2)O(n2)O(nlog2n)O(nlog2n)O(nlog2n)O(nlog2n)不稳定较复杂
归并排序O(nlog2n)O(nlog2n)O(nlog2n)O(nlog2n)O(nlog2n)O(nlog2n)O(n)O(n)稳定较复杂
基数排序O(d(n+r))O(d(n+r))O(d(n+r))O(d(n+r))O(d(n+r))O(d(n+r))O(n+r)O(n+r)稳定较复杂

① 插入排序

•思想:每步将一个待排序的记录,按其顺序码大小插入到前面已经排序的字序列的合适位置,直到全部插入排序完为止。 
•关键问题:在前面已经排好序的序列中找到合适的插入位置。 
•方法: 
–直接插入排序 
–二分插入排序 
–希尔排序

(1)直接插入排序(从后向前找到合适位置后插入)

基本思想:每步将一个待排序的记录,按其顺序码大小插入到前面已经排序的字序列的合适位置(从后向前找到合适位置后),直到全部插入排序完为止。

$arr = [8,6,9,20,5,3];
function directInsertSort($arr) {
    $len = count($arr);
    for($i=1; $i<$len; $i++) {  //待排序的元素
        $tmp = $arr[$i];
        for($j=$i-1; $j>=0; $j--) { //前边已经排序好的元素
            if($tmp >= $arr[$j]) {  //减少循环次数
                break;
            } else {    //置换
                $arr[$j+1] = $arr[$j];
                $arr[$j] = $tmp;
            }
            
        }
    }
    return $arr;
}

var_dump(directInsertSort($arr));die;

(2)希尔排序

基本思想:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1,即所有记录放在同一组中进行直接插入排序为止。(实际上就是一个分组插入排序,先对数据分组,然后对每个分组做插入排序)

function shellSort($arr) {
    $len = $d = count($arr);
    while($d>1) {
        $d = ceil($d/2);  //步长
        //直接插入排序
        for($i=$d; $i<$len; $i+=$d) {
            $tmp = $arr[$i];
            for($j= $i-$d; $j>=0; $j-=$d) {
                if($tmp >= $arr[$j]) {  //减少内部循环次数
                    break;
                } else {
                    $arr[$j+$d] = $arr[$j];
                    $arr[$j] = $tmp;
                }
            }
        }
    }
    return $arr;
}

var_dump(shellSort($arr));

② 选择排序

(1)直接选择排序

基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。

function selectSort($arr) {
    $len = count($arr);
    for($i=0; $i<$len; $i++) {
        $pos = $i;  //记录最小值位置,避免内部循环中多次置位
        for($j=$i+1; $j<$len; $j++) {
            if($arr[$j] < $arr[$pos]) {
                $pos = $j;
            }
        }
        if($pos != $i) {
            $tmp = $arr[$i];
            $arr[$i] = $arr[$pos];
            $arr[$pos] = $tmp;
        }
    }
    return $arr;
}
var_dump(selectSort($arr));

(2)堆排序

1、基本思想:

  堆排序是一种树形选择排序,是对直接选择排序的有效改进。

  堆的定义下:具有n个元素的序列 (h1,h2,…,hn),当且仅当满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1) (i=1,2,…,n/2)时称之为堆。在这里只讨论满足前者条件的堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项(大顶堆)。完全二叉树可以很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。

  思想:初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。

function buildMaxHeap(&$arr)
{
    global $len;
    for ($i = floor($len/2); $i >= 0; $i--) {
        heapify($arr, $i);
    }
}

function heapify(&$arr, $i)
{
    global $len;
    $left = 2 * $i + 1;
    $right = 2 * $i + 2;
    $largest = $i;

    if ($left < $len && $arr[$left] > $arr[$largest]) {
        $largest = $left;
    }

    if ($right < $len && $arr[$right] > $arr[$largest]) {
        $largest = $right;
    }

    if ($largest != $i) {
        swap($arr, $i, $largest);
        heapify($arr, $largest);
    }
}

function swap(&$arr, $i, $j)
{
    $temp = $arr[$i];
    $arr[$i] = $arr[$j];
    $arr[$j] = $temp;
}

function heapSort($arr) {
    global $len;
    $len = count($arr);
    buildMaxHeap($arr);
    for ($i = count($arr) - 1; $i > 0; $i--) {
        swap($arr, 0, $i);
        $len--;
        heapify($arr, 0);
    }
    return $arr;
}

var_dump(heapSort($arr));

③ 交换排序

(1)冒泡排序

1、基本思想:在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。

//下沉
function sinkBubbleSort($arr) {
    $len = count($arr);
    for($i=0; $i<$len; $i++) { //共进行$len次(轮)冒泡
        $swapFlag = false;  //标记是否置换
        for($j=0; $j<$len-$i-1; $j++) { //下沉,控制$j的最大位置,因为最下边的$i个已经完成排序。正着循环,因为可以节省下边已经排好序的循环。
            if($arr[$j] > $arr[$j+1]) {
                $swapFlag = true;
                $tmp = $arr[$j];
                $arr[$j] = $arr[$j+1];
                $arr[$j+1] = $tmp;
            }
        }
        if(!$swapFlag) {    //如果没有置换发生,说明已是一个有序数组
            return $arr;
        }
    }
    return $arr;
}
var_dump(sinkBubbleSort($arr));

//上浮
function goUpBubbleSort($arr) {
    $len = count($arr);
    for($i=0; $i<$len-1; $i++) {    //共进行$len次(轮)冒泡
        $swapFlag = false;  //标记是否置换
        for($j=$len-1; $j>$i; $j--) {   //下沉,控制$j的最小位置,因为最上边$i个已经完成排序。倒着循环,因为可以节省上边已经排好序的循环
            if($arr[$j] > $arr[$j-1]) {
                $swapFlag = true;
                $tmp = $arr[$j];
                $arr[$j] = $arr[$j-1];
                $arr[$j-1] = $tmp;
            }
        }
        if(!$swapFlag) {
            return $arr;
        }
    }
    return $arr;
}

var_dump(goUpBubbleSort($arr));

(2)快速排序

1、基本思想:选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。

function quickSort($arr) {
    $len = count($arr);
    if($len <= 1) {
        return $arr;
    }
    $base = $arr[0];    //选取一个基准值
    $left = $right = [];
    for($i=1; $i<$len; $i++) { //除去基准值
        if($arr[$i] < $base) {  //小于基准值
            $left[] = $arr[$i];
        } else {    //不小于基准值
            $right[] = $arr[$i];
        }
    }
    //递归调用
    $left = quickSort($left);
    $right = quickSort($right);
    
    return array_merge($left, [$base], $right);
}

var_dump(quickSort($arr));

④ 归并排序

1、基本思想:归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

function merge($left, $right) {
    $result = [];
    while(count($left) > 0 && count($right) > 0) {
        if($left[0] > $right[0]) {
            $result[] = array_shift($right);
        } else {
            $result[] = array_shift($left);
        }
    }
    while(count($left) > 0) {
        $result[] = array_shift($left);
    }
    while(count($right) > 0) {
        $result[] = array_shift($right);
    }
    return $result;
}

function mergeSort($arr) {
    $len = count($arr);
    if($len <= 1) {
        return $arr;
    }
    $middle = floor($len/2);
    $left = array_slice($arr, 0, $middle);
    $right = array_slice($arr, $middle);
    return merge(mergeSort($left), mergeSort($right));
}

var_dump(mergeSort($arr));

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值