Swift常见算法

923 阅读6分钟

冒泡排序

冒泡排序是最简单的排序算法,每次选出最大的元素(泡泡浮出水面)。对比次数是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))