快速排序

对于包含n个数的输入数组来说,快速排序是一种最坏情况的时间复杂度为的排序算法。虽然最坏情况时间复杂度很差,但是快速排序通常是实际排序应用中最好的选择,因为它的平均性能非常好,它的期望复杂度是nlgn。

1 快速排序

与归并排序一样,快速排序也使用了分治思想。其排序的效果图如下所示。

这里我们使用了QuickSort这个类,已添加部分代码。

//
//  QuickSort.swift
//  QuickSort
//
//  CSDN:http://blog.csdn.net/y550918116j
//  GitHub:https://github.com/937447974/Blog
//
//  Created by yangjun on 15/11/16.
//  Copyright © 2015年 阳君. All rights reserved.
//

import Foundation

/// 快速排序
class QuickSort: NSObject {

    /// 数组
    var list = Array<Int>()

}

1.1 分治过程

我们所要做的就是对list这个数组进行排序。运用分治思想对其进行快速排序的三步分治过程。

1.1.1 分解

数组list被划分为两个(可能为空)子数组list[first..location-1]和list[location+1..end],使得
list[first..location-1]的每一个元素都小于list[location],而list[location]也小于等于[location+1..end]中的每个元素。即

list[first..location-1] <= list[location] <=list[location+1..end]

其中,计算下标location也是划分过程的一部分。

1.1.2 解决

通过递归调用快速排序,对子数组list[first..location-1]和list[location+1..end]进行排序。

1.1.3 合并

因为子数组都是原址排序的,所以不需要合并操作:数组list已经有序。

1.2 快速排序实现

1.2.1 核心代码

下面的程序实现快速排序:

// MARK: - 快速排序
func quickSort() {
    self.quickSort(0, end: self.list.count-1)
}

// MARK: 分解
private func quickSort(first:Int, end:Int) {
    if first < end {
        // 获取中位点
        let location = self.partition(first, end: end)
        self.quickSort(first, end: location-1)
        self.quickSort(location+1, end: end)
    }
}

为了排序一个数组list的全部元素。初始调用了self.quickSort(0, end: self.list.count-1),并给外部提供了quickSort接口。

算法的关键部分是partition方法,它实现了对数组list[first..end]的重排。

// MARK: 排序核心算法
private func partition(first:Int, end:Int) ->Int {
    var index = first - 1
    for(var i = first; i < end; i++) {
        if self.list[i] < self.list[end] {
            index++
            self.exchange(i, index)
        }
    }
    index++
    self.exchange(index, end)
    return index
}

// MARK: 交换数据
private func exchange(p:Int, _ r:Int){
    if p == r {
        return
    }
    let temp = self.list[p]
    self.list[p] = self.list[r]
    self.list[r] = temp
}

1.2.1 核心代码解说

下面用一个例子向你阐述这个操作过程。假设传入[2,7,8,1,4]的数组。初始化的状态如下:

indexfirst、iend
27814

index在-1位,i在首尾。i会从first移动到end。当i移动到0位时,2<4。此时index移动到0位。

index、firstiend
27814

i继续移动,移动到3位时,1<4。index移动到1位,交换index和i位置的数据,也就是交换1和7。

firstindexiend
21874

此时i已经移动到3的位置,下一位就是end,也就停止移动。然后index+1右移到2位。交换index和end的数据,也就是交换8和4。此时发现4的前面的数据小于4,后面的数据大于4。也就是说一次partition操作完成,返回index。

firstindexiend
21478

交换位置我们用到了过程exchange(p:Int, _ r:Int),其中p==r时,不交换位置,避免重复操作赋值。

2 快速排序的性能

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

2.1 最坏情况

当划分产生的两个子问题分别包含了n-1个元素和0个元素时,快速排序的最坏情况就发生了。也就是放入的数组是一个倒序数组。这样会得到一个递归式

T(n) = T(n-1) + o(n)

解后发现算法的时间复杂度为

2.2 最好情况划分

在可能的最平衡的划分中,partition得到的两个子问题的规模都不大于n/2。在这种情况下,快速排序的性能非常好。此时,算法运行时间的递归为

T(n) = 2T(n/2) + o(n)

解后算法的时间复杂度为nlgn。

2.3 平衡的划分

快速排序的平均运行时间更接近与其最好情况。

3 随机快速排序

为了尽量的避免碰到最坏情况,其实际是避免数组的末位数据全大于或小于前面的数据。即

list[first..end-1] <= list[end] 或 list[first..end-1] >= list[end]

这样都会产生最坏情况。我们引入了一个随机数,让list[first..end-1]中随机的一个数据和list[end]交换。

引入两个方法radomQuickSort和radomPartition即可完成随机化时间,这里还有一个radomQuickSort供外部使用。

// MARK: - 随机快速排序
func radomQuickSort() {
    self.radomQuickSort(0, end: self.list.count-1)
}

// MARK: 开始随机排序
private func radomQuickSort(first:Int, end:Int) {
    if first < end {
        // 获取中位点
        let location = self.radomPartition(first, end: end)
        self.radomQuickSort(first, end: location-1)
        self.radomQuickSort(location+1, end: end)
    }
}

// MARK: 获取划分的位置
private func radomPartition(first:Int, end:Int) ->Int {
    // 随机一个数放到尾部,主要防止遇到最坏排序情况,即数组为倒序
    // 获取first-end的随机数
    let random = arc4random_uniform(UInt32(end-first))
    // 或 random = Int(arc4random()) % (end-first)
    let index = Int(random) + first
    self.exchange(index, end);
    return self.partition(first, end: end)
}

4 多线程快速排序

快速排序的思想是分治思想,而且子数组list[first..location-1]和list[location+1..end]是不相互影响的,可以分离运行。这就提供了多线程的实现,而且是并发的多线程。

如果你不知道IOS的多线程可以阅读我的博文《IOS多线程》。

我们的改造是建立在随机快速排序的基础上的,这就更好的避免了遇到最坏情况。

只需添加方法threadQuickSort和threadQuickSort。

// MARK: - 多线程快速排序
func threadQuickSort() {
     self.threadQuickSort(0, end: self.list.count-1)
}

// MARK: 开始多线程快速排序
private func threadQuickSort(first:Int, end:Int) {
    // 在随机排序的基础上启用多线程排序
    if first < end {
        // 获取中位点
        let location = self.radomPartition(first, end: end)
        // 分到多个线程排序,并发
        let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
        dispatch_async(queue, { () -> Void in
            self.threadQuickSort(first, end: location-1)
        })
        dispatch_async(queue, { () -> Void in
            self.threadQuickSort(location+1, end: end)
        })
    }
}

5 测试

5.1 测试代码

// 快速排序测试
let quickSort = QuickSort()
quickSort.list.append(0)
for(var i = 10; i > 0; i--){
    quickSort.list.append(i)
}
quickSort.quickSort()
print("快速排序:\(quickSort.list)")


// 速度测试,使用最坏情况测试,也就是原数组为倒序
let count = 10000 // 要排序的元素个数
var source = Array<Int>()
for(var i = count; i > 0; i--){
    source.append(i)
}
print("快排速度测试,总数:\(count)")

// 快速排序
var date = NSDate()
quickSort.list = source
quickSort.quickSort()
print("快速排序耗时:\(-date.timeIntervalSinceNow)")

// 随机快速排序
date = NSDate()
quickSort.list = source
quickSort.radomQuickSort()
print("随机快速排序耗时:\(-date.timeIntervalSinceDate(NSDate()))")

// 多线程快速排序
date = NSDate()
quickSort.list = source
quickSort.threadQuickSort()
print("多线程快速排序耗时:\(-date.timeIntervalSinceNow)")

5.2 结果

快速排序:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
快排速度测试,总数:10000
快速排序耗时:2.01945704221725
随机快速排序耗时:0.0124430060386658
多线程快速排序耗时:0.000885963439941406

6 小结

这篇博文讲解了关于快速排序的swift实现。说明了其分治思想和实现,以及性能分析。为避免遇到最坏情况,我们引入了随机函数;根据分治思想我们引入了多线程。通过测试发现多线程快速排序的效率非常高。

 


其他

源代码

https://github.com/937447974/Algorithms

参考资料

算法导论

IOS多线程

文档修改记录

时间描述
2015-11-16算法项目完成、博文完成

版权所有

CSDN:http://blog.csdn.net/y550918116j

GitHub:https://github.com/937447974/Blog

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页