阅读 81

🐻Swift排序算法分析

相关概念

  • 稳定排序:两个相同的值,排序前后,相对位置是否发生变化
  • 原地排序:是否是常量级的临时空间
  • 有序度: 数组中具有有序关系的元素对的个数
  • 满有序度: 这种完全有序的数组的有序度
  • 逆序度: 定义正好跟有序度相反(默认从小到大为有序)
  • 逆序度 = 满有序度 - 有序度
  • 平均时间复杂度:( 最大逆序度 - 最大有序度)/ 2
  • 平均时间复杂度推导过程其实并不严格
  • 不仅递归求解的问题可以写成递推公式,递归代码的时间复杂度也可以写成递推公式
  • 求复杂度度时:假设每行代码执行的时间都一样,为 unit_time

大 O 时间复杂度表示法

代码执行时间随数据规模增长的变化趋势,总的时间复杂度就等于量级最大的那段代码的时间复杂度

1、只关注循环执行次数最多的一段代码 2、加法法则、乘法法则

常见的复杂度量级

常量阶、对数阶、线性阶、线性对数阶、平方阶、指数阶、阶乘阶

如何分析一个“排序算法”?

一、排序算法的执行效率

  • 1.最好情况、最坏情况、平均情况时间复杂度
  • 2.时间复杂度的系数、常数 、低阶
  • 3.比较次数和交换(或移动)次数

二、排序算法的内存消耗

三、排序算法的稳定性

一览表

排序名称 时间复杂度 空间复杂度 平均时间复杂度 最优 / 最差时间复杂度 是否稳定
冒泡排序 O(n^2) O(1) O(n^2) O(n) / O(n^2) 稳定
鸡尾酒排序 O(n^2) O(1) O(n^2) O(n) / O(n^2) 稳定
插入排序 O(n^2) O(1) O(n^2) O(n) / O(n^2) 稳定
选择排序 O(n^2) O(1) O(n^2) O(n^2)/O(n^2) 不稳定
归并排序 O(nlogn) O(n) O(nlogn) O(nlogn)/O(nlogn) 稳定
快速排序 O(nlogn) O(n) O(nlogn) O(n^2)/O(nlogn) 不稳定
O(n) x x x x
快速排序 O(n) x x x x

简单一览表

排序名称 时间复杂度
冒泡、插入、选择 O(n^2)
归并、快排 O(nlogn)
桶、计数 O(n)

冒泡排序

思想:

1、数据量为n,每轮比较n次,取出最大的,放到最后;

2、需要进行n轮

评价

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)
  • 稳定排序
  • 最优时间复杂度:O(n)
  • 最差时间复杂度:O(n^2)
  • 平均时间复杂度:O(n^2)

代码

func bubbleSourt<T:Comparable>(nums:[T])->[T]{
    var newNums = nums
    var isChanged = false
    var lastChangeIndex = 0
    var sortBorder = newNums.count - 1
    
    for _ in 0..<newNums.count{
        for j in 0..<sortBorder{
            if newNums[j] > newNums[j+1]{
                let temp = newNums[j]
                newNums[j] = newNums[j+1]
                newNums[j+1] = temp
                isChanged = true
                lastChangeIndex = j
            }
        }
        sortBorder = lastChangeIndex
        if !isChanged{
            break
        }
    }
    return newNums
}
复制代码

鸡尾酒排序

思想

1、和冒泡排序一样,多了左右交替循环。

评价

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)
  • 稳定排序
  • 最优时间复杂度:O(n)
  • 最差时间复杂度:O(n^2)
  • 平均时间复杂度:O(n^2)

代码

func jjSort<T:Comparable>(nums:[T])->[T]{
    
    var newNums = nums
    var isChanged = false
    
    for i in 0..<newNums.count/2{
        /// 从左向右
        for j in i..<newNums.count - i - 1{
            if newNums[j] > newNums[j+1]{
                let temp = newNums[j]
                newNums[j] = newNums[j+1]
                newNums[j+1] = temp
                isChanged = true
            }
        }
        if !isChanged{
            break
        }
        /// 从右向左
        
        for j in i..<newNums.count - i - 1{
            let newJ = newNums.count - 1 - i - j
            if newNums[newJ] < newNums[newJ-1]{
                let temp = newNums[newJ]
                newNums[newJ] = newNums[newJ-1]
                newNums[newJ-1] = temp
                isChanged = true
            }
        }
        if !isChanged{
            break
        }
    }
    return newNums
}
复制代码

插入排序

思想

保持一个有序数组,然后从剩余元素中取出一个,插到有序数组中的合适位置(找到比当前元素小位置,然后插进去),默认有序数组由原数组的第一个元素构成

评价

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)
  • 稳定排序
  • 最优时间复杂度:O(n)
  • 最差时间复杂度:O(n^2)
  • 平均时间复杂度:O(n^2)

代码

func insertSort<T:Comparable>(nums:[T])->[T]{
    if nums.count < 2{
        return nums
    }
    var newNums = [nums[0]]
    for i in 1..<nums.count{
        let value = nums[i]
        var hadInsert = false
        for j in 0..<newNums.count {
            if value < newNums[j]   {
                newNums.insert(nums[i], at: j)
                hadInsert = true
                break
            }
        }
        if !hadInsert{
            newNums.append(value)
        }
    }
    return newNums
}
复制代码

选择排序

思想

选择排序算法的实现思路有点类似插入排序,也分已排序区间和未排序区间。但是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。

评价

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)
  • 不稳定排序
  • 最优时间复杂度:O(n^2)
  • 最差时间复杂度:O(n^2)
  • 平均时间复杂度:O(n^2)

代码

func selecteSort<T:Comparable>(nums:[T])->[T]{
    if nums.count < 2{
        return nums
    }
    var newNums = nums
    for i in 0..<newNums.count{
        var minValue = newNums[i]
        var minIndex = i

        for j in i..<newNums.count{
            if newNums[j] < minValue{
                minValue = newNums[i]
                minIndex = j
            }
        }
        if i != minIndex{
            newNums.swapAt(minIndex, i)
        }
    }
    return newNums
}
复制代码

归并排序

思想

  • 1、分治思想、递归方式
  • 2、数组从中间划分为两个数组,直到数组中的数量不可划分
  • 3、分成最小颗粒后,有序合并成一个大数组

变种

如何在 O(n) 的时间复杂度内查找一个无序数组中的第 K 大元素?

评价

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)
  • 稳定排序
  • 最优时间复杂度:O(nlogn)
  • 最差时间复杂度:O(nlogn)
  • 平均时间复杂度:O(nlogn)

代码

func mergeSort<T:Comparable>(nums:[T])->[T]{
    if nums.count < 2{
        return nums
    }
    var newNums = nums
    return mergeSortPartA(nums: &newNums, start: 0, end: nums.count-1)
}

func mergeSortPartA<T:Comparable>(nums:inout [T],start:Int,end:Int)->[T]{
    if start >= end{
        return nums
    }
    
    let middle = (start + end)/2
    // 分
    mergeSortPartA(nums: &nums, start: start, end: middle)
    mergeSortPartA(nums: &nums, start: middle+1, end: end)

    // 合
    return mergeSortPartB(nums: &nums, start: start, middle: middle, end: end)
}

func mergeSortPartB<T:Comparable>(nums:inout [T],start:Int,middle:Int,end:Int)->[T]{

    var tempNums = [T]()
    var i = 0
    var j = 0
    
    var nums1 = [T]()
    for i in start...middle{
        nums1.append(nums[i])
    }
    var nums2 = [T]()
    for i in middle+1...end{
        nums2.append(nums[i])
    }

    for _ in start...end{
        if i >= nums1.count{
            tempNums.append(contentsOf: nums2[j..<nums2.count])
            break
        }
        if j >= nums2.count{
            tempNums.append(contentsOf: nums1[i..<nums1.count])
            break
        }
        let valueA = nums1[i]
        let valueB = nums2[j]
        if valueA < valueB || valueA == valueB{
            tempNums.append(valueA)
            i = i + 1
        }else{
            tempNums.append(valueB)
            j = j + 1
        }
    }
    for i in 0..<tempNums.count{
        nums[i+start] = tempNums[i]
    }
    return tempNums
}
复制代码

快速排序

思想

  • 1、分治思想、递归方式
  • 2、先在数组中随机找一个基准值
  • 3、根据基准值,将数组分为三部分,左侧的小于基准值,中间的等于基准值,右侧的大于基准值
  • 4、划分的方式是,左右游标,首先是在右侧找到一个小于基准值的元素,然后停止查找,右侧游标减一
  • 5、左侧找到一个大于等于基准值的元素后,停止查找,左侧游标加一
  • 6、交换左右游标对应的值
  • 7、直到左侧游标大于等于右侧游标
  • 8、最后交换中间值与基准值

变种

如何在 O(n) 的时间复杂度内查找一个无序数组中的第 K 大元素?

评价

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)
  • 不稳定排序
  • 最优时间复杂度:O(nlogn)
  • 最差时间复杂度:O(n^2)
  • 平均时间复杂度:O(nlogn)

代码

func quickSort<T:Comparable>(nums:inout [T])->[T]{
    if nums.count < 2{
        return nums
    }
    quickSortA(nums: &nums, start: 0, end: nums.count-1)
    return nums
}

func quickSortA<T:Comparable>( nums:inout [T],start:Int,end:Int){
    if start >= end{
        return
    }
    let pivotIndex = quickSortB(nums: &nums, start: start, end: end)
    quickSortA(nums: &nums, start: start, end: pivotIndex-1)
    quickSortA(nums: &nums, start: pivotIndex+1, end: end)
}
func quickSortB<T:Comparable>(nums:inout [T],start:Int,end:Int)->Int{
    
    let pivot = nums[start]
    var left = start
    var right = end

    while left != right {
        while left<right,nums[right]>pivot {
            right = right - 1
        }
        while left<right,nums[left] <= pivot {
            left = left + 1
        }
        if left < right{
            nums.swapAt(left, right)
        }
    }
    nums[start] = nums[left]
    nums[left] = pivot
    return left
}

复制代码

其他

设计模式概览

本文使用 mdnice 排版