美文网首页
排序算法概述

排序算法概述

作者: yousa_ | 来源:发表于2020-07-10 17:23 被阅读0次

在本系列中我们综述十个常用排序算法,分别是简单插入排序、希尔排序、简单选择排序、快速排序、冒泡排序、堆排序、归并排序、计数排序、桶排序、基数排序。此外,针对快速排序算法,额外介绍了基于单链表的快速排序算法。整体框架如下:

排序算法概览

基于插入思想

这里我们介绍简单插入排序已经希尔排序

    • 简单插入排序
  • 思想
    开始时默认第一个数有序,将剩余n-1个数逐个插入。插入操作具体包括:比较确定插入位置,数据移位腾出合适空位。每轮操作O(n)次,共O(n)轮,时间复杂度O(n^2)。额外空间开销出在数据移位时那一个过渡空间,空间复杂度O(1)。
  • Python3代码
#!usr/bin/env python
# -*- coding:utf-8 -*-
# author: ShidongDu time:2020/7/9

# 时间复杂度:o(n^2)
# 空间复杂度:o(1)
# 稳定性:稳定

'''
简单插入排序同样操作n-1轮,每轮将一个未排序树插入排好序列。

开始时默认第一个数有序,将剩余n-1个数逐个插入。插入操作具体包括:比较确定插入位置,数据移位腾出合适空位

每轮操作O(n)次,共O(n)轮,时间复杂度O(n^2)。

额外空间开销出在数据移位时那一个过渡空间,空间复杂度O(1)。
'''
from typing import List
class Solution:
    def InsertSort(self, obj: List[int]):
        if len(obj) <= 1:
            return obj

        for i in range(1, len(obj)):
            j = i
            target = obj[j]     # target是将要进行比较的元素
            # 这里是>0而不是>=0,因为obj[j]要和j前一位数字进行比较,所以j最小只能是1,否则下面的obj[j-1]会数组越界
            while j>0 and target<obj[j-1]:
                obj[j] = obj[j-1]   # 所有大于target的元素后移
                j -= 1
            # 最后直到找到一位j,使得obj[j] > obj[j-1]或者j为0
            obj[j] = target

        return obj


if __name__ == '__main__':
    x = input("请输入待排序数列:\n")
    obj = list(map(int, x.split()))
    solution = Solution()
    res = solution.InsertSort(obj)
    print(res)
    • 希尔排序
  • 思想
    希尔排序是插入排序的高效实现,对简单插入排序减少移动次数优化而来。本质是还是插入排序。
    希尔排序将序列按固定间隔划分为多个子序列,在子序列中简单插入排序,先做远距离移动使序列基本有序;逐渐缩小间隔重复操作,最后间隔为1时即简单插入排序。希尔排序对序列划分O(n)次,每次简单插入排序O(logn),时间复杂度O(nlogn)
  • Python3代码
#!usr/bin/env python
# -*- coding:utf-8 -*-
# author: ShidongDu time:2020/7/9

# 时间复杂度:o(n*log(n))
# 空间复杂度:o(1)
# 稳定性:不稳定

'''
希尔排序是插入排序的高效实现,对简单插入排序减少移动次数优化而来。

简单插入排序每次插入都要移动大量数据,前后插入时的许多移动都是重复操作,若一步到位移动效率会高很多。

若序列基本有序,简单插入排序不必做很多移动操作,效率很高。

希尔排序将序列按固定间隔划分为多个子序列,在子序列中简单插入排序,先做远距离移动使序列基本有序;逐渐缩小间隔重复操作,最后间隔为1时即简单插入排序。

希尔排序对序列划分O(n)次,每次简单插入排序O(logn),时间复杂度O(nlogn)

额外空间开销出在插入过程数据移动需要的一个暂存,空间复杂度O(1)
希尔排序会设置一个增量,初始增量设为待排序样本长度的一半,即 gap = length//2 ,之后每一次增量为前一次的一半,即 gap = gap//2
当增量减至1时,整个文件恰被分成一组,算法便终止。
'''
from typing import List
class Solution:
    def ShellSort(self, obj: List[int]):
        if len(obj) <= 1:
            return obj
        gap = len(obj) >> 1

        while gap > 0:
            for i in range(gap, len(obj)):
                target = obj[i]
                j = i-gap
                while(j>=0 and target<obj[j]):
                    obj[j+gap] = obj[j]
                    j -= gap
                obj[j+gap] = target
            gap >>= 1
        return obj


if __name__ == '__main__':
    x = input("请输入待排序数列:\n")
    obj = list(map(int, x.split()))
    solution = Solution()
    res = solution.ShellSort(obj)
    print(res)

基于选择思想

    • 简单选择排序
  • 思想
    简单选择排序同样对数据操作n-1轮,每轮找出一个最大(小)值。操作指选择,即未排序数逐个比较交换,争夺最值位置,每轮将一个未排序位置上的数交换成已排序数,即每轮选一个最值。每轮操作O(n)次,共O(n)轮,时间复杂度O(n^2)。
  • Python3代码
#!usr/bin/env python
# -*- coding:utf-8 -*-
# author: ShidongDu time:2020/7/9

# 时间复杂度:o(n^2)
# 空间复杂度:o(1)
# 稳定性:稳定

'''
简单选择排序同样对数据操作n-1轮,每轮找出一个最大(小)值。

操作指选择,即未排序数逐个比较交换,争夺最值位置,每轮将一个未排序位置上的数交换成已排序数,即每轮选一个最值。

每轮操作O(n)次,共O(n)轮,时间复杂度O(n^2)。

额外空间开销出在交换数据时那一个过渡空间,空间复杂度O(1)。
'''
from typing import List
class Solution:
    def SelectSort(self, obj: List[int]):
        if len(obj) <= 1:
            return obj

        # 因为对于选择排序,排到最后一个元素的时候不用算hhhh,所以就len(obj)-1 就行
        for i in range(len(obj)-1):
            minIndex = i
            for j in range(i+1, len(obj)):
                if obj[j] < obj[minIndex]:
                    minIndex = j
            if minIndex != i:
                obj[i], obj[minIndex] = obj[minIndex], obj[i]
        return obj


if __name__ == '__main__':
    x = input("请输入待排序数列:\n")
    obj = list(map(int, x.split()))
    solution = Solution()
    res = solution.SelectSort(obj)
    print(res)
    • 快速排序
  • 思想
    快速排序基于选择划分,是简单选择排序的优化。每次划分将数据选到基准值两边,循环对两边的数据进行划分,类似于二分法。算法的整体性能取决于划分的平均程度,即基准值的选择,此处衍生出快速排序的许多优化方案,甚至可以划分为多块。基准值若能把数据分为平均的两块,划分次数O(logn),每次划分遍历比较一遍O(n),时间复杂度O(nlogn)。
  • Python3代码
#!usr/bin/env python
# -*- coding:utf-8 -*-
# author: ShidongDu time:2020/7/9

# 时间复杂度:o(n*log(n))
# 空间复杂度:o(log(n))
# 稳定性:不稳定

'''
快速排序基于选择划分,是简单选择排序的优化。

每次划分将数据选到基准值两边,循环对两边的数据进行划分,类似于二分法。

算法的整体性能取决于划分的平均程度,即基准值的选择,此处衍生出快速排序的许多优化方案,甚至可以划分为多块。

基准值若能把数据分为平均的两块,划分次数O(logn),每次划分遍历比较一遍O(n),时间复杂度O(nlogn)。

额外空间开销出在暂存基准值,O(logn)次划分需要O(logn)个,空间复杂度O(logn) (因为是递归形式的,因此所有基准值都要存储)

'''
from typing import List
class Solution:
    def QuickSort(self, obj: List[int]):
        if len(obj) <= 1:
            return obj

        # 因为快速排序是基于递归思想的,因此我们还要再定义一个函数
        def quick_sort(q, l, r):
            if l >= r:
                return
            x = q[(l + r)>>1]
            i, j = l - 1, r + 1
            while (i < j):
                while True:
                    i += 1
                    if q[i] > x:
                        break
                while True:
                    j -= 1
                    if q[j] < x:
                        break
                if i < j:
                    q[i], q[j] = q[j], q[i]

            quick_sort(q, l, j)
            quick_sort(q, j + 1, r)

        quick_sort(obj, 0, len(obj)-1)
        return obj


if __name__ == '__main__':
    x = input("请输入待排序数列:\n")
    obj = list(map(int, x.split()))
    solution = Solution()
    res = solution.QuickSort(obj)
    print(res)
    • 快速排序的单链表形式
  • 思想
    与快速排序一样,基于链表的结构使得快速排序变成稳定排序,即元素前后顺序不会更改。
  • 代码
#!usr/bin/env python
# -*- coding:utf-8 -*-
# author: ShidongDu time:2020/7/9

'''
给定一个单链表,请使用快速排序算法对其排序。

要求:期望平均时间复杂度为 O(nlogn),期望额外空间复杂度为 O(logn)。

思考题: 如果只能改变链表结构,不能修改每个节点的val值该如何做呢?

数据范围
链表中的所有数大小均在 int 范围内,链表长度在 [0,10000]。

输入样例:
[5, 3, 2]
输出样例:
[2, 3, 5]
'''
# 快速排序的单链表形式,时间和空间复杂度与普通快排相同,但是优点是可以保留原有顺序,是稳定的排序方式
# Definition for singly-linked list.
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution:
    def quickSortList(self, head: ListNode):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        if not head or not head.next:
            return head
        left = ListNode(-1) ; mid = ListNode(-1)    ; right = ListNode(-1)
        ltail = left        ; mtail = mid           ; rtail = right
        target = head.val
        p = head
        while p:
            if p.val < target:
                ltail.next = p
                ltail = p
            elif p.val == target:
                mtail.next = p
                mtail = p
            else:
                rtail.next = p
                rtail = p
            p = p.next
        ltail.next = None; mtail.next = None; rtail.next = None
        left.next = self.quickSortList(left.next)
        right.next = self.quickSortList(right.next)
        self.get_tail(left).next = mid.next
        self.get_tail(left).next = right.next
        return left.next

    def get_tail(self, head: ListNode):
        while head and head.next:
            head = head.next
        return head

基于交换思想

    • 冒泡排序
  • 思想
    冒泡排序对数据操作n-1轮,每轮找出一个最大(小)值。操作指对相邻两个数比较与交换,每轮会将一个最值交换到数据列首(尾),像冒泡一样。每轮操作O(n)次,共O(n)轮,时间复杂度O(n^2)。额外空间开销出在交换数据时那一个过渡空间,空间复杂度O(1)。
  • Python3代码
#!usr/bin/env python
# -*- coding:utf-8 -*-
# author: ShidongDu time:2020/7/9

# 时间复杂度:o(n^2)
# 空间复杂度:o(1)
# 稳定性:稳定

'''
冒泡排序对数据操作n-1轮,每轮找出一个最大(小)值。

操作指对相邻两个数比较与交换,每轮会将一个最值交换到数据列首(尾),像冒泡一样。

每轮操作O(n)次,共O(n)轮,时间复杂度O(n^2)。

额外空间开销出在交换数据时那一个过渡空间,空间复杂度O(1)。
'''
from typing import List
class Solution:
    def BubbleSort(self, obj: List[int]):
        if len(obj) <= 1:
            return obj
        for i in range(len(obj)):
            for j in range(len(obj)-i-1):
                if obj[j] > obj[j+1]:
                    obj[j], obj[j+1] = obj[j+1], obj[j]
        return obj


if __name__ == '__main__':
    x = input("请输入待排序数列:\n")
    obj = list(map(int, x.split()))
    solution = Solution()
    res = solution.BubbleSort(obj)
    print(res)
    • 堆排序
  • 思想
    堆排序基于比较交换,是冒泡排序的优化。具体涉及大(小)顶堆的建立与调整。大顶堆指任意一个父节点都不小于左右两个孩子节点的完全二叉树,根节点最大。堆排序首先建立大顶堆(找出一个最大值),然后用最后一个叶子结点代替根节点后做大顶堆的调整(再找一个最大值),重复此操作,直到将数列按由大到小的顺序找完。
    这里需要注意两个公式,在以列表形式表现的完全二叉树中,节点n的左、右孩子编号分别为2*n+1,2*n+2,节点n的父亲编号为(n-1)//2(节点从0开始编号)
    堆排序有两个核心步骤:①建堆(根据定义建堆,即每个父节点的值>=子节点) ②依照建好的堆,依次读取堆顶元素,具体的做法是将堆顶元素与末尾元素交换(相当于把堆顶元素放到答案空间),然后对新的堆进行再一次调整。
  • Python3代码
#!usr/bin/env python
# -*- coding:utf-8 -*-
# author: ShidongDu time:2020/7/9

# 时间复杂度:o(n*log(n))
# 空间复杂度:o(1)
# 稳定性:不稳定
# 注意:初始建堆时间复杂度O(n),一次重建堆时间复杂度O(logn),所以如果对整个长为n的数组进行排序,时间复杂度是 o(n + n*log(n)) = o(n*log(n))

'''
堆排序基于比较交换,是冒泡排序的优化。具体涉及大(小)顶堆的建立与调整。

大顶堆指任意一个父节点都不小于左右两个孩子节点的完全二叉树,根节点最大。

堆排序首先建立大顶堆(找出一个最大值),然后用最后一个叶子结点代替根节点后做大顶堆的调整(再找一个最大值),重复

以数组(列表)实现大顶堆时,从上到下,从左到右编号。父节点序号为n,则左右孩子节点序号分别为2*n+1、2*n+2

大顶堆的调整:将仅有根节点不满足条件的完全二叉树,调整为大顶堆的过程。

大顶堆调整方法:将根节点与较大一个儿子节点比较,不满足条件则交换。
若发生交换,将被交换儿子节点视作根节点重复上一步

大顶堆的建立:从最后一个非叶子节点开始到根节点结束的一系列大顶堆调整过程。

堆排序的初始建堆过程比价复杂,对O(n)级别个非叶子节点进行堆调整操作O(logn),时间复杂度O(nlogn);之后每一次堆调整操作确定一个数的次序,时间复杂度O(nlogn)。合起来时间复杂度O(nlogn)

额外空间开销出在调整堆过程,根节点下移交换时一个暂存空间,空间复杂度O(1)

'''
# 堆排序特点 ①:必须是一棵完全二叉树  ②:父节点的值 > 子节点的值
# 所以,堆排序的第一步就是建堆。如何建堆呢?那就让堆符合我们的定义,即:父节点的值 > 子节点的值

# 堆排序总共有两大步骤: ①建堆,这一步是o(n)的时间复杂度  ②依照建好的堆,一次读取堆顶元素,具体的做法是将堆顶元素与末尾元素交换
# 每一次交换后都要对新堆进行微调,使其达到要求(满足堆的定义),这里每一步是o(log(n))的复杂度,一共n次
# 一个公式是,节点i的子节点,序号是 2*i+1, 2*i+2, 节点i的父节点,序号是 (i-1)>>1
from typing import List
class Solution:
    def HeapSort(self, obj: List[int]):
        if len(obj) <= 1:
            return obj
        n = len(obj)

        def heapify(n, i):
            # heapify(i)表示对第i个节点进行heapify操作,即使以i节点为根节点的树符合堆的特性
            if i > n:
                return

            c1 = 2*i+1
            c2 = 2*i+2
            max_ = i
            if c1<n and obj[c1]>obj[max_]:
                max_ = c1
            if c2<n and obj[c2]>obj[max_]:
                max_ = c2
            if max_ != i:
                obj[i], obj[max_] = obj[max_], obj[i]
                heapify(n, max_)

        def build_heap(n):
            last_node = n-1
            parent = (last_node-1) >> 1
            for i in range(parent, -1, -1):
                heapify(n, i)

        def heap_sort():
            build_heap(n)
            for i in range(n-1, -1, -1):
                obj[i], obj[0] = obj[0], obj[i]
                heapify(i, 0)

        build_heap(n)
        heap_sort()
        return obj



if __name__ == '__main__':
    x = input("请输入待排序数列:\n")
    obj = list(map(int, x.split()))
    solution = Solution()
    res = solution.HeapSort(obj)
    print(res)

基于分治思想

分治排序

可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。

    • 归并排序
  • 思想
    归并排序运用分治递归思想:将大问题分为较小的子问题,分而治之;递归调用同样的方法解决子问题。最终将序列的排序问题分治为一个数的排序问题,关键在于如何将子问题答案合并为问题答案。两个有序序列合并为一个有序序列,借助一个暂存数组(列表),两个序列元素依次比较填入暂存列表,形成一个有序序列。归并排序划分子问题采用二分法,共需O(log(n))次划分,当然需要相当次合并;每次合并遍历比较O(n)。时间复杂度O(n*log(n))。额外空间开销出在合并过程中的一个暂存数组,空间复杂度O(n)。归并排序算法中,归并最后到底都是相邻元素之间的比较交换,并不会发生相同元素的相对位置发生变化,故是稳定性算法。
  • Python3代码
#!usr/bin/env python
# -*- coding:utf-8 -*-
# author: ShidongDu time:2020/7/9

# 时间复杂度:o(n*log(n))
# 空间复杂度:o(n)
# 稳定性:稳定

'''
归并排序运用分治递归思想:将大问题分为较小的子问题,分而治之;递归调用同样的方法解决子问题。最终将序列的排序问题分治为一个数的排序问题,关键在于如何将子问题答案合并为问题答案。

两个有序序列合并为一个有序序列,借助一个暂存数组(列表),两个序列元素依次比较填入暂存列表,形成一个有序序列。

归并排序划分子问题采用二分法,共需O(log(n))次划分,当然需要相当次合并;每次合并遍历比较O(n)。时间复杂度O(n*log(n))。

额外空间开销出在合并过程中的一个暂存数组,空间复杂度O(n)。
归并排序算法中,归并最后到底都是相邻元素之间的比较交换,并不会发生相同元素的相对位置发生变化,故是稳定性算法。
'''
from typing import List
class Solution:
    def MergeSort(self, obj: List[int]):
        if len(obj) <= 1:
            return obj
        mid = (len(obj)) >> 1
        left = self.MergeSort(obj[:mid])
        right = self.MergeSort(obj[mid:])
        res = []
        while left and right:
            if left[0] <= right[0]:
                res.append(left.pop(0))
            else:
                res.append(right.pop(0))
        if left:
            # 注意这里要用 extend,不然会出现形如[3, 4, [5], 7]的情况
            res.extend(left)
        elif right:
            res.extend(right)
        return res


if __name__ == '__main__':
    x = input("请输入待排序数列:\n")
    obj = list(map(int, x.split()))
    solution = Solution()
    res = solution.MergeSort(obj)
    print(res)

基于桶思想

    • 计数排序
  • 思想
    计数排序可以说是桶排序的一种特殊情况,当排序n个数据时,n的取值范围很小的时候,比如说是0-k,我们就可以分成k个桶,因为桶内的数据都是一致的,所以可以减少了桶内排序的时间。 举个例子,对高考成绩省内排名,成绩仅是一个三位数区间,我们就可以用这种方法。
  • 例子
    一组学生的成绩为{2,5,3,0,2,3,0,3},这时候我们可以用一个数组A[8]存储这组数据,然后按照桶排序的标准,将数据分成0-5,建立一个初始化的数组C[6]表示桶,然后遍历一遍A[8],得到一个新的C[6]


    计数排序

    从图中可以看出分数为3的有3人,小于3的分数有4人,所以可以得出一个R[8]



    如何快速确认成绩在数组中的位置呢,当然不能通过我们肉眼看,我们的思路是进行对C[6]进行一次顺序求和,所以C[6]的存储情况变成了如下:
  • 总结:
    计数排序利于数据可视化,非常适用于待排序数列分布集中的场景,局限是计数排序需要保证待排序数为整数,对于带有小数位的数列排列时,解法方法是可以先对其整数位进行排序,得到基本有序的数列,接着对于桶内每一个元素,再进行进一步排序,如快排,希尔排序或堆排序、归并排序等时间复杂度为o(n*log(n))的操作。
  • Python3代码
#!usr/bin/env python
# -*- coding:utf-8 -*-
# author: ShidongDu time:2020/7/10

# 时间复杂度:o(n + k)
# 空间复杂度:o(k)
# 稳定性:稳定
# n:数据规模 ;k:‘桶’个数

'''
计数排序用待排序的数值作为计数数组(列表)的下标,统计每个数值的个数,然后依次输出即可。

计数数组的大小取决于待排数据取值范围,所以对数据有一定要求,否则空间开销无法承受。

计数排序只需遍历一次数据,在计数数组中记录,输出计数数组中有记录的下标,时间复杂度为O(n+k)。

额外空间开销即指计数数组,实际上按数据值分为k类(大小取决于数据取值),空间复杂度O(k)。
注意: 计数排序只能用在所有元素均整数的情况下,而且计数排序的取值范围最好不要太大.
'''
from typing import List
class Solution:
    def CountSort(self, obj: List[int]):
        if len(obj) <= 1:
            return obj
        min_num = min(obj)
        max_num = max(obj)
        count = [0]*(max_num - min_num + 1)
        for i in obj:
            count[i - min_num] += 1

        res = []
        for i in range(len(count)):
            if count[i] > 0:
                res.extend([i+min_num]*count[i])
        return res

if __name__ == '__main__':
    x = input("请输入待排序数列,所有元素必须为整数:\n")
    obj = list(map(int, x.split()))
    solution = Solution()
    res = solution.CountSort(obj)
    print(res)
    • 桶排序
  • 思想
    相当于对计数排序做了一个改进:先用一定的函数关系将数据划分到不同有序的区域(桶)内,然后子数据分别在桶内排序(如快速排序、归并排序等等),之后顺次输出。当每一个不同数据分配一个桶时,也就相当于计数排序。
  • Python3代码
#!usr/bin/env python
# -*- coding:utf-8 -*-
# author: ShidongDu time:2020/7/10

# 时间复杂度:o(n + k)
# 空间复杂度:o(n + k)
# 稳定性:稳定
# n:数据规模 ;k:‘桶’个数

'''
桶排序实际上是计数排序的推广,但实现上要复杂许多。

桶排序先用一定的函数关系将数据划分到不同有序的区域(桶)内,然后子数据分别在桶内排序,之后顺次输出。

当每一个不同数据分配一个桶时,也就相当于计数排序。

假设n个数据,划分为k个桶,桶内采用快速排序,时间复杂度为O(n)+O(k * n/k*log(n/k))=O(n)+O(n*(log(n)-log(k))),

显然,k越大,时间复杂度越接近O(n),当然空间复杂度O(n+k)会越大,这是空间与时间的平衡。
桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序
'''
from typing import List
class Solution:
    def BucketSort(self, obj: List[int]):
        res = []
        if len(obj) <= 1:
            return obj
        # 假设一共设10个桶
        k = 10
        min_num = min(obj)
        max_num = max(obj)
        space = (max_num - min_num) // k
        bucket = [[] for _ in range(k+1)]
        for i in obj:
            bucket[(i-min_num)//space].append(i)
        for sub_bucket in bucket:
            self.QuickSort(sub_bucket)
            res.extend(sub_bucket)
        return res


    def QuickSort(self, obj: List[int]):
        if len(obj) <= 1:
            return obj

        # 因为快速排序是基于递归思想的,因此我们还要再定义一个函数
        def quick_sort(q, l, r):
            if l >= r:
                return
            x = q[(l + r)>>1]
            i, j = l - 1, r + 1
            while (i < j):
                while True:
                    i += 1
                    if q[i] > x:
                        break
                while True:
                    j -= 1
                    if q[j] < x:
                        break
                if i < j:
                    q[i], q[j] = q[j], q[i]

            quick_sort(q, l, j)
            quick_sort(q, j + 1, r)

        quick_sort(obj, 0, len(obj)-1)
        return obj

if __name__ == '__main__':
    x = input("请输入待排序数列,所有元素必须为整数:\n")
    obj = list(map(int, x.split()))
    solution = Solution()
    res = solution.BucketSort(obj)
    print(res)
    • 基数排序
  • 思想
    基数排序进行多轮按位比较排序,轮次取决于最大数据值的位数。先按照个位比较排序,然后十位百位以此类推,优先级由低到高,这样后面的移动就不会影响前面的。基数排序按位比较排序实质上也是一种划分,一种另类的‘桶’罢了。比如,第一轮按各个位比较,按个位大小排序分别装入10个‘桶’中,‘桶’中个位相同的数据视作相等,桶是有序的,按序输出,后面轮次接力完成排序。基数排序‘桶’内数据在划分桶时便已排序O(n),k个桶,时间复杂度为O(n*k)。额外空间开销出在数据划分入桶过程,桶大小O(n+k),空间复杂度O(n+k)。事实上,这里我们k通常是10个,也就是位数从0到9
  • Python3代码
#!usr/bin/env python
# -*- coding:utf-8 -*-
# author: ShidongDu time:2020/7/10

# 时间复杂度:o(n * k)
# 空间复杂度:o(n+k)
# 稳定性:稳定
# n:数据规模 ;k:‘桶’个数

'''
基数排序进行多轮按位比较排序,轮次取决于最大数据值的位数。

先按照个位比较排序,然后十位百位以此类推,优先级由低到高,这样后面的移动就不会影响前面的。

基数排序按位比较排序实质上也是一种划分,一种另类的‘桶’罢了。比如,第一轮按各个位比较,按个位大小排序分别装入10个‘桶’中,‘桶’中个位相同的数据视作相等,桶是有序的,按序输出,后面轮次接力完成排序。

基数排序‘桶’内数据在划分桶时便已排序O(n),k个桶,时间复杂度为O(n*k)。

额外空间开销出在数据划分入桶过程,桶大小O(n+k),空间复杂度O(n+k)。
事实上,这里我们k通常是10个,也就是位数从0到9
'''
from typing import List
class Solution:
    def RadixSort(self, obj: List[int]):
        if len(obj) <= 1:
            return obj
        radix = 10
        K = len(str(max(obj)))  # K表示最大位数
        for i in range(1, K+1):
            bucket = [[] for _ in range(10)]    # 不能用 [[]]*radix,否则相当于开了radix个完全相同的list对象
            for val in obj:
                bucket[val%(radix**i) // (radix**(i-1))].append(val)    # 这是为了得到val的第i位的数字
            obj = []
            for each in bucket:
                obj.extend(each)
        return obj

if __name__ == '__main__':
    x = input("请输入待排序数列,所有元素必须为整数:\n")
    obj = list(map(int, x.split()))
    solution = Solution()
    res = solution.RadixSort(obj)
    print(res)

总结

1.存在即有理。十种排序算法在时间、空间复杂度,实现难度,稳定性等指标上存在较大差异,但并没有最好最坏之说,适合的才是最好的。

2.三种O(n^2)平均时间复杂度的排序算法在空间复杂度、稳定性方面表现较好,甚至在特定情况下即便考虑时间复杂度也是最佳选择。

3.堆排序初始建堆过程较复杂,仅建堆时间复杂度就达到O(nlogn),但之后的排序开销稳定且较小,所以适合大量数据排序。

4.希尔排序性能看似很好,但实际上他的整体性能受步长选取影响较大,插入排序本质也使他受数据影响较大。

5.归并排序在平均和最坏情况下时间复杂度都表现良好O(nlogn),但昂贵的空间开销大O(n)。

6.快速排序大名鼎鼎,又有个好名字,但最坏情况下时间复杂度直逼O(n^2),远不如堆排序和归并排序。

7.基于比较排序的算法(如前七种)时间复杂度O(nlogn)已是下限。

8.三种线性时间复杂度排序算法虽然在速度上有决定性的优势,但也付出了沉重的空间代价,有时数据的特点让这种空间代价变得无法承受。所以他们的应用对数据本身有着特定的要求。

9.关于稳定性,希尔排序、快速排序和堆排序这三种排序算法无法保障。三种算法因为划分(子序列、大小端、左右孩子)后各自处理无法保证等值数据的原次序。

参考资料:
aiya_aiya_的CSDN博客
堆排序_Youtube,作者黄浩杰,B站搜 '正月点灯笼'
堆排序_Wikipedia
归并排序_Wikipedia
基数排序_Wikipedia

相关文章

  • 算法-选择排序

    算 法:选择排序算法时间复杂度: 选择排序算法概述 选择排序伪代码 选择排序实现 选择排序算法概述 排序算法有许...

  • 算法-冒泡排序

    算 法:冒泡排序算法时间复杂度: 冒泡排序算法概述 冒泡排序伪代码 冒泡排序实现 冒泡排序算法概述 冒泡排...

  • 排序算法

    概述 一般排序算法(以元素比较为基础) => 快速排序、归并排序、插入排序、冒泡排序、堆排序 特殊排序算法 => ...

  • 排序一:冒泡、插入、选择

    文章结构 概述 冒泡排序 插入排序 选择排序 1. 概述 常见的排序算法有:冒泡排序、插入排序、选择排序、归并排序...

  • python 排序算法

    文章概述 介绍各大常用经典的排序算法和效率,以及python实现常用算法(冒泡排序,选择排序,快速排序,插入排序)...

  • 常见十大排序算法概述

    排序算法概述 网上常见的排序算法有十种:冒泡排序、快速排序、插入排序、希尔排序、选择排序、堆排序、归并排序、计数排...

  • 算法-插入排序

    算 法:插入排序算法时间复杂度: 插入排序算法描述 插入排序伪代码 插入排序实现 插入排序算法概述 插入排...

  • 11、【排序】快速排序(1)

    1、概述 快速排序(Quick Sort)是一种高级排序算法。 快速排序算法相对来说比较复杂,因为快速排序算法所延...

  • 排序算法概述

    一、综述 二、选择排序 思想: 首先找到数组中最小的元素,其次将它和数组的第一个元素交换位置(如果第一个元素是最小...

  • 排序算法概述

    在本系列中我们综述十个常用排序算法,分别是简单插入排序、希尔排序、简单选择排序、快速排序、冒泡排序、堆排序、归并排...

网友评论

      本文标题:排序算法概述

      本文链接:https://www.haomeiwen.com/subject/aafmcktx.html