Swift实现的快速排序及sorted方法的对比

Swift语言有着优秀的函数式编程能力,面试的时候面试官都喜欢问我们快速排序,那么用Swift如何实现一个快速排序呢?首先扩展Array类:

extension Array {
    var decompose : (head: T, tail: [T])? {
        return (count > 0) ? (self[0], Array(self[1..<count])) : nil
    }
}

属性decompose的作用是返回数组中的第一个元素和剩下的元素,注意这个属性是可选型的,当count为0的时候返回nil,count是Array的属性。使用扩展的原因是这种拆分可以实现非常多的操作,一劳永逸。

然后实现快速排序的方法:

func qsortDemo(input: [Int]) -> [Int] {
    if let (pivot, rest) = input.decompose {
        let lesser = rest.filter { $0 < pivot }
        let greater = rest.filter { $0 >= pivot }
        return qsortDemo(lesser) + [pivot] + qsortDemo(greater)
    } else {
        return []
    }
}

可以发现使用Swift实现快速排序的代码非常的简洁。首先调用待排序序列的decompose属性,使用一个元组来保存数组的第一个元素和首先的数组,由于依旧是采用递归的方式,所以使用可选绑定来做边界判断。在可选绑定内部使用了filter方法来分割元素,省去了比较移动元素的复杂过程,得到的lesser是小于pivot的数组、greater是大于pivot的数组,在返回时使用了数组的拼接并对拆分的数组进行递归,结构非常的简单,至此一个快速排序的过程就结束了。

让我们在storyboard中做个性能测试:

var a:[Int] = [1,2,4,6,2,4,3,7,8]
qsortDemo(a)

数组a在快排中的效率如下:

可以看到可选绑定中的return执行了9次等于a中元素的个数,和预想的一样,这是因为每一个元素是在这个return中确定自身的位置的,所以执行次数应该为n。那么为什么else中的语句执行了n+1次呢?想知道每一个元素在一次递归中发生了什么,可以把让a中只有一个元素模拟一次递归发生的事情,结果如下图:

打开decompose的执行记录:

可以看到decompose被执行了三次,第一次是[1]来访问,返回了([1],[]),此时在可选绑定中,lesser和greater都是[],在return中递归的时候lesser和greater会继续访问decompose此时返回了两个nil,所以对应的可选绑定判断为假直接运行else中的return[],整个过程结束。

else条件返回的是[],[]加入到数组中不会起作用,所以可以作为边界返回值。

把a中的元素扩充到两个。

decompose中的执行记录为:

很好理解了,第一次拆分得到[1]和[2],pivot为[1],lesser为[],而greater为[2]。在return时lesser访问decompose得到nil,可选绑定为假执行else中的语句,此时greater又成了一个元素的数组,步骤同上。所以在这个快排的递归过程中每次只有最后一个元素的lesser和greater会同时为[],其他元素都只有一边为[],这也就解释了为什么return会出现n+1的执行次数。

观察一下两个filter,这种拆分方法需要多余的空间来保存lesser和greater,点开追踪可以看到lesser和greater中的追踪轨迹是相反的,这很好理解。另外filter是系统API,并不知道内部的实现方法,但是可以看到在判断[2]中的元素的时候被调用了三次,应该与内部机制有关,虽然看起来执行的次数变多了,但是免去了传统快速排序中的元素交换位置的操作,效率高低并不好说。总之写了这么多最后的效果就一个:排序。

在看完这段代码后我做了如下思考:既然是排序,那么必然可以使用系统的sorted方法(以前的sort方法),效果如何呢?让我们用第一个例子来试试,只需要一行代码:

let b = a.sorted{$0<$1}

效果如何呢?请看下图:

没错,整个方法只有15次比较!效率非常的惊人,sorted的实现是由苹果的工程师在底层实现的,我想他们一定用了什么好办法来提升效率。不信?来看下面的例子,我们都知道快速排序的最坏情况出现在递归时对数组的不均衡划分上,比如修改数组a为:

var a:[Int] = [1,1,1,1,1,1,1,1,1]

数组的整体大小没有发生变化,运行效率如图:

可以看到算法的主要耗时部分lesser和greater的执行此时由之前的35次变为45次了,那么sorted方法的执行效率又如何呢?

你没有看错!对于快排最头疼的顺序性数组,sorted的重复次数只有n次!说明在面对这种类型的数组的时候sorted方法进行过判断,直接输出了。当然闭包中的语句一定要合适,“千万不要使用等于号!”,比如改写a:

var a:[Int] = [1,1,2,2,3,1,1,1,1]

没有等于号的情况:

如果你写上等于号:

OMG!效果一样的前提下效率差了好多。

另外一种极端情况,完全逆序一个数组:

当然快排的时间和完全相同的元素一样:

如果觉得数量级太小不过瘾,那么来个大号的数组:

现在修改a为500个随机的100以内的正整数:

var a:[UInt32] = []
for _ in 0..<500{
  a.append(arc4random() % 100)
}

同时比较两种排序方式,下面是快排的:

下面是sorted的效率:

大家可以试试,规模越大的数组效率差别越明显,sorted以肉眼可见的速度秒杀了快排!

掌声在哪里?

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-12 21:57:43

Swift实现的快速排序及sorted方法的对比的相关文章

Swift语言中的属性,方法,下标脚本以及继承

从这篇章节起,Swift编程语言指南大部分的重要内容在于概念,代码并不是太多.理解Swift的面向对象理念,语法以及类结构,构造析构过程对于很好的应用Swift语言将会有比较大的帮助. 属性 存储属性 存储属性通常是那些可以通过直接赋值,或者直接访问成员能够获得的属性类型. 它有些要注意的地方: 若一个结构体被声明为常量,则子属性无法被修改了.在Objective-C中,我们总是无法修改结构体的子属性,但是swift却可以,不过这种情况是个例外,当你存储型属性是个结构体并且是个常量,那你就不要再

《The Swift Programming Language 》——闭包 使用方法详细讲解

闭包是自包含的函数代码块,可以在代码中被传递和使用. Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的 lambdas 函数比较相似. 闭包可以捕获和存储其所在上下文中任意常量和变量的引用.这就是所谓的闭合并包裹着这些常量和变量,俗称闭包.Swift 会为您管理在捕获过程中涉及到的所有内存操作. 注意: 如果您不熟悉捕获(capturing)这个概念也不用担心,您可以在值捕获 章节对其进行详细了解. 在函数章节中介绍的全局和嵌套函数实际上

《The Swift Programming Language 》——函数 使用方法详细讲解

函数是用来完成特定任务的独立的代码块.你给一个函数起一个合适的名字,用来标示函数做什么,并且当函数需要执行的时候,这个名字会被"调用". Swift 统一的函数语法足够灵活,可以用来表示任何函数,包括从最简单的没有参数名字的 C 风格函数,到复杂的带局部和外部参数名的 Objective-C 风格函数.参数可以提供默认值,以简化函数调用.参数也可以即当做传入参数,也当做传出参数,也就是说,一旦函数执行结束,传入的参数值可以被修改. 在 Swift 中,每个函数都有一种类型,包括函数的参

Python中sorted()方法的用法

1.先说一下iterable,中文意思是迭代器. Python的帮助文档中对iterable的解释是:iteralbe指的是能够一次返回它的一个成员的对象.iterable主要包括3类: 第一类是所有的序列类型,比如list(列表).str(字符串).tuple(元组). 第二类是一些非序列类型,比如dict(字典).file(文件). 第三类是你定义的任何包含__iter__()或__getitem__()方法的类的对象. 2.Python帮助文档中对sorted方法的讲解: sorted(i

python中sorted方法和列表的sort方法

sort 与 sorted 区别: sort 是应用在 list 上的方法,属于列表的成员方法,sorted 可以对所有可迭代的对象进行排序操作. list 的 sort 方法返回的是对已经存在的列表进行操作,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作. sort使用方法为ls.sort(),而sorted使用方法为sorted(ls) 一.基本形式 列表有自己的sort方法,其对列表进行原址排序,既然是原址排序,那显然元组不可能拥有这种方法,因为元

【转载】机器学习中的相似性度量,方法汇总对比

机器学习中的相似性度量,方法汇总对比 人工智能  林  1周前 (01-10)  876℃  0评论 作者:苍梧 在做分类时常常需要估算不同样本之间的相似性度量(Similarity Measurement),这时通常采用的方法就是计算样本间的“距离”(Distance).采用什么样的方法计算距离是很讲究,甚至关系到分类的正确与否. 本文的目的就是对常用的相似性度量作一个总结. 本文目录: 1. 欧氏距离 2. 曼哈顿距离 3. 切比雪夫距离 4. 闵可夫斯基距离 5. 标准化欧氏距离 6. 马

js+jquery动态设置/增加/删除/获取元素属性的两种方法集锦对比(动态onclick属性设置+动态title设置)

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html140/strict.dtd"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> <title>

跨平台开发的两种方法及其对比

为什么移动应用开发对很多开发人员来说,都是一件令人头痛的事?这是因为,每种流行的移动平台都具有自身的开发语言.开发工具及其特征. 这就意味着,你开发一款应用不但需要花费 3 倍的开发时间,并且需要维护 3 个项目,因此开发原生应用的代价是非常巨大的. 跨平台开发的两种方法: 幸运的是,有很多公司已经在研究如何使原生 APP的开发变得简单,目前为止多平台的开发方法主要有两种: 第一种:以 Web应用为内核,填充到原生 app中(即 PhoneGap 提供的解决方案). 这种方法能够吸引那些想要转到

MongoDB中insert方法、update方法、save方法简单对比

MongoDB中insert方法.update方法.save方法简单对比 1.update方法 该方法用于更新数据,是对文档中的数据进行更新,改变则更新,没改变则不变. 2.insert方法 该方法用于插入数据到文档中,也就是给文档添加新数据. 3.save方法 该方法同样用于插入数据到文档中,功能是类似于insert方法的.与insert方法不同的是, save方法是遍历文档,逐条将数据插入进去的,而insert方法是将整个文档整体插入进去的. 由两个方法的源码可以看出来. save方法的写法