在阅读此文前,请先阅读 拥抱算法 (Embracing Algorithms) 上 。
在数组中移动元素
如果你要将图中选中的图形移动到最前面
,你会如何编写你的逻辑代码?
很难过,这个算法复杂度太高!
stablePartition(isSuffixElement:) 方法可以满足要求,该方法的时间复杂度为O(nlogn)
!
时间复杂度为O(logn)
,这是个怎样的规模?
请注意,这里的logn
是以2为底n的对数,即 log2n。
一般来说,复杂度中含有logn
的算法都使用了二分法。
然后,再来观察时间复杂度为O(nlogn)
又是一个怎样的规模。
再来看另一个例子
这个方法的作用是依次移动选中的图形元素,使这些元素聚集到选中的最前面的那个元素的前一个位置处,并使这些被移动的元素保持原有的相对位置。除此之外,还要保证其他未选中的元素的相对位置不改变。
请看效果图:
然后,仔细观察下面的图片:
你是否想起了前面用到的stablePartition
算法?
现在来改写这个方法的内部实现算法!
首先,获取到符合条件的首个元素的前一个位置。
然后通过切片,获取你真正需要操作的那部分元素。
最后,移动未选中的元素到数组末尾,并保持这些元素的相对位置不变。
Okay, well done!
需要注意,这里的shapes[predecessor...]
的类型为ArraySlice<Shape>
。
你可以理解为,这个切片就是对原始数组的引用。
所以,这里stablePartition
方法可以通过切片来修改原始数组的值。
封装你的算法
一般来说,我们都会倾向于封装常用的算法,通过提高代码的复用率来减少工作量。
比如我们经常会用到快速排序算法,在Swift标准库中就集成了Collection
类型中的sorted(by areInIncreasingOrder: (Element, Element) -> Bool)
方法。
你需要做的就是给这个方法传入一个谓词函数
(predicate
)来决定元素的排序顺序。
现在,我们来封装Canvas
类专用的算法,然后使其抽象并可用于所有的MutableCollection
。
首先,将扩展的类改为Array
,并将元素类型指定为Shape。
然后,去掉shapes
数组的调用,改为self
。
接下来,让传入的谓词函数来决定元素的排序顺序。
然后,更改类型为MutableCollection
,并去掉对于元素类型的限制。
有编译错误?别紧张!
千万不要像这样操作。
其实,可以将predecessor
的获取方法独立出来,而且抽象化。
到这里,这个算法的封装完成了吗?
你还需要为你的方法加上必要的注释
,以便后续的使用。
毕竟,被抽象后的算法不再针对性地去解决某个具体的问题,而是解决某一类问题。
所以,你要讲清楚该算法的用途。
而且,你需要注明算法的时间复杂度,方便调用者评估算法的整体性能。
通过Xcode,可以很方便地查看这些方法的文档!
以下为stablePartition
方法的源码,可以看到二分法的使用:
extension MutableCollection {
/// Moves all elements satisfying `isSuffixElement` into a suffix of the collection,
/// preserving their relative order, returning the start of the resulting suffix.
///
/// - Complexity: O(n log n) where n is the number of elements.
/// - Precondition: `n == self.count`
mutating func stablePartition(count n: Int, isSuffixElement: (Element) -> Bool) -> Index {
if n == 0 { return startIndex }
if n == 1 { return isSuffixElement(self[startIndex]) ? startIndex : endIndex }
let h = n / 2, i = index(startIndex, offsetBy: h)
let j = self[..<i].stablePartition(count: h, isSuffixElement: isSuffixElement)
let k = self[i...].stablePartition(count: n - h, isSuffixElement: isSuffixElement)
return self[j..<k].rotate(shiftingToStart: i)
}
}
至于rotate(shiftingToStart:)
方法的源码,可以在Swift Algorithms Prototype 中查看。
原文中的最后还有一个例子,也是关于stablePartition
方法的使用,有兴趣请自行查看参考文章。
参考文章:
Embracing Algorithms
转载请注明出处,谢谢~