iOS算法刷题之十大排序算法

554 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第14天,点击查看活动详情

排序算法.png

算法概述

十大算法推荐文章可以看一下 十大排序从入门到入赘 写的非常详细。

算法分类

十种常见排序算法可以分为两大类:

  • 非线性时间比较类排序: 通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此成为非线性时间比较类排序。
  • 线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此成为线性时间非比较类排序。

算法复杂度

排序算法复杂度.png

稳定与不稳定:如果a原本在b的前面并且a=b,排序之后a仍然在b的前面那就是稳定排序,如果排序之后a可能会出现在b的后面则是不稳定排序。

空间复杂度:是指算法在计算机内执行时所需的存储空间与n的规模之间的关系。

时间复杂度:排序的的总操作次数,与n的规模之间的关系。

冒泡排序

冒泡排序是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果大小顺序错误就交换过来。直到该数列交换完成,即排序完成。由于越小(大)的元素经过交换慢慢的“浮”到最上面,类似冒泡一样,名字也是因此得来。

func sort(items: Array<Int>) -> Array<Int> {
    var list = items
    for i in 0..<list.count {
        for j in i+1..<list.count {
            if list[j] > list[i] {
                // 元素交换
                let temp = list[j]
                list[j] = list[i]
                list[i] = temp
            }
        }
    }
    return list
}

选择排序

选择排序是一种简单直观的排序算法。工作原理是:先在未排序的序列中找到最小(最大)元素,存放到排序序列的起始位置,然后再从剩余的未排序的元素中寻找最小(最大)元素,然后放到已经排序序列的末尾。以此类推,直到所有元素排序完毕。

func sort(items: Array<Int>) -> Array<Int> {
    var list = items
    for i in 0..<list.count {
    //记录当前最小的数,比较i+1后更大的数进行记录
        var minIndex = i
        for j in i+1..<list.count {
            if list[j] < list[minIndex] {
                minIndex = j
            }
        }
        // 交换
        let temp = list[minIndex]
        list[minIndex] = list[i]
        list[i] = temp
    }
    return list
}

插入排序

插入排序的算法是一种简单直观的排序算法。原理是通过构建有序序列,对于未排序的数据,在已排序序列中从后向前扫描,找到相应位置并插入。类似于玩扑克牌时摸完牌的操作。直到全部排序完成。

func sort(items: Array<Int>) -> Array<Int> {
    var list = items
    //循环无序数列
    for i in 1..<list.count {   
        var j = i
        //循环有序数列,插入相应的值
        while j > 0 {           
            if list[j] < list[j - 1]  {
                let temp = list[j]
                list[j] = list[j-1]
                list[j-1] = temp
                j = j - 1
            } else {
                break
            }
        }
    }
    return list
}

希尔排序

希尔排序是简单插入排序的改进版,先将待排序的序列根据步长分割成若干个子序列,分别取出相应位置元素进行比对并交换,全部比对完成后缩小步长并重复前面的步骤,直到步长为1为止。

func sort(items: Array<Int>) -> Array<Int> {
    var list = items
    guard list.count > 1 else {
        return list
    }
    // 步长
    var step = list.count / 2
    while step > 0 {
        for i in step..<list.count {
            var formIndex = i - step
            while formIndex >= 0 {
                if list[formIndex] > list[formIndex + step] {
                    let temp = list[formIndex]
                    list[formIndex] = list[formIndex + step]
                    list[formIndex + step] = temp
                }
                formIndex = formIndex - step
            }
        }
        // 缩小步长
        step = step / 2
    }
    return list
}

归并排序

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个典型应用。将已经有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。如果将两个有序表合并成一个有序表,称为2路归并。

归并排序用一句话来说就是:先把左半边数组排好序,再把右半边数组排好序,然后把两半数组合并。

// 定义:将子数组nuns[lo..hi]进行排序
func sort(nums: inout [Int], lo: Int, hi: Int) {
    if lo == hi {
        // 单个元素不用排序
        return
    }
    // 这样写是为了防止溢出,效果等同于 (hi + lo)/2
    let mid = lo + (hi - lo) /  2
    // 先对左半部分数组nums[lo..mid]排序
    sort(nums: &nums, lo: lo, hi: mid)
    // 再对右半部分数组nums[mid+1..hi]排序
    sort(nums: &nums, lo: mid+1, hi: hi)
    // 将两部分有序数组合并成一个有序数组
    merge(nums: &nums, lo: lo, mid: mid, hi: hi)
}

// 将 nums[lo..mid] 和 nums[mid+1..hi] 这两个有序数组合并成一个有序数组
func merge(nums: inout [Int], lo: Int, mid: Int, hi: Int) {
    // 先把nums[lo..hi]复制到辅助数组中
    // 以便合并后的结果能够直接存入nums
    for i in lo...hi {
        temp[i] = nums[i]
    }
    
    // 数组双指针技巧,合并两个有序数组
    var i = lo
    var j = mid + 1
    for p in lo...hi {
        if i == mid + 1 {
            // 左半边数组已全部被合并
            nums[p] = temp[j]
            j += 1
        }else if j == hi + 1 {
            // 右半边数组已全部被合并
            nums[p] = temp[i]
            i += 1
        }else if temp[i] > temp[j] {
            nums[p] = temp[j]
            j += 1
        }else {
            nums[p] = temp[i]
            i += 1
        }
    }
}

var temp: [Int] = []
func sort(items: Array<Int>) -> Array<Int> {
    temp = Array(repeating: 0, count: nums.count)
    var nums = items
    sort(nums: &nums, lo: 0, hi: nums.count-1)
    return nums
}

快速排序

快速排序的基本思想:先在序列中挑出一个基准,把所有比基准小的元素放在基准前面,所有比基准大的元素放到基准后面,这个称为分区操作,每个分区重复上面的步骤。

用一句话来说快排就是:先将一个元素排好序,然后再将剩下的元素排好序。

快速排序其实就是一个二叉树的前序遍历。

/// 6.快速排序
func sort(items: Array<Int>) -> Array<Int> {
    var list = items
    quickSort(list: &list, low: 0, high: list.count-1)
    return list
}
// 将数组以第一个值为准分成两部分,前半部分比该值要小,后半部分比该值要大
func parition(list: inout Array<Int>, low: Int, high: Int) -> Int {
    var low = low
    var high = high
    let temp = list[low]
    while low < high {
        while list[high] >= temp && low < high {
            high -= 1
        }
        list[low] = list[high]
        while list[low] <= temp && low < high {
            low += 1
        }
        list[high] = list[low]
    }
    list[low] = temp
    return low
}

func quickSort(list: inout Array<Int>, low: Int, high: Int) {
    if low < high {
        let mid = parition(list: &list, low: low, high: high)
        quickSort(list: &list, low: low, high: mid-1)
        quickSort(list: &list, low: mid+1, high: high)
    }
}

堆排序

完全二叉树结果,将堆顶元素与最后一个元素交换,然后调整新堆,然后再将堆顶与无序区最后一个元素交换,调整新堆,直到全部排序完成。

func sort(items: Array<Int>) -> Array<Int> {        
    var list = items
    //创建大顶堆,其实就是将list转换成大顶堆层次的遍历结果
    heapCreate(items: &list)
    var endIndex = items.count - 1
    while endIndex >= 0 {
        //将大顶堆的顶点(最大的那个值)与大顶堆的最后一个值进行交换
        let temp = list[0]
        list[0] = list[endIndex]
        list[endIndex] = temp
        endIndex -= 1   //缩小大顶堆的范围
        //对交换后的大顶堆进行调整,使其重新成为大顶堆
        heapAdjast(items: &list, startIndex: 0,endIndex: endIndex + 1)
    }
    return list
}

///构建大顶堆的序列
func heapCreate(items: inout Array<Int>) {
    var i = items.count
    while i > 0 {
        heapAdjast(items: &items, startIndex: i - 1, endIndex:items.count )
        i -= 1
    }
}

/// 对大顶堆的局部进行调整,使其该节点的所有父类符合大顶堆的特点
func heapAdjast(items: inout Array<Int>, startIndex: Int, endIndex: Int) {
    let temp = items[startIndex]
    var fatherIndex = startIndex + 1    //父节点下标
    var maxChildIndex = 2 * fatherIndex //左孩子下标
    while maxChildIndex <= endIndex {
        //比较左右孩子并找出比较大的下标
        if maxChildIndex < endIndex && items[maxChildIndex-1] < items[maxChildIndex] {
            maxChildIndex = maxChildIndex + 1
        }
        //如果较大的那个子节点比根节点大,就将该节点的值赋给父节点
        if temp < items[maxChildIndex-1] {
            items[fatherIndex-1] = items[maxChildIndex-1]
        } else {
            break
        }
        fatherIndex = maxChildIndex
        maxChildIndex = 2 * fatherIndex
    }
    items[fatherIndex-1] = temp
}

计数排序

计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

func sort(items: Array<Int>) -> Array<Int> {
    // 计数排序数组用简单的数据
    let list: Array<Int> = [2, 3, 8, 7, 1, 2, 2, 2, 7, 3, 9, 8, 2, 1, 4, 2, 4, 6, 9, 2]   
    // 创建0~9的数组,每一项里是个可变数组
    var listArray: Array<Array<Int>> = [[], [], [], [], [], [], [], [], [],[]]
    for item in 0..<list.count {
        listArray[list[item]].append(list[item])
    }      
    var finalArray = Array<Int>()
    for i in 0..<listArray.count {
        let array = listArray[i]
        for j in 0..<array.count {
            let item = array[j]
            finalArray.append(item)
        }
    }
    return finalArray
}

桶排序

桶排序是计数排序的升级版。桶排序的原理:设置一个定量的数组当做空桶,遍历输入数据,并且把数据一个一个放到对应的桶里去;对每个不是空的桶进行排序;从不是空的桶里把排序好的额数据拼接起来。

func sort(items: Array<Int>) -> Array<Int> {
    // 桶排序数组用简单的数据
    let list = items
    var minValue = list[0]
    var maxValue = list[0]
    for item in 0..<list.count {
        if list[item] < minValue {
            // 数据的最小值
            minValue = list[item]
        }else if list[item] > maxValue {
            // 数据的最大值
            maxValue = list[item]
        }
    }
    var finalArray = Array<Int>(repeating: 0, count: maxValue-minValue+1)

    for item in 0..<list.count {
        var index = finalArray[list[item]-minValue]
        index = index + 1
        finalArray[list[item]-minValue] = index
    }

    var resultArray = Array<Int>()
    for item in 0..<finalArray.count {
        if finalArray[item] > 0 {
            for _ in 0..<finalArray[item] {
                resultArray.append(item+minValue)
            }
        }
    }
    return resultArray
}

基数排序

基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。

func sort(items: Array<Int>) -> Array<Int> {
    var list = items
    if list.count > 0 {
        radixSort(list: &list)
    }
    return list
}
private func radixSort(list: inout Array<Int>) {
    var bucket = createBucket()
    let maxNumber = listMaxItem(list: list)
    let maxLength = numberLength(number: maxNumber)

    for digit in 1...maxLength {
        //入桶
        for item in list {
            let baseNumber = fetchBaseNumber(number: item, digit: digit)
            bucket[baseNumber].append(item) //根据基数进入相应的桶中
        }

        //出桶
        var index = 0
        for i in 0..<bucket.count {
            while !bucket[i].isEmpty {
                // 以队列形式
                list[index] = bucket[i].remove(at: 0)
                index += 1
            }
        }
    }
}

/// 创建10个桶
private func createBucket() -> Array<Array<Int>> {
    var bucket: Array<Array<Int>> = []
    for _ in 0..<10 {
        bucket.append([])
    }
    return bucket
}

/// 计算无序序列中最大的那个数
private func listMaxItem(list: Array<Int>) -> Int {
    var maxNumber = list[0]
    for item in list {
        if maxNumber < item {
            maxNumber = item
        }
    }
    return maxNumber
}

/// 获取数字的长度
func numberLength(number: Int) -> Int {
    return "\(number)".count
}

/// 获取相应位置的数字(number: 操作的数字,digit:  位数)
func fetchBaseNumber(number: Int, digit: Int) -> Int{
    if digit > 0 && digit <= numberLength(number: number) {
        var numbersArray: Array<Int> = []
        for char in "\(number)" {
            numbersArray.append(Int("\(char)")!)
        }
        return numbersArray[numbersArray.count - digit]
    }
    return 0
}