快速排序详解

主要是通过递归的方法,快排的基本原理就是先选定一个基准,然后把数组中全部小于这个数的放到基准的前面,大于这个数的放到基准的后面。
每次递归的输出结果分成了三部分,再叠加成一个数组,分别是小于基准的数组,基准,大于基准的数组。然后再对分出来的数组不断进行快排,直到只剩下一个元素的时候,直接返回。 注意不能数组+数字,所以返回的基准要放在数组里!

算法的时间复杂度是nlogn
时间复杂度的解释,可以知道我们快排的解决办法就是分治法,每一次根据基准划分为两部分,相当于二叉树,如下图所示,并且我们知道二叉树的深度是logn,因为每一层除了第一层等于N之外,其他都是小于N,暂且记为CN,所以总的时间复杂度是NlogN。只要不是[1,2,3,4,5]这种已经排好序的情况的最坏时间复杂度是O(N*N),这种情况可以打乱顺序来优化避免这种情况。

快排里面partition耗费的时间复杂度是O(N)。最终的O(NLOGN)其实是计算来的。算的平均情况,其实很明显 最差的情况是----每次分出来就是N-1和1个,相当于原本就是有序的序列来快排:O(N) = N + O(N-1) = N+N-1+O(N-2)= …= O(N*N)。最好的情况是O(NLGN),因为每次算的是平均划分的情况: 其实关键就是划分次数少了,从上述的N次划分降到了LOGN次划分,这就是关键之处。

满二叉树深度为logN,如何计算:
1、每一层的节点数目是上一层的2倍,也就是2,4,8,16
2、计算等比数列的和,可以得到深度为log2(N)
在这里插入图片描述

下面是基准划分的情况
1.最好的情况
基准划分之后,左右两边的规模都不大于n/2。这样的快排性能最好。一个是n/2(向下取整),一个是n/2-1(向上取整)。
T(n) = *T(n/2)2 + O(n) = O(nlgn)。

2.平均的情况:
平均的运行时间接近最好的情况。总代价也是O(nlgn),只要是常数比例的情况,就是O(nlgn)

为什么?举例:
下面是1:9的情况: 从n, 9/10n, (9/10)^2n… 可以看到,最后到1,计算有多少层,故 (9/10)^d * n= 1
所以, 深度d为log10/9 n---------

设想,如果此时划分的不是9/10,而是接近于1,因为如果每次都是最坏的情况,那么其实就是1,则时间复杂度就是n,那么乘以每一层n,就是n^2

在这里插入图片描述

3.最坏的情况
划分之后,一个是n-1个元素,一个是0个元素。划分操作的时间复杂度是O(n),所以总和是:T(n) = T(n-1) + T(0) + O(n) = T(n-1) + O(n) = … = n×O(n) = O(n^2)。
如果划分平衡,快排的性能与归并算法一样;如果不平衡则接近插入排序

1. 算法导论-快排

快排的期望时间复杂度以及平均时间复杂度是O(nlogn),最坏时间复杂度是O(n^2)。

后面说的随机选择快排(quick select)的期望时间复杂度以及平均时间复杂度是O(nlogn),最坏时间复杂度是O(n^2)。

注意,写的版本中,partition都是以最后一个为基准的,否则需要考虑更多的问题。所以在quick sort的过程中,也就是在搜索之前,先将随机选择的inedx与最后一个index交换值。

基本思想是选定最后一个数作为基准,然后**小于该数的交换(倘若还没有出现大于基准的数,就是与本身交换;如果出现了,就是与大于基准的数进行交换),大于该数的不动。**最后将基准交换到数组中。
在这里插入图片描述
在这里插入图片描述

def quicksort(arr, p, q):
    if p < q:
        if not arr[p:q]:  # j+1是0的时候,J=-1会导致出错,所以需要 p<q
            return []
        x = arr[q]
        j = p-1
        for i in range(p,q):
            if arr[i] <= x:
                j += 1
                arr[j], arr[i] = arr[i], arr[j]
        arr[j + 1], arr[q] = arr[q], arr[j + 1]  
        quicksort(arr, j + 2, q)
        quicksort(arr, p, j)
    return arr

2. 算法导论

import random

def partition(A, p, r):
    
    # 快排优化
    choose_index = random.randint(p, r)
	A[choose_index], A[r] = A[r], A[choose_index]
	
	x = A[r]
    i = p-1
    for j in range(p, r):
        if A[j] <= x:
            i += 1
            A[i], A[j] = A[j], A[i]
    A[i+1], A[r] = A[r], A[i+1]
    return i+1  # 标准的位置
 # partition的作用是可以返回任意第k个已经找好位置的值的序号,并且,这个值已经在arr里的相应位置。
def quicksort(A, p, r):
    if p < r: # 因为后面的减1,到了0之后,仍然继续,就是负的,此时到了list的尾部。
        q = partition(A, p, r)
        quicksort(A, p, q-1)
        quicksort(A, q+1, r)

理解时间复杂度nlogn,栈的高度是logn,每一层处理的元素的个数是n,所以相乘是nlogn.

quick select

时间复杂度是O(N):
假设最好的情况,则每次划分是1/2
每一次的划分的时间复杂度是 N、N/2、 N/4… 1总的时间复杂度就是O(2N)
=====N*(1-1/2 ** n) / (1-1/2) = 2 * N

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4

class Solution(object):
    def findKthLargest(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: int
        """
        # 粗心致命,一遍过   注意快排用  random来优化
        def quick_select(nums, index1, index2):

            def partition(nums, index1, index2):

                record = index1 - 1  # 记录比index2大的

                # 快排优化:随机选择,注意把随机之后的序号与最后一个交换,不能以随机的作为基准。
                chooose_num = random.randint(index1, index2)
                nums[index2], nums[chooose_num] = nums[chooose_num], nums[index2]
                cmp_num = nums[index2]
                #cmp_num = nums[chooose_num]


                for i in range(index1, index2+1):
                    if nums[i] <= cmp_num:
                        record += 1
                        nums[record], nums[i] = nums[i], nums[record]


                return record

            if index1 < index2:
                index = partition(nums, index1, index2)
                if index > len(nums) - k:
                    quick_select(nums, index1, index-1)
                elif index < len(nums) - k:
                    quick_select(nums, index+1, index2)
                else:
                    return 
            return nums

        quick_select(nums, 0, len(nums)-1)
        return nums[len(nums) - k]
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值