c语言输入四个数排序数组_leetcode刷题(三):排序算法(快速排序)

846551b36efc483e110d7ba0a728a6bf.png

今天,我们要来讲讲排序算法中的快速排序,说到快速排序,这可是大家以后在笔试面试中都绕不过去的一道坎,连快排都不会,那笔试基本就凉凉了。所以我们开始吧!~!

快排:

对于包含n个数的输入数组来说,快排是一种最坏情况时间复杂度O(n^2)的排序算法,虽然快排在最坏情况时间复杂度很差,但是它是实际排序运用中最好的选择,因为它的平衡性能特别好。

快排的基本思想就是通过一趟排序将待排序的序列分成两块独立的序列,其中一块序列的子序列均比另一块序列的子序列要大,重复多次,就能达到整个序列有序的目的。

和归并排序一样,快排也使用了分治思想,下面我们对一个典型的子数组A[p……r]来进行散步分治思想的过程:

分解:数组A[p……r]被分成两个(可能是空集)数组,A1[p……q]和A2[q……r],其中A1中每个元素都小于q,而A2中每个元素都大于q。

下面我用伪代码来进行更好的说明:

QOICKSORT(A,p,r)
  if p < r
    q = PARTITION(A,p,r)
    QUICKSORT(A,p,q-1)
    QUICKSORT(A,q+1,r)

其中,PARTITION是调用的关键,他实现了对子数组A[p……r]的原址重排,下面是PARTITION的伪代码:

PARTITION(A,p,r).
  x = A[r]
  i = p-1
  for j = p to r-1
    if A[j] <= x
      i += 1
      exchange A[i] with A[j]
    j += 1
  return i + 1

在这个过程中,PARTITION维护了四个区,分别是[p,i]:这个区间的元素都<=x;[i,j]:这个群区间的元素都>=x;[j,x]:这个区间的元素可能属于任何一种情况;[x]:x的区间。

简单来讲,在PARTITION过程中,会出现两种情况:

  1. A[j]>x,需要做的就是j+1,继续循环;
  2. A[j]<x,此时需要将A[i+1]和A[j]交换,然后j+1即可,此时,循环不变量得到保持。

再通俗地讲,就是将选取的pivotkey不断交换,将比它小的换到左边,比它大的换到右边,在这个过程中自己也不断移动,知道满足这个要求位置。

所以在终止时,j=r,我们得到了三个集合,分别是包含所有大于x的集合,所有包含小于x的集合,以及元素x的集合。

下面我们来来聊一下快速排序的性能:

快速排序运行时间依赖于排序是否平衡,而平衡与否又依赖于用于划分的元素。如果划分平衡,那快速排序的算法性能和归并排序一样,如果划分不平衡,那快速排序的算法性能接近于插入排序了。

最坏情况的划分:

当划分的两个子数组里面存在0和n-1时,最坏划分情况就发生了,此时算法运行时间的递归式是T(n) = T(n-1) + T(0) + O(n) = T(n - 1) + O(n)此时解为O(n^2)

因此,如果在算法的每一层递归上,划分都是最大程度的不平衡,那么,算法的时间复杂度是O(n^2),也就是说,在最坏的情况下,快速排序的运行时间并不比插入排序好,此外,当输入数组已经完全有序的时候,快速排序的时间复杂度依然是O(n^2),而在同样的情况下,插入排序的时间复杂度为O(n)。

最好情况的划分:

在最平衡的划分当中,PARTITION得到的两个子数组的规模都不大于n/2,一个数组规模为[n/2],另一个数组的 规模为[n/2]-1,那么快排的性能就会特别好,算计你发运行时间的递归式是T(n) = 2T(n/2) + O(n/2),此时解为O(nlgn),通过在每一层上都进行平衡划分,我们得到了一个渐进时间上更快的算法。

快排的运行时间更接近于最好的情况,而非最坏的情况,事实上,任何一种常熟比例的划分都会产生深度为O(lgn)的递归树,其中每一层的时间代价都是O(n),只要划分的是常数比例的,算法的运行时间都是O(nlgn)

从直观上看,差的划分代价O(n-1)可以被吸收到好的划分代价O(n),从而得到的划分代价也挺好的。因此,当好的划分代价和差的交替出现的时候,快速排序的时间复杂度和全是好的时候划分是一样的,还是O(nlgn)。

因此我们可以得出结论,在使用PANDOMIZED-PARTITION,在输入元素互异的情况下,快速排序的期望时间复杂度还是O(nlgn)。

这就完了吗?当然没有啦,根据往年经验,面试官大概率会问问你怎么优化的问题,那我们就来谈谈怎么优化快排把!~!

1.优化选取枢轴

前面我们说过的最好情况和最坏情况的区别,就是选区枢轴的不退缩造成的。所以我们可以优化选区枢轴,我们之前的代码选取是固定地选择第一个关键字,但太大或者太小都会影响效率。所以我们就有了三数取中法,即随机取三个关键字进行排序,然后将中间数所谓枢轴,一般我们选区左端,右端和中间三个数。这样至少中间的书不是最大和最小数。可以自己打一打代码。

三数取中对小数组来说有很大的概率选择到一个较好的pivotkey,但是对于大型的数组来说还不够,所以进一步还有九数取中,取三次样本,每一次都去三个,每三个样本的中数中再取出一个中数当作枢轴。

2.优化不必要的交换

我们将pivotkey备份到A[0]中,然后之前是swap时,我们只需要做替换的工作,最终A[i]和A[j]融合,再将A[0]位置的数值赋值回A[i]。因为这里少了多次交换数据的操作,在性能上又得到了部分的提高。

3.优化小数组时的排序方案

如果数组特别小,其实快速排序反而不如直接插入来的快,所以我们可以自己做一个阈值判断,如果i-j大于某个常数时(一般认为,,没个准,具体数值看自己心情吧),就使用直接插入排序,这样就能最大限度使用两种排序的优势来完成任务了。

讲了这么多,做做真题吧!~!

Leetocde : 215. Kth Largest Element in an Array (Medium)

在未排序的数组中找到第 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 partition(self,nums,l,r):
        v = nums[l]
        j = 1
        for i in range(l+1,r+1):
            if nums[i] > v:
                j += 1
                nums[i],nums[j] = nums[j],nums[i]
            nums[l],nums[j] = nums[j],nums[l]
        return j
 
 
    def findKthLargest(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: int
        """
        l = 0
        r = len(nums) - 1
        j = self.partition(nums, l, r)
        while j != k - 1:
            if k - 1 > j:
                j = self.partition(nums, j+1,r)
            if k - 1 < j:
                j = self.partition(nums, l, j-1)
        # print(nums[j])
        return nums[j] 

差不多就是这样了,希望本文能帮到你!~!

最后打个小广告,我的公众号,喜欢写点学习中的小心得,不介意可以关注下!~!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值