《漫画算法》--快速排序

233 阅读3分钟

快速排序

什么是快速排序

  • 快速排序和冒泡排序一样,也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的。
  • 快速排序在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列一边, 比它小的元素移动到数列的另一边,从而把数列拆解成两个部分。这种思路叫作分治法。
  • 每一轮的比较和交换,需要把数组中的全部元素遍历一边,时间复杂度是O(n)。遍历次数,假如元素个数是n,平均情况下需要logn轮,因此快速排序算法总体的平均复杂度是O(nlogn)。

基准元素的选择

  • 基准元素(pivot),在分治过程中,以基准元素为中心,把其他元素移动到它的左右两边。
  • 一边情况下,以第1个元素为基准元素,但如果每轮第1个元素为最大(小)值,则时间复杂度退化成O(n2)。在这种情况下可以随机选择一个元素为基准元素,并且让基准元素和数列首元素交换位置。
  • 快速排序的平均时间复杂度是O(nlogn),但最坏情况下的时间复杂度是O(n2)。

元素的交换

元素交换的方法:

  • 双边循环法

    • 【第一轮】选定基准元素pivot,并设置两个指针left和right,指向数列的最左和最右两个元素
    • 【第一轮】 left和right指针与基准元素比较交换
    • 【第二轮】重新切换left和right的指针
        def quick_sort(start_index, end_index, array = []):
            # 递归结束条件:start_index大于等于end_index的时候
            if start_index >= end_index:
                return
            # 得到基准元素的位置
            pivot_index = partition_v1(start_index, end_index, array)
            # 根据基准元素,分成两部分递归排序
            quick_sort(start_index, pivot_index - 1, array)
            quick_sort(pivot_index +1, end_index, array)
            
        def partiton_v1(start_index, end_index, array = []):
            # 取第一个位置的元素作为基准元素(也可以选择随机位置)
            pivot = array[start_index]
            left = start_index
            right = end_index
            while left != right:
                # 控制right指针进行比较并左移
                while left < right and array[right] > pivot:
                    right -= 1
                # 控制left指针进行比较并右移
                while left < right and array[left] <= pivot:
                    left += 1
                # 交换left指针和right指针指向的元素
                if left < right:
                    array[left], array[right] = array[right], array[left]
           # pivot 和指针重合点交换
           array[start_index] = array[left]
           array[left] = pivot
           return left
           
       my_array = list([3, 4, 14, 1, 5, 6, 7, 8, 9, 1, -1, 0, 9, 11])
       quicky_sort(0, len(my_array)-1, my_array)
       print(my_array)
    
  • 单边循环法

    • 首先选定基准元素pivot,设置一个mark指针指向数列起始位置,这个mark指针代表小于基准元素的区域边界。
    • 从基准元素的下一个位置开始遍历数组
      • 如果遍历到的元素大于基准元素,就继续往后遍历。
      • 如果遍历到的元素小于等于基准元素,第一步mark指针往右移动1位,第二部让当前遍历到的元素与当前mark指针元素交换位置。
    • 最后把pivot元素交换到mark指针所在的位置,第一轮结束。
        def quick_sort(start_index, end_index, array = []):
            # 递归结束条件:start_index大于等于end_index的时候
            if start_index >= end_index:
                return
            # 得到基准元素的位置
            pivot_index = partition_v1(start_index, end_index, array)
            # 根据基准元素,分成两部分递归排序
            quick_sort(start_index, pivot_index - 1, array)
            quick_sort(pivot_index +1, end_index, array)
       
       def partition_v2(start_index, end_index, array = []):
           # 取第一个位置的元素作为基准元素(也可以随机选取)
           pivot = array[start_index]
           for i in range(start_index + 1, end_index + 1):
               if array[i] < pivot:
                   mark += 1
                   array[mark], array[i] = array[i], array[mark]
           array[start_index] = array[mark]
           array[mark] = pivot
           return mark
            
       my_array = list([3, 4, 14, 1, 5, 6, 7, 8, 9, 1, -1, 0, 9, 11])
       quicky_sort(0, len(my_array)-1, my_array)
       print(my_array)
    

非递归实现

绝大多数的递归逻辑,都可以用栈的方式来代替。

    def quick_sort(start_index, end_index, array = []):
        quick_sort_stack = []
        # 整个数列的起止下表,以哈希表的形式入栈
        root_param = {"startIndex": start_index, "endIndex": end_index}
        quick_sort_stack.append(root_param)
        # 循环结束条件:栈空时结束
        while len(quick_sort_stack) > 0:
        # 栈顶元素出栈,得到起止下表
        param = quick_sort_stack.pop()
        # 得到基准元素位置
        pivot_index = partition(param.get("startIndex"),param.get("endIndex"),array)
        # 根据基准元素分成两个部分,把每一部分的起止下标入栈
        if param.get("startIndex") < pivot_index - 1:
            left_param = {"startIndex": param.get("startIndex"), "endIndex": pivot_index - 1}
            quick_sort_stack.append(left_param)
        if pivot_index +1 <param.get("endIndex"):
            right_param = {"startIndex": pivot_index + 1, "endIndex": param.get("endIndex")}
            quick_sort_stack.append(right_param)
            
            
    def partiton(start_index, end_index, array = []):
        # 取第一个位置的元素作为基准元素(也可以选择随机位置)
        pivot = array[start_index]
        mark = start_index
        for i in range(start_index + 1, end_index + 1):
            if array[i]< pivot:
                mark += 1
                array[mark], array[i] = array[i], array[mark]
        array[start_index] = array[mark]
        array[mark] = pivot
        return mark
   
    
   my_array = list([3, 4, 14, 1, 5, 6, 7, 8, 9, 1, -1, 0, 9, 11])
   quicky_sort(0, len(my_array)-1, my_array)
   print(my_array)