python实现几种排序算法

洛基准备把一些python 3实现的算法题总结到这儿(python大法真香)。其实洛基觉得,不管是C++,java,python,各有各的妙处,只要玩的好了,都可以写出优雅、高效的代码,实现各种各样酷炫的功能。大家也没必要争吵到底谁才是第一语言。Anyway,语言只是工具,最重要的是我们能用工具完成什么样的任务,创造什么样的价值。

于是乎立下此博,把一些常见的算法存放于此,闲时看一看,以期巩固提高。书读百遍其意自现,算法看百遍内功up up up!

——各种排序算法。

1.冒泡。和名字一样的有趣且简洁的算法。复杂度 Time(n2) 、Space(1) ,是稳定的。

# py3
def sortBubble(nums):
    n = len(nums)
    # 1 to n-1
    for i in range(1, n):
        # range(0, n-1) to range(0, 1)
        for j in range(0, n - i): 
            if nums[j] > nums[j + 1]:
                nums[j], nums[j + 1] = nums[j + 1], nums[j]
    return nums

2.选择排序,从第一个索引开始,每趟在后续的序列中找到最小的,与当前位置交换。复杂度Time(n2)、Space(1), 不稳定,比如 3 3 2 1,选择排序后第一个3到末尾去了,第二个3跑到倒数第二个位置了。

def sortSelect(nums):
    length = len(nums)
    for cur in range(length - 1):
        min_idx = cur
        for i in range(cur + 1, length):
            if nums[i] < nums[min_idx]:
                min_idx = i 
        nums[cur], nums[min_idx] = nums[min_idx], nums[cur]
    return nums

 3.希尔排序,又称为“缩小增量排序”。 第一趟选定step=length/2:从step开始,每次以一定步长和前面的元素进行比较,一直循环到数组的第一个元素;接着从step+1开始继续执行...此为一趟。第二趟则把step继续缩小一半,重复之前的步骤。 复杂度:Time(nlogn)、 Space(1)。不稳定。

def sortShell(nums):
    step = len(nums) // 2
    while step > 0:
        for i in range(step, len(nums)):
            # 因为前面的已经两两比较过了,所以当后面没有因新的更小元素而交换时,不需要再遍历i-step之前的元素
            while i >= step and nums[i] < nums[i - step]:
                nums[i], nums[i - step] = nums[i - step], nums[i]
                i -= step
        step = step // 2
    return nums

 4.归并排序,分治思想。首先将序列二分成两个子序列,然后对子序列再进行二分...直到所有子序列都只包含单个元素。然后将相邻两个序列进行归并操作,并保证归并后的序列内部有序;接着再对相邻序列进行归并,一直到只剩下一个序列为止。Time(nlogn) 、Space(1) , 稳定的。

def sortMerge(nums):
    if len(nums) <= 1:
        return nums
    # 要拆分,必须要找到中点mid
    mid = len(nums) // 2
    # 递归拆分
    left = sortMerge(nums[:mid])
    right = sortMerge(nums[mid:])

    # 归并子序列得到有序序列
    return merge(left, right)

def merge(left, right):
    result = []
    while left and right:
        if left[0] <= right[0]: # 为了排序的稳定性
            result.append(left.pop(0))
        else:
            result.append(right.pop(0))
    return result + left + right

5.快速排序(请叫它面试小常客)。T(nlogn) ,空间复杂度有两种情况,如果原地交换元素,那么空间复杂度是递归消耗的Space(logn),如果采用额外辅助数组(会很好理解),那么空间也可以达到Space(n)(夸我)。不稳定。

# 借助辅助数组的话,空间复杂度O(n)
def sortQuick(nums):
    if len(nums) <= 1:
        return nums
    small, equal, big = [], [], []
    p = nums[0]
    for num in nums:
        if num < p:
            small.append(num)
        elif num > p:
            big.append(num)
        else:
            equal.append(num)
    # 对剩余的small和big数组继续快排
    small = sortQuick(small)
    big = sortQuick(big)
    return small + equal + big


# 快速排序方法二,就地排序,空间复杂度:S(logn),是递归消耗的复杂度
# 时间仍然是T(nlogn)
# 思路:定义一个区间内快排的函数,对一个区间进行一趟快排,取pivat为区间末尾元素,
#       先取index为头元素保存最终的pivat应去的位置;扫描区间内前面n-1个元素,交换...;函数返回中间idx。
#       定义一个递归函数,先对整个数组进行一趟快排,得到中间idx,再递归对划分出来的两个子区间进行快排。
def sortQuick(nums, start, end):
    '''一趟快排'''
    # index指示小的那个值应该放到当前哪个位置
    index = start
    pivot = nums[end]
    # 在指定区间内进行一趟快排
    for i in range(start, end): # 没有检查最后一个元素,因为最后一个元素是pivat,最终index会指引它应该去的位置
        if nums[i] < pivot:
            nums[index], nums[i] = nums[i], nums[index]
            index += 1
    # 最后,把mid值放到它应放的位置上,index之前的数都小于mid,index之后的数都大于mid
    nums[index], nums[end] = nums[end], nums[index]
    return index

def mysort(nums, start, end):
    if start < end: # 如果start和end相等或者start大于end,那么不需要在这个区间快排了
        m_index = sortQuick(nums, start, end)
        mysort(nums, start, m_index - 1)
        mysort(nums, m_index + 1, end)

 6.计数排序。当输入的元素是n个0到k之间的整数时,运行时间是Time(n + k), 空间Space(k)。稳定。 当数据范围k比数组长度小很多时,此方法比较好。

def sortCount(nums):
    small, big = nums[0], nums[0]
    for x in nums:
        if x < small:
            small = x
        elif x > big:
            big = x
    # 计数数组
    count = [0] * (big - small + 1)
    for x in nums:
        count[x - small] += 1
    
    # 填值
    # index是当前填入nums的位置,每填一个值index就加一,如此遍历到nums的末尾
    index = 0
    for i, freq in enumerate(count):
        # 原数组中出现的某个值=i+small, 出现次数=freq
        x = i + small
        while freq > 0:
            nums[index] = x
            index += 1
            freq -= 1
    return nums

 7.堆排序。后面会补充。之所以暂时每做,是因为python自带的heapq包已经继承了小根堆的功能,堆排序仅仅几行代码不到就可以搞定,感觉很trick(trick是对于面试官来说的。自己用的话,heapq有多么舒适,谁用谁知道=。=)。之后等洛基手撸一个堆出来再把这里补上咯。

8.topK问题:一亿个整数,求topk(假设k=100),分析时间复杂度和空间复杂度。

第一种思路,直接做快速排序,然后取最大k个,时间复杂度Time(nlogn);个人认为,由于大数据下的快速排序不可能使用递归解决,必须用额外的辅助数组,故空间复杂度应该是Space(n)。

第二种思路,建立一个小根堆,并把这个堆的元素个数限制为k(此例中k=100),然后先从全部数据中拿出100个数装到堆里面,调整堆之后得到一个小根堆,此时堆顶元素就是整个堆中最小的了(小根堆的性质)。接着,每次从全部数据中读取一部分至内存,遍历这一小部分元素,每次都与堆顶元素进行比较,如果新的元素大于堆顶元素,那么新的元素替换掉堆顶元素,调整一下堆。如此循环,即可以在O(n)的时间内,以O(k)的额外空间(建堆的空间)完成任务了,很酷不是吗?What's more,用python的heapq包真的能很优雅的处理这个问题。朋友你觉得用python不香吗~

import heapq

class topkHeap:
    def __init__(self, k):
        self.heap = []
        self.k = k

    def push(self, num):
        if len(self.heap) < self.k:
            heapq.heappush(self.heap, num)
        else:
            heapq.heappushpop(self.heap, num)

    def topk(self):
        return sorted(self.heap, reverse=True)

# 最小的k个值,与topk异曲同工,只是会先把原数据的逐个元素取反。
class minkHeap:
    def __init__(self, k):
        self.heap = []
        self.k = k

    def push(self, num):
        if len(self.heap) < self.k:
            heapq.heappush(self.heap, -num) # 取反
        else:
            heapq.heappushpop(self.heap, -num)

    def mink(self):
        return sorted([-num for num in self.heap])

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值