快速排序算法讲解及swift代码实现

2018-09-1015:20:25数据结构与算法Comments3,128 views字数 4251阅读模式

快速排序算法被称之为20世纪十大算法之一,也是各大公司面试比较喜欢考察的算法。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

快速排序算法讲解

快速排序的基本思想是:通过一趟排序将带排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

上述文字摘自《大话数据结构》文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

它的实现步骤为:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

  1. 从数列中挑出一个元素(挑选的算法可以是随机,也可以作其他的优化),称为"基准"(pivot)。
  2. 重新对数组进行排序:所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面,相同的放两边。
  3. 递归地进行分区操作,继续把小于基准值元素的子数列和大于基准值元素的子数列排序。

从上面的描述可以看出,分区操作是快速排序中的核心算法。下面笔者结合实例来描述一下分区操作的过程。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

首先拿到初始的数组:[5,4,9,1,3,6,7,8,2]文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

  • 选择5作为pivot。
  • 从剩下部分的两端开始:左侧1的标记为low,最右侧2的标记为high。
  • 先看j:2 < 5 , 交换5和2,j不变 :[2,4,9,1,3,6,7,8,5]
  • 再看i:2 < 5 , i ++ ;4 < 5, i++;9 > 5,交换 9 和 5,i不变[2,4,5,1,3,6,7,8,9]

代码实现

使用Swift的filter函数

因为在Swift中有一个数组的filter函数可以找出数组中符合某范围的一些数值,所以笔者先介绍一个会用该函数的简单的快速排序的实现:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

func quickSort0<T: Comparable>(_ array: [T]) -> [T] {
    
    guard array.count > 1 else { return array }
    
    let pivot = array[array.count/2]
    let less = array.filter { $0 < pivot }
    let greater = array.filter { $0 > pivot }
    
    return quickSort0(less) + quickSort0(greater)
}
复制代码

不难看出这里面使用了递归:选中pivot以后,将数组分成了两个部分,最后将它们合并在一起。虽然这里面使用了Swift里面内置的函数来找出符合这两个个部分的元素,但是读者可以通过这个例子更好地理解快速排序的实现方式。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

使用取index = 0 的partition函数

除了使用swift内置的filter函数,当然我们也可以自己实现分区的功能,通常使用的是自定义的partition函数。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

func _partition(_ array: inout [Int], low: Int, high: Int) -> Int{

    var low       = low
    var high      = high

    let pivotValue = array[low]

    while low < high {

        while low < high && array[high] >= pivotValue {
            high -= 1
        }
        array[low] = array[high]
        
        while low < high && array[low] <= pivotValue {
            low += 1
        }
        array[high] = array[low]
    }

    array[low] = pivotValue
  
    return low
}
复制代码

从代码实现可以看出,最初在这里选择的pivotValue是当前数组的第一个元素。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

然后从数组的最右侧的index逐渐向左侧移动,如果值大于pivotValue,那么index-1;否则直接将high与low位置上的元素调换;同样左侧的index也是类似的操作。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

该函数执行的最终效果就是将最初的array按照选定的pivotValue前后划分。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

那么_partition如何使用呢?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

func quickSort1(_ array: inout [Int], low: Int, high: Int){
  
    guard array.count > 1 else { return }
    
    if low < high {        
        let pivotIndex = _partition(&array, low: low, high: high)
        quickSort1(&array, low: low, high: pivotIndex - 1)
        quickSort1(&array, low: pivotIndex + 1, high: high)
    }
    
}
复制代码

外层调用的quickSort1是一个递归函数,不断地进行分区操作,最终得到排好序的结果。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

我们将上面实现的归并排序,使用swift内置函数的快速排序,以及自定义partition函数的快速排序的性能作对比:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

merge sort...
merge sort time duration : 4.85s

quick sort...
quick sort0 time duration : 984ms //swift filter function
quick sort1 time duration : 2.64s //custom partition
复制代码

上面的测试用例是选择随机数组的,我们看一下测试用例为元素个数一致的基本有序的数组试一下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

merge sort...
merge sort time duration : 4.88s

quick sort...
quick sort0 time duration : 921ms
quick sort1 time duration : 11.3s
复制代码

虽然元素个数一致,但是性能却差了很多,是为什么呢?因为我们在分区的时候,pivot的index强制为第一个。那么如果这个第一个元素的值本来就非常小,那么就会造成分区不均的情况(前重后轻),而且由于是迭代操作,每次分区都会造成分区不均,导致性能直线下降。所以有一个相对合理的方案就是在选取pivot的index的时候随机选取。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

使用随机选择pivotValue的partition函数

实现方法肯简单,只需在分区函数里将pivotValue的index随机生成即可:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

func _partitionRandom(_ array: inout [Int], low: Int, high: Int) -> Int{
    
    let x      = UInt32(low)
    let y      = UInt32(high)
    
    let pivotIndex = Int(arc4random() % (y - x)) + Int(x)
    let pivotValue = array[pivotIndex] 
    
    ...
}
复制代码

现在用一个数组长度和上面的测试用例一致的基本有序的数组来测试一下随机选取pivotValue的算法:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

merge sort...
merge sort time duration : 4.73s

quick sort...
quick sort0 time duration : 866ms
quick sort1 time duration : 15.1s  //fixed pivote index
quick sort2 time duration : 4.28s  //random pivote index
复制代码

我们可以看到当随机抽取pivot的index的时候,其运行速度速度是上面方案的3倍。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

现在我们知道了3种快速排序的实现,都是根据pivotValue将原数组一分为二。但是如果数组中有大量的重复的元素,而且pivotValue很有可能落在这些元素里,那么显然上面这些算法对于这些可能出现多次于pivotValue重复的情况没有单独做处理。而为了很好解决存在与pivot值相等的元素很多的数组的排序,使用三路排序算法会比较有效果。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

三路快速排序

三路快速排序将大于,等于,小于pivotValue的元素都区分开,我们看一下具体的实现。先看一下partition函数的实现:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

func swap(_ arr: inout [Int],  _ j: Int, _ k: Int) {
    
    guard j != k else {
        return;
    }
    
    let temp = arr[j]
    arr[j] = arr[k]
    arr[k] = temp
}



func quickSort3W(_ array: inout [Int], low: Int, high: Int) {
    
    if high <= low { return }
    
    var lt = low       // arr[low+1...lt] < v
    var gt = high + 1  // arr[gt...high] > v
    var i  = low + 1   // arr[lt+1...i) == v
    
    let pivoteIndex = low
    let pivoteValue = array[pivoteIndex]
    
    while i < gt {
        
        if array[i] < pivoteValue {
          
            swap(&array, i, lt + 1)
            i += 1
            lt += 1
           
        }else if pivoteValue < array[i]{
       
            swap(&array, i, gt - 1)
            gt -= 1
            
        }else {
            i += 1
        }
    }
    
    swap(&array, low, lt)
    quickSort3W(&array, low: low, high: lt - 1)
    quickSort3W(&array, low: gt, high: high)
    
    
}


func quickSort3(_ array: inout [Int] ){
    
    quickSort3W(&array, low: 0, high: array.count - 1)
    
}
复制代码

主要看quickSort3W方法,这里将数组分成了三个区间,分别是大于,等于,小于pivote的值,对有大量重复元素的数组做了比较好的处理。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

我们生成一个元素数量为500,最大值为5的随机数组看一下这些快速排序算法的性能:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

quick sort1 time duration : 6.19s //fixed pivote index
quick sort2 time duration : 8.1s  //random pivote index
quick sort3 time duration : 4.81s //quick sort 3 way 
复制代码

可以看到三路快速排序(quick sort 3 way)在处理大量重复元素的数组的表现最好。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

对于三路快速排序,我们也可以使用Swift内置的filter函数来实现:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

func quicksort4(_ array: [Int]) -> [Int] {
    
    guard array.count > 1 else { return array }
    
    let pivot = array[array.count/2]
    let less = array.filter { $0 < pivot }
    let equal = array.filter { $0 == pivot }
    let greater = array.filter { $0 > pivot }
    
    return quicksort4(less) + equal + quicksort4(greater)
}
复制代码

以上,介绍完了快速排序在Swift中的5中实现方式。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html

文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/4509.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/suanfa/4509.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定