对于包含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]的数组。初始化的状态如下:
index | first、i | end | |||
---|---|---|---|---|---|
2 | 7 | 8 | 1 | 4 |
index在-1位,i在首尾。i会从first移动到end。当i移动到0位时,2<4。此时index移动到0位。
index、first | i | end | ||
---|---|---|---|---|
2 | 7 | 8 | 1 | 4 |
i继续移动,移动到3位时,1<4。index移动到1位,交换index和i位置的数据,也就是交换1和7。
first | index | i | end | |
---|---|---|---|---|
2 | 1 | 8 | 7 | 4 |
此时i已经移动到3的位置,下一位就是end,也就停止移动。然后index+1右移到2位。交换index和end的数据,也就是交换8和4。此时发现4的前面的数据小于4,后面的数据大于4。也就是说一次partition操作完成,返回index。
first | index | i | end | |
---|---|---|---|---|
2 | 1 | 4 | 7 | 8 |
交换位置我们用到了过程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
参考资料
算法导论
文档修改记录
时间 | 描述 |
---|---|
2015-11-16 | 算法项目完成、博文完成 |