排序总结

参考博客:https://www.cnblogs.com/onepixel/articles/7674659.html
排序的分类脑图:

排序分类的脑图

另一种排序分类图:
排序分类图之2

)

一些重要的概念:

  • 稳定性
    如果a原本在b前面,而a=b,排序之后a仍然在b的前面,则称该算法是稳定的,否则是不稳定的。
  • 时间复杂度(T(n)):
    对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律.
  • 空间复杂度(S(n)):
    是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。

1.冒泡排序

用冒泡基本概念的实现代码:

def bubble_sort(input_arr):  
    if not input_arr or len(input_arr) < 1:  
        return False  
  
    length = len(input_arr)  
    bottom_idx = 0  
    while bottom_idx < length - 1:  
        i = length - 1  
        while i > bottom_idx:  
            if input_arr[i] < input_arr[i - 1]:  
                tmp = input_arr[i]  
                input_arr[i] = input_arr[i - 1]  
                input_arr[i - 1] = tmp  
            i -= 1  
        bottom_idx += 1  
    return  

优化点:

  • 冒泡优化点1:如果一趟比较中,没有任何位置交换,则直接结束,这说明序列已经按照正确的顺序排列:
def bubble_sort_opt_1(input_arr):  
    """ 
    冒泡优化点1:如果一趟比较中,没有任何位置交换,则直接结束,这说明序列已经按照正确的顺序排列 
    :param input_arr: 
    :return: 
    """  
    if not input_arr or len(input_arr) < 1:  
        return False  
  
    length = len(input_arr)  
    bottom_idx = 0  
  
    while bottom_idx < length - 1:  
        i = length - 1  
        is_exchange = False  
        while i > bottom_idx:  
            if input_arr[i] < input_arr[i - 1]:  
                tmp = input_arr[i]  
                input_arr[i] = input_arr[i - 1]  
                input_arr[i - 1] = tmp  
                is_exchange = True  
            i -= 1  
        if not is_exchange:  
            break  
        bottom_idx += 1  
    return
  • 冒泡优化点2:如果一趟比较中,有位置交换,则标记第一次交换位置的地方,说明在这次交换之前的序列都是有序的。下一次循环直接从这个标记的位置开始
def bubble_sort_opt_2(input_arr):  
    """ 
    冒泡优化点2:如果一趟比较中,有位置交换,则记录第一次交换位置的地方,说明这次交换之前的序列都是有序的,下一次循环直接从这个位置开始 
    :param input_arr: 
    :return: 
    """  
    if not input_arr or len(input_arr) < 1:  
        return False  
  
    length = len(input_arr)  
    bottom_idx = 0  
  
    top_idx = length - 1  
    while bottom_idx < top_idx:  
        i = top_idx  
        is_exchange = False  
        # 用来标记第一次交换的位置  
        next_i = -1  
        while i > bottom_idx:  
            if input_arr[i] < input_arr[i - 1]:  
                tmp = input_arr[i]  
                input_arr[i] = input_arr[i - 1]  
                input_arr[i - 1] = tmp  
                is_exchange = True  
                if next_i < 0:  
                    next_i = i  
            i -= 1  
        if not is_exchange:  
            break  
        bottom_idx += 1  
        if next_i > 0:  
            top_idx = next_i  
        bottom_idx += 1 
    return

特点:

最小T(n) 最大T(n) 平均T(n) 空间复杂度 是否稳定 适用场景
O(n) O(n^2) O(n^2) O(1) 稳定 入门算法

2.快速排序

快排是经典的分治思想的排序,其中一个很重要的函数是partition函数,该函数作用是把一个乱序数组找到一个中枢值(pivot),把整个数组调整成小于pivot的值和大于pivot的值分布于数组左右,并返回具体pivot的索引位置,时间复杂度为O(n)。
这个partition还可以应用于多种场合,比如无须数组求第K大的数字
代码如下:

def partition(arr, low, high):
    if low > high:
        return -1
    pivot = arr[low]
    left = low
    right = high
    while left < right:
        while right > left and arr[right] > pivot:
            right -= 1
        arr[left] = arr[right]
        # 注意,这里为什么用 “<=” ? 如果用 “<” 可能会死循环,考虑如果 arr[right]==arr[left]==pivot
        while left < right and arr[left] <= pivot:
            left += 1
        arr[right] = arr[left]
    target_loc = left
    arr[target_loc] = pivot
    return target_loc


def qucik_sort_proxy(arr, low, high):
    if low < high and low >= 0 and high >= 0:
        middle = partition(arr, low, high)
        qucik_sort_proxy(arr, low, middle - 1)
        qucik_sort_proxy(arr, middle + 1, high)


def quick_sort(arr):
    if not arr or len(arr) < 2:
        return
    qucik_sort_proxy(arr, 0, len(arr) - 1)

优化点

参考1:三种快排及四种优化方式
参考2:编程珠玑中的快速排序 优化 详细分析

  • 快排优化点1:合理选择pivot
    改变固定中枢值的选择:取三个数,取中间值作为中枢值(pivot)
  • 快排优化点2:混用快排和插入排序
    当待排序序列的长度分割到一定大小后,使用插入排序,因为针对长度比较小的数组,快排的效率是不如插入排序的。根据统计结论得到的临界阈值是50左右(REF[3]),也有采用20的(REF[1])
  • 快排优化点3:处理和pivot相等的值
    把和pivot相等的值聚合在一起,递归或迭代操作时,摒除和pivot相等的值。
  • 快排优化点4:尾递归优化
    针对大数据量的数组,如果用递归方法,递归栈会非常大,可以用尾递归的方式,缩减堆栈深度,由原来的O(n)缩减为O(logn)。(其实这种优化编译器会自己优化,相比不使用优化的方法,时间几乎没有减少)
    所谓尾递归的写法如下:
def qucik_sort_proxy(arr, low, high):
    while low < high and low >= 0 and high >= 0:
        middle = partition(arr, low, high)
        qucik_sort_proxy(arr, low, middle - 1)
        low = middle + 1
  • 快排优化点5:递归改为迭代写法
    迭代写法其实也是用栈保存low,high参数来模拟递归函数的操作,效率肯定比递归好,但是可读性完全没递归高

特点:

最小T(n) 最大T(n) 平均T(n) 空间复杂度 是否稳定 适用场景
O(nlogn) O(n^2) O(nlogn) O(logn) 不稳定 大数据量的内部排序中,被认为是最好的适用排序算法,最常用

最差情况下,快排退化为冒泡排序了,最大时间复杂度为O(n^2),空间复杂度为O(n),这里空间复杂度是递归栈所占用的内存空间。

3.直接插入排序

思想:将一个值插入到一个有序队列中,找到合适的坑位插入之。还是用自己感到舒服的语言吧。。
PHP代码:

/**
 * 测试用例: null , false, '', [], [1],[1,2], [5,7,9,1,2,8,8]
 * @param $arr
 * @return bool
 */
function straight_insert_sort($arr) {
    if (empty($arr) || !is_array($arr)) {
        return false;
    }
    if (count($arr) < 2) {
        return $arr;
    }
    $len = count($arr);
    
    for ($i = 1; $i < $len; ++$i) {
        // 将一个值插入到一个有序序列中
        $tmpVal = $arr[$i];
        $targetLoc = $i;
        for ($j = $i; $j > 0; --$j) {
            if ($tmpVal < $arr[$j - 1]) {
                $targetLoc = $j - 1;
                $arr[$j] = $arr[$j - 1];
            } else {
                break;
            }
        }
        $arr[$targetLoc] = $tmpVal;
    }
    return $arr;
}

特点:

最小T(n) 最大T(n) 平均T(n) 空间复杂度 是否稳定 适用场景
O(n) O(n^2) O(n^2) O(1) 稳定 实现简单,在待排序数据量较小并且基本有序的情况下能获得很高的收益。因此常和快排、归并排序结合使用。

4. 希尔排序

参考:图解希尔排序
代码:

/**
 * 希尔排序:步长从大到小,执行插入排序
 * 测试用例: null , false, '', [], [1],[1,2], [5,7,9,1,2,8,8]
 * @param $arr
 * @return array|bool
 */
function shell_sort($arr) {
    if (empty($arr) || !is_array($arr)) {
        return false;
    }
    if (count($arr) < 2) {
        return $arr;
    }
    $len = count($arr);

    for ($gap = intval($len / 2); $gap > 0; $gap = intval($gap / 2)) {
        for ($i = $gap; $i < $len; $i += $gap) {
            // 将一个值插入到一个有序序列中
            $tmpVal = $arr[$i];
            $targetLoc = $i;
            for ($j = $i; $j - $gap >= 0; $j -= $gap) {
                if ($tmpVal < $arr[$j - $gap]) {
                    $targetLoc = $j - $gap;
                    $arr[$j] = $arr[$j - $gap];
                } else {
                    break;
                }
            }
            $arr[$targetLoc] = $tmpVal;
        }
    }
    return $arr;
}

特点

最小T(n) 最大T(n) 平均T(n) 空间复杂度 是否稳定 适用场景
O(n) O(n^2) O(n^1.5) O(1) 稳定 是直接插入排序的一种优化,但是实现起来比较复杂,不常用

希尔排序也叫缩小增量排序,特点是先用大的增量把整个数组调整成一个基本有序的数组,最后执行一次直接插入排序,其时间复杂度依赖于增量的选取策略,综合网上的说法,比较牛的增量选取策略能让平均时间复杂度降低到O(n^1.5)左右。

5. 简单选择排序

思想是,每次都进行一趟子序列的比较,把最小或最大的元素选出来,和指定位置的元素交换。
PHP代码:

/**
 * 简单选择排序:每一趟选择出最小的元素放到子序列的最前面
 * 测试用例: null , false, '', [], [1],[1,2], [5,7,9,1,2,8,8]
 * @param $arr
 * @return array|bool
 */
function simple_selection_sort($arr) {
    if (empty($arr) || !is_array($arr)) {
        return false;
    }
    if (count($arr) < 2) {
        return $arr;
    }
    $len = count($arr);
    for ($i = 0; $i < $len - 1; ++$i) {
        $targetLoc = $i;
        $stdVal = $arr[$i];
        for ($j = $i + 1; $j < $len; ++$j) {
            if ($arr[$j] < $stdVal) {
                $targetLoc = $j;
                $stdVal = $arr[$j];
            }
        }
        $arr[$targetLoc] = $arr[$i];
        $arr[$i] = $stdVal;
    }
    return $arr;

}

特点

最小T(n) 最大T(n) 平均T(n) 空间复杂度 是否稳定 适用场景
O(n^2) O(n^2) O(n^2) O(1) 不稳定 实现简单,但不适用于大数据量的排序

6.堆排序

参考:图解堆排序
堆排序的精髓在于堆创建和堆调整两个步骤,可以认为堆排序就是这两个步骤的组合封装,PHP代码:

/**
 * 大顶堆的调整,自上而下调整顺序,满足大顶堆的特征
 * @param $arr 堆数组
 * @param $idx 当前调整位置的索引位置
 * @param $maxIdx 这一次调整的堆数组的最大边界索引
 * @return bool
 */
function justify_heap(&$arr, $idx, $maxIdx) {
    $len = $maxIdx + 1;
    while ($idx < $len) {
        $leftIdx = 2 * $idx + 1;
        $rightIdx = 2 * $idx + 2;

        if ($leftIdx < $len && $rightIdx < $len) {
            // 有左右孩子
            $maxChildIdx = $arr[$leftIdx] > $arr[$rightIdx] ? $leftIdx : $rightIdx;
        } else if ($leftIdx < $len && $rightIdx >= $len) {
            // 只有左孩子
            $maxChildIdx = $leftIdx;
        } else {
            // 左右孩子都无,是叶子节点
            break;
        }

        if ($arr[$idx] > $arr[$maxChildIdx]) {
            break;
        } else {
            $tmp = $arr[$idx];
            $arr[$idx] = $arr[$maxChildIdx];
            $arr[$maxChildIdx] = $tmp;
            $idx = $maxChildIdx;
        }
    }
    return true;
}

/**
 * 构建大顶堆,本质是从中间节点开始,从下往上调整堆
 * @param $arr
 * @return bool
 */
function build_heap(&$arr) {
    $len = count($arr);
    if ($len < 1) {
        return false;
    }
    $midIndex = intval($len / 2);
    for ($i = $midIndex; $i >= 0; --$i) {
        justify_heap($arr, $i, $len - 1);
    }
    return true;
}

/**
 * 堆排序
 * @param $arr
 * @return array|bool
 */
function heap_sort($arr) {
    if (empty($arr) || !is_array($arr)) {
        return false;
    }
    $len = count($arr);
    if ($len < 2) {
        return $arr;
    }
    // 先构建大顶堆
    build_heap($arr);
    // 从最后一个元素开始,置换出最大的值,并且调整堆
    for ($i = $len - 1; $i > 0; --$i) {
        $tmp = $arr[0];
        $arr[0] = $arr[$i];
        $arr[$i] = $tmp;
        justify_heap($arr, 0, $i - 1);
    }
    return $arr;
}

特点

最小T(n) 最大T(n) 平均T(n) 空间复杂度 是否稳定 适用场景
O(nlogn) O(nlogn) O(nlogn) O(1) 不稳定 时间复杂度比较均衡的O(nlogn)级别的排序,不会出现快排的最差情况下O(n^2)的时间复杂度,并且空间复杂度优于快排的O(logn),但是依然是不稳定的排序。

7. 归并排序

代码实现,注意,归并排序的特性可以在单链表中实现O(nlongn)时间复杂度的排序~

/**
     * 合并两个有序数组
     * @param array $arr
     * @param $low
     * @param $mid
     * @param $high
     * @return array
     */
    function merge_div_array(array &$arr, $low, $mid, $high) {
        $tmpArr = [];
        $i = $low;
        $j = $mid + 1;
        $tmpIdx = 0;
        while ($i <= $mid && $j <= $high) {
            if ($arr[$i] <= $arr[$j]) {
                $tmpArr[$tmpIdx++] = $arr[$i++];
            } else {
                $tmpArr[$tmpIdx++] = $arr[$j++];
            }
        }
        // 处理剩余的数组
        while ($i <= $mid) {
            $tmpArr[$tmpIdx++] = $arr[$i++];
        }
        while ($j <= $high) {
            $tmpArr[$tmpIdx++] = $arr[$j++];
        }

        foreach ($tmpArr as $idx => $val) {
            // 这里要注意是 $idx + $low
            $arr[$idx + $low] = $val;
        }
        return;
    }

    /**
     * 归并数组的辅助函数:分治,然后合并组装
     * @param array $arr
     * @param $low
     * @param $high
     */
    function sort_merge_proxy(array &$arr, $low, $high) {
        $mid = intval(($low + $high) / 2);
        if ($low < $high) {
            $this->sort_merge_proxy($arr, $low, $mid);
            $this->sort_merge_proxy($arr, $mid + 1, $high);
            $this->merge_div_array($arr, $low, $mid, $high);
        }
        return;
    }

    /**
     * 归并排序,该特性甚至可以在单链表中排序实现  O(nLogn)的时间复杂度排序
     * @param array $arr
     * @return array
     */
    function merge_sort(array $arr) {
        $len = count($arr);
        if ($len <= 1) {
            return $arr;
        }
        $this->sort_merge_proxy($arr, 0, $len - 1);
        return $arr;
    }

8. OK,我们来看看一些语言中的排序库函数如何实现的

8.1 PHP的sort函数如何实现

参考: https://tyloafer.github.io/posts/30174/
白话描述:
1、如果容量<=16, 使用插入排序;
2、如果容量>16,使用快速排序,快速排序使用迭代的形式,并且选择pivot有一个根据容量大小进行三数取中、五数取中的策略。
这里算法中用的临界值是16,为什么?“临界值不是计算出来的,算是个经验值,具体对算法性能的影响取决于机器性能,内存大、CPU强的机器当然可以取更大的临界值~”

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 一、对比分析图 均按从小到大排列 k代表数值中的"数位"个数 n代表数据规模 m代表数据的最大值减最小值 稳定性:...
    leo567阅读 1,228评论 0 1
  • 项目需要,自己上学的时候接触过一些算法,我记得当时算法那门考了系里最高分,98分,想着没什么用呢,谁知道这两天就用...
    爱尚开发阅读 1,836评论 0 3
  • 众所周知,排序算法在数据结构中是很重要的,而排序又分为内部排序(待排序记录存放在计算机存储器中进行的排序过程)...
    橙小汁阅读 5,903评论 15 101
  • 一、插入排序 插入排序分为直接插入、二分插入和希尔排序; 直接插入排序 类似于扑克的排序,将待排序列分为有序序列和...
    期门阅读 228评论 0 0
  • 06节 怎样治疗放射性亢进病? 研究者指出,一些常见病、许多不易危及生命的绝症,可能都与癌症有着某种联系,因为这些...
    道易无成2阅读 138评论 0 0