剑指Offer_编程题29:最小的K个数

题目:输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

牛客网:链接

参考链接:点我呀  点我呀

第一种方法:快排。这个思路应该是最简单的,将整个数组排序,然后取出前k个数据就可以了,这个算法的时间复杂度为nlogn

# -*- coding:utf-8 -*-
class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        # write code here
        length = len(tinput)
        if not tinput or k < 0 or k > length:
            return []
        start = 0
        end = length - 1
        self.partition(tinput, start, end)
        return tinput[:k]

    def partition(self, tinput, start, end):
        if end <= start:
            return start
        base = tinput[start]
        index1, index2 = start, end 
        while start < end:
            ''' 从右往左遍历 如果比base大 就接着遍历 直到比base小 '''
            while start < end and tinput[end] >= base:
                end -= 1
            ''' 比base小都赋值给左指针 '''
            tinput[start] = tinput[end]
            ''' 从左往右遍历 如果比base小 就接着遍历 直到比base大 '''
            while start < end and tinput[start] <= base:
                start += 1
            ''' 比base大都赋值给右指针 '''
            tinput[end] = tinput[start]
        '''将base赋值给重合位置'''
        tinput[start] = base
        self.partition(tinput, index1, start-1)
        self.partition(tinput, start+1, index2)

if __name__ == '__main__':
    a = Solution()
    print(a.GetLeastNumbers_Solution([4,23,23,1], 2))       

第二种方法:是在第一种解法上面的一种改进,快速排序的思想大家都已经知道,现在我们只需要最小的k个数,所以如果我们在某次快速排序中,选择的基准的大小刚好是整个数组的第k小的数据,那么在这次排序完成之后,这个基准数之前的数据就是我们需要的(尽管他们并不是有序的)。这个方法同样改变了数组,但是可以将时间复杂度压缩到O(n),只有当我们可以修改输入的数组时可用。

# -*- coding:utf-8 -*-
class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        # write code here
        length = len(tinput)
        if not tinput or k <= 0 or k > length:
            return []
        start = 0
        end = length - 1
        index = self.partition(tinput, start, end)
        while index != k-1:
            print(index)
            print(k-1)
            if index > k-1:
                index = self.partition(tinput, start, index-1)
            elif index < k-1:
                index = self.partition(tinput, index+1, end)
        return sorted(tinput[:k])

    def partition(self, tinput, start, end):
        if end <= start:
            return start
        base = tinput[start]
        while start < end:
            while start < end and tinput[end] >= base:
                end -= 1
            tinput[start] = tinput[end]
            while start < end and tinput[start] <= base:
                start += 1
            tinput[end] = tinput[start]
        tinput[start] = base
        return start

第三种方法(海量数据):创建一个大小为k的数据容器来存储最小的k个数字,接下来每次从输入的n个整数中读入一个数。如果容器中已有的数字少于k个,则直接把这次读入的整数放入容器之中;如果容器中已有k个数字了,也就是容器已满,此时我们不能再插入新的数字而只能替换已有的数字。找出这已有的k个数中的最大值,然后拿这次待插入的整数和最大值进行比较,如果待插入的值比当前已有的值小,则用这个数替换当前已有的最大值;如果待插入的值比当前已有的最大值还要大,那么这个数不可能是最小的k个整数之一,于是我们抛弃这个整数。我们可以使用最大堆来实现这个容器,在最大堆中,根节点的值总是大于它的子树中任意节点的值。于是我们可以在O(1)时间内得到已有的k个数字中的最大值,但需要O(logk)时间完成删除及插入操作。建堆时间是O(k),调整一次的时间O(logk),一共调整k次,所以堆排序的时间是O(klogk)。

堆排序:点我

# -*- coding:utf-8 -*-
def HeadAdjust(input_list, parent, length):
    '''
    函数说明:堆调整,调整为最大堆
    Parameters:
        input_list - 待排序列表
        parent - 堆的父结点
        length - 数组长度
    ''' 
    temp = input_list[parent]   # temp保存当前父节点
    child = 2 * parent + 1      # 先获得左孩子

    while child < length:
        # 如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点
        if child + 1 < length and input_list[child] < input_list[child+1]:
            child += 1
        # 如果父节点的值比孩子结点大 跳出这次循环
        # 一定要用temp比 因为在没有最终调整好之后 temp是不能赋值给结点的
        if temp >= input_list[child]:
            break
        # 把孩子结点的值赋给父结点
        input_list[parent] = input_list[child]
        # 选取孩子结点的左孩子结点,继续向下筛选
        parent = child
        child = 2 * parent + 1
    '''把最初的父节点赋值给经过筛选之后的父节点'''
    input_list[parent] = temp

def HeadSort(input_list):
    if len(input_list) == 0:
        return []
    sorted_list = input_list
    length = len(sorted_list)
    '''循环建立初始最大堆 length//2-1是最后一个非叶子结点 '''
    for i in range(0, length // 2)[::-1]:
        HeadAdjust(sorted_list, i, length)
    '''进行n-1次循环,完成排序'''
    for j in range(1, length)[::-1]:
        # 最后一个元素和第一元素进行交换
        sorted_list[0], sorted_list[j] = sorted_list[j], sorted_list[0]
        # 筛选 R[0] 结点,得到i-1个结点的堆
        HeadAdjust(sorted_list, 0, j)
        print('第%d趟排序:' % (length - j), end = '')
        print(sorted_list)

    return sorted_list

if __name__ == '__main__':
    input_list = [6, 4, 8, 9, 2, 3, 1]
    print('排序前:', input_list)
    sorted_list = HeadSort(input_list)
    print('排序后:', sorted_list)

最小的k个数(nlogk):

# -*- coding:utf-8 -*-
class Solution:
    def HeadAdjust(self, input_list, parent, length):
        temp = input_list[parent]
        child = 2 * parent + 1
        while child < length:
            if child + 1 < length and input_list[child+1] > input_list[child]:
                child += 1
            if temp >= input_list[child]:
                break
            input_list[parent] = input_list[child]
            parent = child
            child = 2 * parent + 1
        input_list[parent] = temp

    def GetLeastNumbers_Solution(self, tinput, k):
        # write code here
        if not tinput or k <= 0 or k > len(tinput):
            return []
        '''选取k个数构建堆'''
        sorted_list = tinput[:k]
        '''构建最大堆'''
        for i in range(k//2)[::-1]:
            self.HeadAdjust(sorted_list, i, k)
        for j in tinput[k:]:
            cur_max = sorted_list[0]
            '''如果值比最大堆值小,重新构建最大堆'''
            if j < cur_max:
                sorted_list[0] = j
                self.HeadAdjust(sorted_list, 0, k)
        '''堆排序'''
        for j in range(1, k)[::-1]:
            sorted_list[0], sorted_list[j] = sorted_list[j], sorted_list[0]
            self.HeadAdjust(sorted_list, 0, j)
        return sorted_list

if __name__ == '__main__':
    a = Solution()
    print(a.GetLeastNumbers_Solution([4,5,1,6,2,7,3,8], 4))

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值