本人已参与「新人创作礼」活动,一起开启掘金创作之路。
概述
第二章主要内容是从数组、链表等基本数据结构入手,介绍了算法的基本内容。再着重讲解选择排序的实现及思想,让我们了解了一种简单直接的排序算法。
数组 & 链表
数组
数组在内存中直接获取一块连续的地址,来存储数据。当数据量超过已有地址时,将无法继续存储,需要搬迁整个数组。 由于数组的内存地址是连续的,所以十分易于读取,但并不容易插入和删除。
链表
链表在内存中通过每个元素存储下一个元素的地址,使得一系列随机的内存地址串在一起。 想要在链表中插入新的元素,只需要随机在内存中获取地址即可;想要在链表中删除一个元素,只需要修改前一个元素指向的地址,使其跳过要删除的元素即可。 因此,链表易于插入删除,而不易于读取。
数组和链表的操作时间复杂度对比
| 操作 | 数组 | 链表 |
|---|---|---|
| 读取 | O(1) | O(n) |
| 插入 | O(n) | O(1) |
| 删除 | O(n) | O(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等)。