几种排序算法实现方式及代码(python语言)

写在前面,看这篇博客前需要你有一点的排序算法知识,我只是贴一下代码理一下思路,不会从零开始讲排序算法。

1.快速排序,说一下两种不同的思路和代码

快速排序的原理我就不说了,直接上代码。

def quick_sort(li,left=None,right=None):
    if left == None or right == None:
    #在第一次使用函数时不必要传入列表的头尾索引值
        left = 0
        right = len(li)-1
    if left < right:
        mid_post = Partition(li, left, right)   #根据某个特定值将数组重新排序并分为两部分
        #分别进行递归
        quick_sort(li,left = left,right = mid_post-1)
        quick_sort(li,left = mid_post+1,right = right)
    return li
    
if __name__ == "__main__":
    li = [2,8,7,1,3,5,6,4]
    print(quick_sort(li))

先看主体代码,在第七行,我调用了一个Partition函数,两个方式的不同就是在这个函数上,这个函数的功能是在给定的数组范围中选一个特定值,并且依次为比较对象将数组分为左右两部分(左边小,右边大),大概说来就是 两侧夹击从左至右 循环方式的不同,接下来我用两种方法编写Partition函数并解释。
第一种方式:
定义两个指针,一个为 i = -1,一个为 j = 0,从头开始循环数组,若是比特定值小,将这个数和 i + 1位置的数交换(要是一上来就小相当于把自己和自己交换),然后将 i 和 j 各自加一,若是比特定值大,直接把 j 自加 1,直到数组循环完毕,这样保证了 i 指针左边的都是小于特定值的,i 和 j 之间的都是大于特定值的,最后 i 指向的位置就是特定值的位置。工作方式如下图,
第一种方式的工作原理图
代码实现如下:

def Partition(li, left, right):
    pivotkey = li[right]
    low ,high = -1,0
    while high <= right:
        if li[high] > pivotkey:
            high += 1
        else:
            low += 1
            li[low],li[high] = li[high],li[low]
            high += 1
    return low

另一种常见的方式:
左右两个指针一开始指向两端,在循环中开始向中间靠拢,这两个指针相遇时表明数组循环完毕并且相遇的位置就是特定值的位置,左边都是比特定值小的数,右边相反。
先从右边开始,遇到比特定值小的数就把这个值放到左边指针的位置,接着将左边指针往中间移动,遇到大的数再把数放到右边指针的位置,然后移动右边指针,直到左右两个指针相遇。返回中间值的位置。
代码如下:

def Partition(li, left, right):
    pivotkey = li[left]
    while left < right:
        while left < right and li[right] >= pivotkey:
            right -= 1
        li[left] = li[right]
        while left < right and li[left] <= pivotkey:
            left += 1
        li[right] = li[left]
    li[left] = pivotkey
    return left

感兴趣的可以直接拷贝分别运行一下这两个版本的Partition。

2.归并排序

先将一整个数组不断拆分,最后汇总的时候进行排序,时间复杂度与快排差不多,但是需要额外的空间复杂度,代码如下:

def merge_sort(nums,left,right):
    if left<right-1:
        mid = (left + right)//2
        merge_sort(nums,left,mid)
        merge_sort(nums,mid,right)
        merge(nums,left,mid,right)

def merge(nums,left,mid,right):
    new_nums = []
    i,j = left,mid  #两个指针
    while i<mid and j<right:
        if nums[i]<=nums[j]:
            new_nums.append(nums[i])
            i+=1
        else:
            new_nums.append(nums[j])
            j+=1
    while i<mid:
        # 当j先循环完后,再把i指针剩余的数添加进去,while可改为if判断加for循环
        new_nums.append(nums[i])
        i+=1
    while j<right:
        # 当i先循环完后,再把j指针剩余的数添加进去
        new_nums.append(nums[j])
        j+=1
    for k in range(left,right):
        nums[k] = new_nums[k-left]
        
if __name__ == "__main__":
    li = [1,4,2,3,6,8,0,9,12,5,78,234]
    merge_sort(li,0,len(li))
    print(li)

3.堆排序

个人感觉是思路比较复杂的一种排序方法,你得先明白什么是二叉树,排序原理我懂,但是凭我的口才讲不清楚,这里偷个懒,直接引用一下别人的讲解,后续有时间的话自己写一下
(简书)堆排序原理及讲解
https://www.jianshu.com/p/0d383d294a80
对了,我记得LeetCode上面有个题,就是求第K大的数怎么求,题目的大概意思就是给定一个数组,让你求出这个数组中第K大的数是哪个,比如输入nums = [1,3,5,7,9,2,4,6,8,0],K = 3,输出结果为7(解释:7是这个数组中第三大的数),想解决这个问题,一种方法是将数组全部排序,然后返回第K大的数,还有就是用冒泡排序等,排到第K个就点到为止,不必全部排序,还有比较高逼格的就是利用我们的堆排序来解决了。
使用堆数据结构,维护一个数量为K的小顶堆,先把前K个数建成一个小顶堆,然后从第K个数开始然后循环数组,遇到比堆顶更大的数就加进去替换堆顶,然后进行堆维护保持堆是一个小顶堆,循环完后将堆顶得数返回即可。
把堆排序弄明白后可以尝试做一下这个题。下面贴出我的(没有使用heapq模块):

# 找出第k大的数
def k_max(nums,k):
    for low in range(k//2-1,-1,-1):
        maintain(nums,low,k)
    for i in range(k,len(nums)):
        if nums[i]>nums[0]:
            nums[i],nums[0] = nums[0],nums[i]
            maintain(nums,0,k)
    return nums[0]
    
def maintain(li,low,high):
    tmp = li[low]
    i = low 
    j = 2 * i + 1
    while j <= high:
        if j + 1 <= high and li[j+1] < li[j]:
            j += 1
        if li[j] < tmp:
            li[i] = li[j]
            i = j
            j = 2 * i + 1
        else:
            break
    li[i] = tmp

nums = [9,3,4,7,1,2,8,0,5,6]
print(k_max(nums,3))

如果使用python自带的heapq,代码就更简单了,比如输出前三大的数:

from heapq import nlargest

nums = [-3, 22, 45, 34, 99, 102, -44]
print(nlargest(3,nums)[-1])

nlargest函数返回的是nums里前三大的数,是一个数组,取最后一个即可。

4.基数排序

这个方法不是很难,思路倒是挺有意思的,速度在某些时候甚至比上面的几种还要快很多,但是适用范围局限性太大,先说一下基数排序的思路吧。
比如有个数组nums=[62, 88, 58, 47, 62, 35, 73, 51, 99, 37, 93]要排序
我们先创建十个容器(或者叫桶),每个桶的编号是0-9,根据nums里每个数的末尾来分组,按照数组里出现的顺序,末尾是几就放到相对应的编号桶里,第一次分组完后是这个样子:
在这里插入图片描述
然后再按照顺序出桶放回原数组,原数组就变成了这样:
在这里插入图片描述
末尾排完了,然后按照倒数第二位(十位)数在进行一次上边的操作:
在这里插入图片描述
这样下来两趟就完成了排序,但是不稳定,而且很明显循环的次数跟数组里最大的数有关系,上面的数组都是两位数的,所以循环了两次,如果有一个三位数的,或者四位数的,就得再接着循环,如果数组里就两个数,像这样nums=[1,14123234546546546565],短板就很明显了,所以说局限性很大,比如统计某个小区里住户的年龄并且排序,绝大部分年龄都在0-100这个区间里,用基数排序就比较适合,所以基数排序适合小范围的、数据分布密集型的这种工作,在这种条件下它比上面的三种都是要快的(理论上是的)。下面贴出python实现的代码:

def redix_sort(nums):
    max_num = max(nums)    #找出最大数
    i = 0
    while 10**i <= max_num:  #按照最大数的位数进行循环
        buckets = [[] for _ in range(10)]   #创建十个桶
        for num in nums:        #按照顺序进桶
            end_digit = num // (10**i) % 10
            buckets[end_digit].append(num)
        nums.clear()
        for bucket in buckets:     #按照顺序出桶
            for num in bucket:
                nums.append(num)
        i += 1
    return nums

print(redix_sort([62, 88, 58, 47, 62, 35, 73, 51, 99, 37, 93]))
  • 10
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值