踏踏实实地打好算法基础(3)—快速排序算法

2,115 阅读3分钟

快速排序(Quick Sort)

算法和冒泡排序算法类似,都是基于交换排序思想。快速排序算法对冒泡排序算法进行了改进,从而具有更高的执行效率。

快速排序算法

  • 首先设定一个分界值(pivot),分界值选择是任意随机没有任何限制,通过该分界值将数组分成左右两部分
  • 将大于分界值放在数据集中到数组右边,小于分界值的数据集中到数组的左边。
  • 然后左边和右边的数据可以独立排序,对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分
  • 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两部分各数据排序完成后,整个数组的排序也就完成了。

基本思想

  • 分而治之的思想将问题分解为多个子问题,通过递归方式,不断划分数组直至将问题分解为足够简单来解决

quick_sort_001.png

  • 首先在数组中选择一个元素作为 pivot 这里就选择 4 作为 pivot
  • 然后分别设定两个指针位于数组的两侧
  • 首先从左侧开始移动左侧的指针来到 6 因为 6 比 4 大所以将 6 和 4 位置交换

quick_sort_002.png

  • 接下来开始移动右侧指针,移动方向从右向左
  • 当指针来到 3 时候我们发现 3 小于 4,我们需要保证在右侧的元素要大于 4 所以就进行将 3 和 4 进行交换。
  • 交换完后,再去移动左侧指针将左侧指针向右侧移动一个位置
  • 因为 5 大于 4 说以就需要将 4 和 5 位置进行交换

quick_sort_003.png

  • 接下来还是去移动右侧指针,向左移动一个位置来到 1 ,1 又小于 4 所以继续进行交换
  • 当完成交换后再去移动左侧指针发现这一次指针重合在一个位置,这样边结束了一轮迭代

演示

001.png

  • 这里选择 2 做 pivot 作为标准值
  • 然后在数组两端分别放置一个指针,这两个指针相对而行
  • 首先我们将右侧指针向左侧移动,当移动到值大于或者等于标准值,也就是 pivot 值就继续向前移动知道

002.png

  • 当从右侧出发指针找到了比标准值小的值就停下来
  • 然后左侧指针开始出发找比标准值大值,2 等于标准值所继续向前(右侧)前进来 5 ,5 比 2 大所以从左侧出发指针就停在 5 位置
  • 然后判断两个指针是否指向同一个位置

003.png

  • 完成交换之后,现在轮到开始移动右侧指针,右侧指针继续向前移动一个位置,这时两个指针重叠
  • 接下来就将 2 和两个指针位置的值进行交换完成第一次迭代

005.png

现在数组被 2 pivot 切分为两个部分,2 左侧的数据元素都小于 2 而 2 右侧数据元素都大于 2

006.png

时间复杂度

  • 最坏情况为 O(n2)O(n^2)
  • 最理想或者平均 O(nlog(n))O(n\log(n))
class Solution:

    def quickSort(self,nums,left,right):
        if (left >= right):
            return
        p = nums[left]
        i = left
        j = right
        while (i != j):
            while (j > i) and nums[j] > p:
                j -= 1
            nums[i],nums[j] = nums[j],nums[i]
            while (i < j) and nums[i] <= p:
                i += 1
            nums[i],nums[j] = nums[j],nums[i]
        self.quickSort(nums,left,i-1)
        self.quickSort(nums,i+1,right)
        # print(nums)
        
    def quickArraySort(self,nums):
        left = 0
        right = len(nums) - 1
        self.quickSort(nums,left,right)

Hoare 划分方式

hoare_001.png

  • 首先在数组左侧选择 11 作为 pivot 也就是所谓的标准值
  • 然后设置两个指针分别如图

hoare_002.png

  • 接下来就通过移动两个指针来实现排序,左侧指针向右移动每一次移动直到找到大于 11 数字就停止

  • 右侧指针向左移动直到找到一个小于 11 停止

  • 交替移动两个指针

  • 先从左侧开始移动指针,指针到 9 ,因为 9 小于 11 所以继续向右侧移动来到 29 因为 29 大于 11 所以这时指针就停止了

  • 接下来开始移动右侧指针,右侧指针向左侧移动,先是移动到 28 因为 28 大于 11 就继续向右移动,到 15 15 也大于 11 就继续向左侧移动 2 ,因为 2 小于 11 所以停止

hoare_003.png

  • 现在两个指针都停止了,判断一下指针位置左侧指针位于右侧指针的左侧
  • 这时就可以交换两个数值
  • 然后左侧指针继续向右移动找到大于 11 的 29 位置
  • 接下来右侧指针向右侧移动找到小于 11 的 7 位置
  • 现在因为右侧指针已经位于左侧指针的左侧

hoare_005.png

  • 就不需要再交换左右指针对应的值
  • 而是直接将右侧指针值和 pivot 标准值进行交换从而实现划分两个区
def hoare_partition(arr, low, high):
    pivot = arr[low]
    i = low - 1
    j = high + 1

    while(True):
        i += 1
        while (arr[i] < pivot):
            i += 1
        j -= 1
        while (arr[j] > pivot):
            j -= 1
        if( i >= j):
            return j
        arr[i], arr[j] = arr[j],arr[i]

def quickSort(arr,low,high):
    if (low < high):
        pi = hoare_partition(arr,low,high)

        quickSort(arr,low, pi)
        quickSort(arr, pi+1, high)

Lumuto 划分方式

lumuto_001.png

lumuto_002.png

lumuto_003.png

lumuto_005.png

lumuto_006.png

def lumuto_partition(arr, low, high):
    pivot = arr[high]

    i = (low -1)
    for j in range(low,high):
        if (arr[j] <= pivot):
            i += 1
            arr[i], arr[j] = arr[j],arr[i]
    arr[i + 1],arr[high] = arr[high],arr[i+1]
    return (i+1)

def quickSort(arr,low,high):
    if (low < high):
        pi = lumuto_partition(arr,low,high)

        quickSort(arr,low, pi-1)
        quickSort(arr, pi+1, high)

arr = [9, 21, 29, 38, 4, 17, 11, 25, 32]
quickSort(arr,0,len(arr)-1)
print(arr)