冒泡排序
冒泡排序是最简单的排序算法,每次选出最大的元素(泡泡浮出水面)。对比次数是1+2+3+...n-1+n = n(n+1)/2,算法复杂度是O(n^2),是所有算法中最差的表现,现实应用很少使用冒泡排序。 冒泡排序实现如下:实现的关键在于两次for循环中条件swap(a[j],a[j+1])
/// 冒泡排序
func bubbleSort<T: Comparable>(_ array: inout [T]) -> [T] {
for i in 0..<array.count {
for j in 0..<(array.count - i - 1) {
if array[j] > array[j + 1] {
array.swapAt(j, j + 1)
}
}
}
return array
}
快速排序
选择一个基数(中枢值/轴值),将所有小于基数的数放在左边,大于基数的数值放在右边。然后递归对左右两组数进行相同的操作,直到数组变成有序。
- 快速排序的基数最为关键,选对了基数可以很大程度减少对比交换的次数
- 快速排序最差时复杂度是O(n^2)但是这个概率很低,它的平均时间是O(nlog(n))
方法一:分组对比中枢值
/// 快速排序-三次filter
func quickSort<T:Comparable>(_ arr: [T]) -> [T] {
guard arr.count > 1 else {return arr}
let pivot = arr[arr.count / 2]
let less = arr.filter{ $0 < pivot }
let equal = arr.filter{ $0 == pivot }
let great = arr.filter{$0 > pivot}
return quickSort(less) + equal + quickSort(great)
}
方法二:分区
Lomuto分区法:是用数组最后一个元素作为基准元素对数组分区,区域为[low...p-1] [p+1...high],然后递归调用quickLomutoSort分别对左右区排序,一直到数组有序
/// Lomuto分区:是用数组最后一个元素作为基准元素重新排序数组。然后递归调用quickLomutoSort分别对左右区排序。[low...p-1] [p+1...high]
func partitionLomuto<T:Comparable>(_ a: inout [T],_ low: Int,_ high: Int) -> Int{
guard low <= high else {
return low
}
let pivot = a[high]
var i = low
for j in low..<high {
if a[j] <= pivot {
(a[i],a[j]) = (a[j],a[i])
i += 1
}
}
(a[i],a[high]) = (a[high],a[i])
return i
}
func quickSortLomuto<T:Comparable>(_ a: inout [T],_ low: Int,_ high: Int){
if low < high {
let pivot = partitionLomuto(&a, low, high)
quickSortLomuto(&a, low, pivot-1)
quickSortLomuto(&a, pivot+1, high)
}
}
验证
var list = [ 10, 0, 3, 9, 2, 14, 26, 27, 1, 5, 8, -1, 8 ]
print(partitionLomuto(&list, 0, list.count - 1))
quickSortLomuto(&list, 0, list.count - 1)
print(list)
Hoare分区:选择数组的first元素作为基准,数组划分区域为[low...p] [p+1...high],然后递归调用quickSortHoare分别对左右区排序,一直到数组有序
/// Hoare分区:选择数组的first元素作为基准,数组划分为[low...p] [p+1...high]
func partitionHoare<T:Comparable>(_ a: inout [T],_ low: Int,_ high: Int) -> Int {
let pivot = a[low]
var i = low - 1
var j = high + 1
while true {
repeat { i += 1} while a[i] < pivot
repeat { j -= 1} while a[j] > pivot
if i < j {
a.swapAt(i, j)
}else{
return j
}
}
}
/// Hoare快速排序
func quickSortHoare<T:Comparable>(_ a: inout [T],_ low: Int,_ high: Int){
if low < high {
let p = partitionHoare(&a, low, high)
quickSortHoare(&a, low, p)
quickSortHoare(&a, p + 1, high)
}
}
验证
var list = [ 10, 0, 3, 9, 2, 14, 26, 27, 1, 5, 8, -1, 8 ]
print(partitionHoare(&list, 0, list.count - 1))
quickSortHoare(&list, 0, list.count - 1)
print("quickSortHoare result:\(list)")
改进快速排序:荷兰国旗分区
思路分析: 设置两个标志位begin和end分别指向这个数组的开始和末尾,然后用一个标志位current从头开始进行遍历:
1)若遍历到的位置为0,则说明它一定属于前部,于是就和begin位置进行交换,然后current向前进,begin也向前进(表示前边的已经都排好了)。
2)若遍历到的位置为1,则说明它一定属于中部,根据总思路,中部的我们都不动,然后current向前进。
3)若遍历到的位置为2,则说明它一定属于后部,于是就和end位置进行交换,由于交换完毕后current指向的可能是属于前部的,若此时current前进则会导致该位置不能被交换到前部,所以此时current不前进。而同1),end向后退1。
func partitionDutchFlag<T: Comparable>(_ a: inout [T],_ low: Int,_ high: Int,_ pivotIndex: Int) -> (Int,Int){
let pivot = a[pivotIndex]
var begin = low
var current = low
var end = high
while current <= end {
if a[current] < pivot {
swap(&a, begin, current)
begin += 1
current += 1
}else if a[current] == pivot {
current += 1
}else{
swap(&a, current, end)
end -= 1
}
}
return (begin,end)
}
func swap<T>(_ a: inout [T],_ i: Int,_ j: Int) {
if i != j {
a.swapAt(i, j)
}
}
func quicksortDutchFlag<T:Comparable>(_ a: inout [T],_ low: Int,_ high: Int) {
if low < high {
let pivotIndex = Int.random(in: low...high)
let (p,q) = partitionDutchFlag(&a, low, high, pivotIndex)
print("pivotIndex=\(pivotIndex) (p,q) = (\(p),\(q))")
quicksortDutchFlag(&a, low, p - 1)
quicksortDutchFlag(&a, q + 1, high)
}
}
验证
var list = [ 10, 0, 3, 9, 2, 14, 8, 27, 1, 5, 8, -1, 26 ]
print(partitionDutchFlag(&list, 0, list.count - 1, 10))
quicksortDutchFlag(&list, 0, list.count - 1)
print(list)
Swift最大公约数
最大公约数:将m、n整除都没有余数的最大正整数x
例如13 整除 52 、 39 后没有余数。
//最大公约数:将m、n整除都没有余数的最大正整数
func gcd(_ m: Int, _ n: Int) -> Int {
var a = 0
var b = max(m,n)
var r = min(m,n)
while r != 0 {
a = b
b = r
r = a % b
}
return b
}
找到两个数字的GCD的费力方法是先找出两个数字的因子,然后取其共同的最大数。 问题在于分解数字是非常困难的,特别是当它们变大时。 (从好的方面来说,这种困难也是保证您的在线支付安全的原因。)
有一种更聪明的方法来计算GCD:欧几里德的算法。 这个算法主要观点是,
gcd(a, b) = gcd(b, a % b)
其中a%b是a除以b的余数。
func gcd(_ m: Int,_ n: Int) -> Int {
let r = m % n
if r != 0 {
return gcd(n, r)
}else{
return n
}
}
Swift最小公倍数
两个数字a和b的最小公倍数是两者的倍数中最小的正整数。 换句话说,LCM可以被a和b整除。 例如:lcm(2, 5) = 10,因为10可以被2整除,也可以被5整除。2 和 5二者倍数中最小的正整数。
a * b
lcm(a, b) = ---------
gcd(a, b)
所以最小公倍数就是a、b的乘积 除以 最大公约数
func lcm(_ a:Int,_ b:Int) -> Int {
return a * b / gcd(a,b)
}
判断质数
func isPrimes(_ n: Int) -> Bool {
if n < 2 {
return false
}
guard n > 3 else {
return true
}
for i in 2...Int(sqrt(Double(n))) {
if n % i == 0{
return false
}
}
return true
}
print(isPrimes(3))
print(isPrimes(4))
小于n的质数的个数有多少
func countPrimes(_ n: Int) -> Int {
guard n > 3 else{return 0}
var count = 1
//i>3 且 不能整除2 = 奇数
for i in 3..<n where i % 2 != 0 {
if isPrimes(i) {
count += 1
}
}
return count
}
print(countPrimes(10))