面试必会:十大经典排序算法(python实现,附复杂度分析及稳定性)

时间:2021-2-27 作者:admin

十大排序算法复杂度及稳定性属性表

面试必会:十大经典排序算法(python实现,附复杂度分析及稳定性)
图片名词解释:

n: 数据规模
k: “桶”的个数
In-place: 占用常数内存,不占用额外内存
Out-place: 占用额外内存

1、冒泡排序(Bubble Sort)

冒泡排序算法的原理如下:

a. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
——
b. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
——
c. 针对所有的元素重复以上的步骤,除了最后一个。
——
d. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较

复杂度及稳定性:
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳如老狗,内排序

python实现代码:

def bubble_sort(nums):
    n = len(nums)
    # 进行多次循环
    for c in range(n):
        for i in range(1, n - c):
            if nums[i - 1] > nums[i]:
                nums[i - 1], nums[i] = nums[i], nums[i - 1]
    return nums

2、选择排序(Select Sort)

每一趟从待排序的数据元素中选出最小(最大)的元素,顺序放在待排序的数列最前,直到全部待排序的数据元素全部排完。

举例:
[4, 2, 3] 找出最小的:2,与第一个元素交换
[2, 4, 3] 找出最小的:3,与第二个元素交换
[2, 3, 4]

复杂度及稳定性:
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:链表稳定,数组不稳定,内排序

python实现代码:

def select_sort(nums):
    n = len(nums)
    for i in range(n):
        for j in range(i, n):
            if nums[i] > nums[j]:
                nums[i], nums[j] = nums[j], nums[i]
    return nums

3、插入排序(Insertion Sort)

核心思想:插入排序是前面已排序数组找到插入的位置

复杂度及稳定性:
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳如老狗,内排序

python实现代码:

def insertion_sort(nums):
    n = len(nums)
    for i in range(1, n):
        while i > 0 and nums[i - 1] > nums[i]:
            nums[i - 1], nums[i] = nums[i], nums[i - 1]
            i -= 1
    return nums

4、希尔排序(Shell Sort)

插入排序的进阶版。。。

算法描述:

我们来看下希尔排序的基本步骤,在此我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2…1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:

步骤1:选择一个增量序列 t1,t2,…,tk,其中 ti > tj,tk=1;
——
步骤2:按增量序列个数k,对序列进行k 趟排序;
——
步骤3:每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

复杂度及稳定性:
时间复杂度:O(nlogn)
空间复杂度:O(n)
稳定性:很稳定,外排序(需要额外空间)

python实现代码:

def shell_sort(nums):
    n = len(nums)
    gap = n // 2
    while gap:
        for i in range(gap, n):
            while i - gap >= 0 and nums[i - gap] > nums[i]:
                nums[i - gap], nums[i] = nums[i], nums[i - gap]
                i -= gap
        gap //= 2
    return nums

5、归并排序(Merge Sort)

归并排序,采用是分治法,先将数组分成子序列,让子序列有序,再将子序列间有序,合并成有序数组。

算法描述:

1、把长度为n的输入序列分成长度 n/2的子序列;
2、对两个子序列采用归并排序;
3、合并所有子序列。

复杂度及稳定性:
时间复杂度:O(nlogn)
空间复杂度:O(1)
稳定性:不稳定,内排序

python实现代码:

def merge_sort(nums):
    if len(nums) <= 1:
        return nums
    mid = len(nums) // 2
    # 分
    left = merge_sort(nums[:mid])
    right = merge_sort(nums[mid:])
    # 合并
    return merge(left, right)

def merge(left, right):
    res = []
    i = 0
    j = 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            res.append(left[i])
            i += 1
        else:
            res.append(right[j])
            j += 1
    res += left[i:]
    res += right[j:]
    return res

6、快速排序(Quick Sort)

快速排序是选取一个“哨兵”(pivot),将小于pivot放在左边,把大于pivot放在右边,分割成两部分,并且可以固定pivot在数组的位置,在对左右两部分继续进行排序。

快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:

步骤1:从数列中挑出一个元素,称为 “基准”(pivot );
——
步骤2:重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
——
步骤3:递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

复杂度及稳定性:
时间复杂度:O(nlogn)
空间复杂度:O(1)
稳定性:不稳定,内排序

python实现代码:

def quick_sort(nums):
    n = len(nums)

    def quick(left, right):
        if left >= right:
            return nums
        pivot = left
        i = left
        j = right
        while i < j:
            while i < j and nums[j] > nums[pivot]:
                j -= 1
            while i < j and nums[i] <= nums[pivot]:
                i += 1
            nums[i], nums[j] = nums[j], nums[i]
        nums[pivot], nums[j] = nums[j], nums[pivot]
        quick(left, j - 1)
        quick(j + 1, right)
        return nums

    return quick(0, n - 1)

7、堆排序(Heap Sort)

堆排序是利用堆这个数据结构设计的排序算法。

算法描述:

1、建堆,从底向上调整堆,使得父亲节点比孩子节点值大,构成大顶堆;
2、交换堆顶和最后一个元素,重新调整堆。

调整堆方法写了递归和迭代,都很好理解!

复杂度及稳定性:
时间复杂度:O(nlogn)
空间复杂度:O(1)
稳定性:不稳定,内排序

python实现代码:

def heap_sort(nums):
    # 调整堆
    # 迭代写法
    def adjust_heap(nums, startpos, endpos):
        newitem = nums[startpos]
        pos = startpos
        childpos = pos * 2 + 1
        while childpos < endpos:
            rightpos = childpos + 1
            if rightpos < endpos and nums[rightpos] >= nums[childpos]:
                childpos = rightpos
            if newitem < nums[childpos]:
                nums[pos] = nums[childpos]
                pos = childpos
                childpos = pos * 2 + 1
            else:
                break
        nums[pos] = newitem
    
    # 递归写法
    def adjust_heap(nums, startpos, endpos):
        pos = startpos
        chilidpos = pos * 2 + 1
        if chilidpos < endpos:
            rightpos = chilidpos + 1
            if rightpos < endpos and nums[rightpos] > nums[chilidpos]:
                chilidpos = rightpos
            if nums[chilidpos] > nums[pos]:
                nums[pos], nums[chilidpos] = nums[chilidpos], nums[pos]
                adjust_heap(nums, pos, endpos)

    n = len(nums)
    # 建堆
    for i in reversed(range(n // 2)):
        adjust_heap(nums, i, n)
    # 调整堆
    for i in range(n - 1, -1, -1):
        nums[0], nums[i] = nums[i], nums[0]
        adjust_heap(nums, 0, i)
    return nums

8、计数排序(Counting Sort)

计数排序是典型的空间换时间算法,开辟额外数据空间存储用索引号记录数组的值和数组值个数

算法描述:

1、找出待排序的数组的最大值和最小值;
2、统计数组值的个数;
3、反向填充目标数组。

复杂度及稳定性:
时间复杂度:O(n + k)
空间复杂度:O(k),对于数据范围很大的数组,需要大量时间和内存
稳定性:稳定,外排序

python实现代码:

def counting_sort(nums):
    if not nums: return []
    n = len(nums)
    _min = min(nums)
    _max = max(nums)
    tmp_arr = [0] * (_max - _min + 1)
    for num in nums:
        tmp_arr[num - _min] += 1
    j = 0
    for i in range(n):
        while tmp_arr[j] == 0:
            j += 1
        nums[i] = j + _min
        tmp_arr[j] -= 1
    return nums

9、桶排序(Bucket Sort)

桶排序是计数排序的升级版,原理是:输入数据服从均匀分布的,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的算法或是以递归方式继续使用桶排序,此文编码采用递归方式)

算法描述:

1、人为设置一个桶的BucketSize,作为每个桶放置多少个不同数值(意思就是BucketSize = 5,可以放5个不同数字比如[1, 2, 3,4,5]也可以放 100000个3,只是表示该桶能存几个不同的数值);
——
2、遍历待排序数据,并且把数据一个一个放到对应的桶里去;
——
3、对每个不是桶进行排序,可以使用其他排序方法,也递归排序;
——
4、不是空的桶里数据拼接起来;

复杂度及稳定性:
时间复杂度:O(n ^ 2)
空间复杂度:O(n + k)
稳定性:稳定,外排序

python实现代码:

def bucket_sort(nums, bucketSize):
    if len(nums) < 2:
        return nums
    _min = min(nums)
    _max = max(nums)
    # 需要桶个数
    bucketNum = (_max - _min) // bucketSize + 1
    buckets = [[] for _ in range(bucketNum)]
    for num in nums:
        # 放入相应的桶中
        buckets[(num - _min) // bucketSize].append(num)
    res = []

    for bucket in buckets:
        if not bucket: continue
        if bucketSize == 1:
            res.extend(bucket)
        else:
            # 当都装在一个桶里,说明桶容量大了
            if bucketNum == 1:
                bucketSize -= 1
            res.extend(bucket_sort(bucket, bucketSize))
    return res

10、基数排序(Radix Sort)

基数排序是对数字每一位进行排序,从最低位开始排序

算法描述:

1、找到数组最大值,得最大位数;
2、从最低位开始取每个位组成radix数组;
3、对radix进行计数排序(计数排序适用于小范围的特点)。

复杂度及稳定性:
时间复杂度:O(n * k)
空间复杂度:O(n + k)
稳定性:稳定,外排序

python实现代码:

def Radix_sort(nums):
    if not nums: return []
    _max = max(nums)
    # 最大位数
    maxDigit = len(str(_max))
    bucketList = [[] for _ in range(10)]
    # 从低位开始排序
    div, mod = 1, 10
    for i in range(maxDigit):
        for num in nums:
            bucketList[num % mod // div].append(num)
        div *= 10
        mod *= 10
        idx = 0
        for j in range(10):
            for item in bucketList[j]:
                nums[idx] = item
                idx += 1
            bucketList[j] = []
    return nums

参考来源:https://leetcode-cn.com/problems/sort-an-array/solution/python-shi-xian-de-shi-da-jing-dian-pai-xu-suan-fa/

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。