递归:快速排序,归并排序和堆排序

快速排序

// 当i==j的时候依然进入循环,执行相应的逻辑
while (i <= j) {
do_something()
}
// 很明显,快排的外层循环判断中,当i==j的时候其实是不需要执行交换的逻辑了,所以快排并不使用等号
while (i < j) {
do_something()
}

快速排序的思想非常的简单,随便找一个元素作为标兵,然后使用双指针分别从左右开始移动,

  1. 首先让右指针指向数组末尾R=len(arr)-1,然后判断是否大等于标兵元素,如果大于的话那么右指针就继续向左侧移动,即R -=1,直到找到小于标兵的位置
  2. 然后让左指针指向数组的开始,然后判断是否小于等于标兵元素,如果小于那么指针就持续向右移动,即L += 1,直到找到打羽标兵的位置
  3. 最后交换两个元素,将大的元素放到右侧,小的元素放到左侧。左右指针持续进行,直到两个指针相遇

在这里插入图片描述
可以看到快速排序其实就是不断进行交换的操作,操作完一轮之后,再对左右两侧的数据进行同样的操作,把一个大的问题转换成小的问题,所以快排用的就是递归算法。

快排有两个点非常容易写错

  1. 与标兵比对的时候包含了等号。这是因为数组中可能有很多数据是与标兵相同的,与标兵相同的元素其实放在左边和放在右边都是可以的,反正最后排序之后都会与标兵相邻。而且还可以避免陷入死循环。例如下面这个数组,如果不加等号的话,你会发现l和r都不会变动。
    在这里插入图片描述

  2. 最外层的循环比较好理解,要求i<j,如果l==r就直接返回即可,不需要排序,但是内层循环中依然有一个i<j,这是为什么呢?主要是为了防止数组越界。当原本的数组就是有序时,可以看到永远都有arr[j]>=pivot,因此最终会使得j<0,如果时python的话j=-1相当于变成了末尾,如果java等语言的话就回直接数组越界了。
    在这里插入图片描述

  3. 当i与j相等的时候,这一轮交换就结束了,然后将这个元素与标兵元素进行互换,标兵元素就到了他该到的位置。这一步也非常的迷惑,明明两两交换了为什么最后还要跟标兵进行互换。
    从二叉树的角度更容易理解快速排序,首先就是找到根节点(标兵),然后将数据分成两部分,左边都是小于等于根节点的,右边都是大于等于根节点的。i与j交汇的位置也就是标兵的位置,我们将标兵放到这个位置即可。所以每一轮排序的后,标兵都会放到正确的位置。

  4. 最后交换的是arr[l],arr[i] = arr[i],pivot。我当时就非常的奇怪,如果i,j交汇的时候arr[i]>pivot怎么办呢?其实i,j根本不可能交汇,这是因为我们在判断的时候就有i<j这个条件,因此一定有arr[i]<=pivot,这个时候我们将arr[i]与标兵位置进行交换之后就可以将标兵放到正确的位置了。

这里有个优化,就是使用随机选取pivot,这样在原本就是递增的数组中也可以有很好的性能。如果原本就是一个排序好的arr,那么此时就会退化成 O ( n 2 ) O(n^2) O(n2)


  def quickSort(arr, l, r):
      if l >= r:             # 递归结束
          return  

      pivot_idx = random.randint(l, r)                   # 随机选择pivot
      arr[l], arr[pivot_idx] = arr[pivot_idx], arr[l]     # pivot放置到最左边
      pivot = arr[l]                                        # 选取最左边为pivot

      i, j = l, r     # 双指针
      while i < j:
          
          while i<j and arr[j] >= pivot:          # 找到右边第一个<pivot的元素
              j -= 1
          
          while i<j and arr[i] <= pivot:           # 找到左边第一个>pivot的元素
              i += 1
          
          arr[i],arr[j] = arr[j],arr[i]       # 并将其移动到right处
      arr[l],arr[i] = arr[i],pivot 			  # 将标兵放到正确的位置

      quickSort(arr, l, i-1)          # 递归对mid两侧元素进行排序
      quickSort(arr, i+1, r)
  
  quickSort(arr, 0, len(arr)-1)         # 调用快排函数对nums进行排序
  return nums

归并排序

归并排序的底层也是递归的思考,其实根快速排序非常的相似。归并排序有两个点

  1. 我们把数组分成两个,左边和右边。如果左边我们排好序,然后右边也排好序,那么此时只需要将这两个子数组合并成一个即可。那么左右两个数组该如何排序呢?我们先看左边的数组,我们把左边的数组也分成两部分,如果这两部分是排好序的,我们直接合并即可。此时可以看到,我们把大的排序问题,转化成了每一个字部分的排序问题加合并问题。
    在这里插入图片描述
    这个过程用递归写非常的简单
class Solution:
    def sortArray(self, nums: List[int]) -> List[int]:

        def mergesort(arr, l, r):
            if l >= r:
                return
            m = (l + r) // 2
            mergesort(arr,l,m) # 左半部分排序
            mergesort(arr,m+1,r) # 右半部分排序

            # 排好顺序的左右两部分再进行合并

            # 左边数组的范围是l~m
            # 右边数组的范围是m+1~r
            # 现在就是把这两个有序数组进行合并
            i,j = l,m+1
            tmp = []
            while i <= m and j <= r:
                if arr[i] < arr[j]:
                    tmp.append(arr[i])
                    i += 1
                else:
                    tmp.append(arr[j])
                    j += 1
            tmp += arr[i:m+1]
            tmp += arr[j:r+1]
            for k in range(l,r+1):
                arr[k] = tmp[k-l]

        mergesort(nums, 0, len(nums)-1)         # 调用快排函数对nums进行排序
        return nums
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值