算法图解之Swift实践【第二章 选择排序】

206 阅读5分钟

本人已参与「新人创作礼」活动,一起开启掘金创作之路。

概述

第二章主要内容是从数组、链表等基本数据结构入手,介绍了算法的基本内容。再着重讲解选择排序的实现及思想,让我们了解了一种简单直接的排序算法。

数组 & 链表

数组

数组在内存中直接获取一块连续的地址,来存储数据。当数据量超过已有地址时,将无法继续存储,需要搬迁整个数组。 由于数组的内存地址是连续的,所以十分易于读取,但并不容易插入和删除。

链表

链表在内存中通过每个元素存储下一个元素的地址,使得一系列随机的内存地址串在一起。 想要在链表中插入新的元素,只需要随机在内存中获取地址即可;想要在链表中删除一个元素,只需要修改前一个元素指向的地址,使其跳过要删除的元素即可。 因此,链表易于插入删除,而不易于读取。

数组和链表的操作时间复杂度对比

操作数组链表
读取O(1)O(n)
插入O(n)O(1)
删除O(n)O(1)

练习

2.1  假设你要编写一个记账的应用程序。

题图2.1

你每天都将所有的支出记录下来,并在月底统计支出,算算当月花了多少钱。因此,你执行的插入操作很多,但读取操作很少。该使用数组还是链表呢?

链表。

由上文中数组和链表的操作时间复杂度对比可以知道,数组的读取速度比链表快;而链表的插入速度比链表快,本题中描述的场景执行的插入操作很多,但读取操作很少,显然使用链表更适合。

2.2 假设你要为饭店创建一个接受顾客点菜单的应用程序。这个应用程序存储一系列点菜单。服务员添加点菜单,而厨师取出点菜单并制作菜肴。这是一个点菜单队列:服务员在队尾添加点菜单,厨师取出队列开头的点菜单并制作菜肴。你使用数组还是链表来实现这个队列呢?(提示:链表擅长插入和删除,而数组擅长随机访问。在这个应用程序中,你要执行的是哪些操作呢?)

链表。

与上题同理,服务员添加菜单,厨师从队列开头取出菜单中的菜肴,这两个操作一个是插入一个是删除,因此应使用链表来实现。

2.3 我们来做一个思考实验。假设Facebook记录一系列用户名,每当有用户试图登录Facebook时,都查找其用户名,如果找到就允许用户登录。由于经常有用户登录Facebook,因此需要执行大量的用户名查找操作。假设Facebook使用二分查找算法,而这种算法要求能够随机访问——立即获取中间的用户名。考虑到这一点,应使用数组还是链表来存储用户名呢?

数组。

这里需要频繁使用读取操作,使用数组的效率更高,因此应使用数组来存储用户名。

2.4 经常有用户在Facebook注册。假设你已决定使用数组来存储用户名,在插入方面数组有何缺点呢?具体地说,在数组中添加新用户将出现什么情况?

数组的插入速度很慢。另外,要使用二分查找算法来查找用户名,数组必须是有序的。

假设有一个名为Adit B的用户在Facebook注册,其用户名将插入到数组末尾,因此每次插入用户名后,你都必须对数组进行排序。

2.5 实际上,Facebook存储用户信息时使用的既不是数组也不是链表。假设Facebook使用的是一种混合数据:链表数组。这个数组包含26个元素,每个元素都指向一个链表。例如,该数组的第一个元素指向的链表包含所有以A打头的用户名,第二个元素指向的链表包含所有以B打头的用户名,以此类推。

查找时,其速度比数组慢,但比链表快;而插入时,其速度比数组快,但与链表相当。

因此,其查找速度比数组慢,但在各方面都不比链表慢。

Facebook实际使用的是什么呢?很可能是十多个数据库,它们基于众多不同的数据结构:散列表、B树等。数组和链表是这些更复杂的数据结构的基石。

选择排序

选择排序(Selection sort)是一种简单直观的排序算法。 它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。

选择排序是不稳定的排序方法。 使用Swift实现如下:

// 找出数组中最小元素
func findSmallest(_ arr: [Int]) -> Int {
    var smallest = arr[0]
    for i in 0..<arr.count {
        let n = arr[i]
        if n < smallest {
            smallest = n
        }
    }
    
    return smallest
}

func selectionSort(_ arr: [Int]) -> [Int] {
    var varArr = arr
    var res: [Int] = [Int]()
    for _ in 0..<arr.count {
        let smallest = findSmallest(varArr)
        if let index = varArr.firstIndex(of: smallest) {
            varArr.remove(at: index)
        }
        res.append(smallest)
    }
    
    return res
}

测试:

selectionSort([5,3,6,3,10,2,6,9])

结果:

[2, 3, 3, 5, 6, 6, 9, 10]

时间复杂度:O(n^2)

空间复杂度:O(n)

小结

  • 计算机内存犹如一大堆抽屉。
  • 需要存储多个元素时,可使用数组或链表。
  • 数组的元素都在一起。
  • 链表的元素是分开的,其中每个元素都存储了下一个元素的地址。
  • 数组的读取速度很快。
  • 链表的插入和删除速度很快。
  • 在同一个数组中,所有元素的类型都必须相同(都为int、double等)。