数据结构与算法-基础排序算法及TopK问题(Python)

基础排序算法

如果在面试中遇到排序算法,先问清楚数据的特点,结合具体的业务场景,多和面试官交流,先陈述思路,得到面试官肯定以后再编码
没有一个排序算法是任何情况下都表现最好的。
学习算法的可视化网站:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

常见排序算法对比
在这里插入图片描述
所有代码和思路以升序为例。

冒泡排序

思路:

  • 每一轮依次比较相邻的两个元素,如果前一个元素>后一个,则交换位置,保证未排定部分的最大元素置于数组末尾;
  • 重复上述步骤,直到第2个元素是前两个元素中最大的,排序结束;

优化:
通过flag标识已经有序的情况,提前停止排序。

Python实现:

class Solution:
    def sortArray(self, nums: List[int]) -> List[int]:
        if len(nums)<2:
            return nums
        
        flag = True
        n = len(nums)
        for i in range(n,0,-1):
            for j in range(1,i):
                if nums[j-1]>nums[j]:
                    flag = False
                    nums[j-1],nums[j] = nums[j],nums[j-1]
            if flag:
                break
        return nums

复杂度分析:
时间复杂度:O(n2),加上flag最优的情况即本身有序,O(n);
空间复杂度:O(1),原地操作,不需要额外的空间。

选择排序

思路:

  • 每轮选出未排定部分中最小的元素,交换到未排定部分的开头,交换一次;

Python实现:

class Solution:
    def sortArray(self, nums: List[int]) -> List[int]:
        if len(nums)<2:
            return nums
        
        n = len(nums)
        for i in range(n):
            min_idx = i
            for j in range(i+1,n):
                if nums[j]<nums[min_idx]:
                    min_idx = j
            nums[i],nums[min_idx] = nums[min_idx],nums[i]

        return nums

复杂度分析:
时间复杂度:O(n2);
空间复杂度:O(1),原地操作,不需要额外的空间。

优点: 交换次数最少
在交换成本较高的排序任务中,可以使用选择排序。

插入排序

思路:

  • 保持当前元素左侧是排序后的数组,将当前元素插入到对应位置。

Python实现:

class Solution:
    def sortArray(self, nums: List[int]) -> List[int]:
        if len(nums)<2:
            return nums
        
        n = len(nums)
        for i in range(1,n):
            j = i 
            while j>0 and nums[j]<nums[j-1]:
                nums[j],nums[j-1] = nums[j-1],nums[j]
                j -= 1

        return nums

复杂度分析:
时间复杂度:O(n2),最好的情况即数组有序时,复杂度为O(n);
空间复杂度:O(1),原地操作,不需要额外的空间。

优化
可以采用二分查找的方式来确定需要插入的位置

优点:
内层循环可以提前终止,因此插入排序在几乎有序的数组上表现良好,在短数组(每个元素离它最终排定的位置都不会太远)上表现也很好。

归并排序

思路:

  • 分而治之的思想,理解递归的好材料,递归时,只剩下一个元素或没有元素时返回;
  • 借助额外空间,合并两个有序数组,得到更长的有序数组。

Python实现:

class Solution:
    def sortArray(self, nums: List[int]) -> List[int]:
        if len(nums)<2:
            return nums
  
        self.mergesort(nums,0,len(nums)-1)
        return nums
    
    def merge(self,nums,left,mid,right):
        temp = []
        i = left
        j = mid+1
        while i<=mid and j<=right:
            if nums[i]<=nums[j]:
                temp.append(nums[i])
                i += 1
            else:
                temp.append(nums[j])
                j += 1
        while i<=mid:
            temp.append(nums[i])
            i += 1
        while j<=right:
            temp.append(nums[j])
            j += 1
        return temp

    def mergesort(self,nums,left,right):
        if left>=right:
            return
        mid = (left+right)//2
        self.mergesort(nums,left,mid)
        self.mergesort(nums,mid+1,right)
        temp = self.merge(nums,left,mid,right)
        for j in range(len(temp)):
            nums[left+j] = temp[j]

复杂度分析:
时间复杂度:确定的O(nlogn)
空间复杂度:O(n),借助了额外空间,可以实现稳定排序

缺点:
需要额外的空间,元素来回复制;
一般不用于内排序。

优化:
只开一个temp数组,避免频繁申请、释放内存。

快速排序

思路:

  • 每论都排定一个元素(主元pivot),然后递归地去排它左侧和右侧的部分,直到数组有序
    Tips:
    随机化选择切分元素,否则在输入数组是有序或逆序时,快速排序会非常慢;
    递归到规模充分小时,停止递归,直接用简单排序(如插入排序)。

Python实现:

## 方法一:
class Solution:
    def sortArray(self, nums: List[int]) -> List[int]:
        if len(nums)<=1:
            return nums
        
        def partition(left,right):
            pivot = nums[left]
            index = left
            for i in range(left+1,right+1):
                if nums[i]<pivot:
                    index += 1
                    if i!=index:
                        nums[i],nums[index] = nums[index],nums[i]
            nums[left],nums[index] = nums[index],nums[left]
            return index
        
        def quicksort(low,high):
            if low<high:
                idx = partition(low,high)
                quicksort(low,idx-1)
                quicksort(idx+1,high)
            return

        quicksort(0,len(nums)-1)
        return nums
## 方法二:
def quickSort(self,nums,left,right):
        print(left,right,nums)
        if left>=right:
            return
        mid = (left+right)//2
        if nums[mid]<nums[left]:
            nums[mid],nums[left] = nums[left],nums[mid]
        if nums[right]<nums[left]:
            nums[right],nums[left] = nums[left],nums[right]
        if nums[mid]>nums[right]:
            nums[mid],nums[right] = nums[right],nums[mid]
        
        nums[mid],nums[right-1] = nums[right-1],nums[mid]
        #if right-left<=2:
        #    return
      
        pivot = nums[right-1]
        i = left
        j = right-1
        while i<j:
            i += 1
            j -= 1
            while nums[i]<pivot:
                i += 1
            while nums[j]>pivot:
                j -= 1
            if i<j:
                nums[i],nums[j] = nums[j],nums[i]
            else:
                break

        nums[i],nums[right-1] = nums[right-1],nums[i]
        print(nums)
        self.quickSort(nums,left,i-1)
        self.quickSort(nums,i+1,right)

复杂度分析:
时间复杂度:O(nlogn),最坏情况下O(n2)
空间复杂度:O(logn)

经典问题:TopK

TopK 是面试常考的问题
「力扣」第 215 题:数组中的第 K 个最大元素

堆排序

思路:
堆是对选择排序的优化,通过把未排定部分构建成堆,可以实现O(logn)选择最大的元素。

两个特性:

  1. 结构性:用数组表示的完全二叉树;
  2. 有序性:任一结点的关键字是其子树所有结点的最大值(或最小值)
    maxheap:最大堆,又叫大顶堆

python:heapq模块

Python实现:
维护一个k大小的小顶堆,顶部就是第k个大的元素,如果是取最大的k个元素一般直接用大顶堆;
heap数组大小设置为k+1,从下标为1开始存储元素,则下标为p的结点其左右子结点分别为2p/2p+1,其父节点为p//2。

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        if not nums or len(nums)<k:
            return -1
        
        heap = [0 for _ in range(k+1)]
        for i in range(k):
            heap[i+1] = nums[i]
        
        ## build the heap
        for i in range(k//2,0,-1):
            self.percdown(i,k,heap)
        ## insert
        for i in range(k,len(nums)):
            if nums[i]>heap[1]:
                heap[1] = nums[i]
                self.percdown(1,k,heap)
        return heap[1]

    def percdown(self,i,k,heap):
        while i*2<=k:
            if i*2+1<=k and heap[i*2+1]<heap[i*2]:
                Min = i*2+1
            else:
               Min = i*2
            if heap[i]>heap[Min]:
                heap[i],heap[Min] = heap[Min],heap[i]
            i = Min 

复杂度分析:
时间复杂度:O(nlogk)
空间复杂度:O(k)

空间复杂度与k相关,适用于海量的数据,不需要一次性完全读取。

快速排序

# TopK
class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        if len(nums)<k:
            return -1
        
        small = len(nums)-k

        def partition(left,right):
            pivot = nums[left]
            index = left
            for i in range(left+1,right+1):
                if nums[i]<pivot:
                    index += 1
                    if i!=index:
                        nums[i],nums[index]=nums[index],nums[i]
            nums[left],nums[index] = nums[index],nums[left]
            if index==small:
                return 
            elif index<small:
                partition(index+1,right)
            else:
                partition(left,index-1)
        
        partition(0,len(nums)-1)
        return nums[small]

复杂度分析:
时间复杂度:O(n),根据主定理计算可得;
空间复杂度:O(logn),递归栈空间

参考资料
https://leetcode-cn.com/problems/sort-an-array/solution/dang-wo-tan-pai-xu-shi-wo-zai-tan-xie-shi-yao-by-s/
https://leetcode-cn.com/problems/sort-an-array/solution/fu-xi-ji-chu-pai-xu-suan-fa-java-by-liweiwei1419/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值