2.2.4 在中间插入
假设你要让待办事项按日期排列。之前,你在清单末尾添加了待办事项。 但现在你要根据新增待办事项的日期将其插入到正确的位置。
需要在中间插入元素时,数组和链表哪个更好呢?使用链表时,插入元素很简单,只需修改 它前面的那个元素指向的地址。
而使用数组时,则必须将后面的元素都向后移。
如果没有足够的空间,可能还得将整个数组复制到其他地方!因此,当需要在中间插入元素 时,链表是更好的选择。
2.2.5 删除
如果你要删除元素呢?链表也是更好的选择,因为只需修改前一个元素指向的地址即可。而 使用数组时,删除元素后,必须将后面的元素都向前移。
不同于插入,删除元素总能成功。如果内存中没有足够的空间,插入操作可能失败,但在任 何情况下都能够将元素删除。
下面是常见数组和链表操作的运行时间。
需要指出的是,仅当能够立即访问要删除的元素时,删除操作的运行时间才为O(1)。通常我 们都记录了链表的第一个元素和最后一个元素,因此删除这些元素时运行时间为O(1)。
数组和链表哪个用得更多呢?显然要看情况。但数组用得很多,因为它支持随机访问。有两 种访问方式:随机访问和顺序访问。顺序访问意味着从第一个元素开始逐个地读取元素。链表只 能顺序访问:要读取链表的第十个元素,得先读取前九个元素,并沿链接找到第十个元素。随机 访问意味着可直接跳到第十个元素。本书经常说数组的读取速度更快,这是因为它们支持随机访 问。很多情况都要求能够随机访问,因此数组用得很多。数组和链表还被用来实现其他数据结构, 这将在本书后面介绍。
2.3 选择排序
有了前面的知识,你就可以学习第二种算法——选择排序了。要理 解本节的内容,你必须熟悉数组、链表和大O表示法。 假设你的计算机存储了很多乐曲。对于每个乐队,你都记录了其作 品被播放的次数。
你要将这个列表按播放次数从多到少的顺序排列,从而将你喜欢的乐队排序。该如何做呢? 一种办法是遍历这个列表,找出作品播放次数最多的乐队,并将该乐队添加到一个新列表中。
再次这样做,找出播放次数第二多的乐队。
继续这样做,你将得到一个有序列表。
下面从计算机科学的角度出发,看看这需要多长时间。别忘了,O(n)时间意味着查看列表中 的每个元素一次。例如,对乐队列表进行简单查找时,意味着每个乐队都要查看一次。
要找出播放次数最多的乐队,必须检查列表中的每个元素。正如你刚才看到的,这需要的时 间为O(n)。因此对于这种时间为O(n)的操作,你需要执行n次。
需要的总时间为 O(n × n),即O(n2 )。 排序算法很有用。你现在可以对如下内容进行排序:
- 电话簿中的人名
- 旅行日期
- 电子邮件(从新到旧)
**需要检查的元素数越来越少 **
随着排序的进行,每次需要检查的元素数在逐渐减少,最后一次需要检查的元素都只有一 个。既然如此,运行时间怎么还是O(n2 )呢?这个问题问得好,这与大O表示法中的常数相关。 第4章将详细解释,这里只简单地说一说。 你说得没错,并非每次都需要检查n个元素。第一次需要检查n个元素,但随后检查的元素 数依次为n 1, n – 2, …, 2和1。平均每次检查的元素数为1/2 × n,因此运行时间为O(n × 1/2 × n)。 但大O表示法省略诸如1/2这样的常数(有关这方面的完整讨论,请参阅第4章),因此简单地写 作O(n × n)或O(n2 )。
选择排序是一种灵巧的算法,但其速度不是很快。快速排序是一种更快的排序算法,其运行 时间为O(n log n),这将在下一章介绍。
示例代码
前面没有列出对乐队进行排序的代码,但下述代码提供了类似的功能:将数组元素按从小到 大的顺序排列。先编写一个用于找出数组中最小元素的函数。
2.4 小结
- 计算机内存犹如一大堆抽屉。
- 需要存储多个元素时,可使用数组或链表。
- 数组的元素都在一起。
- 链表的元素是分开的,其中每个元素都存储了下一个元素的地址。
- 数组的读取速度很快。 链表的插入和删除速度很快。
- 在同一个数组中,所有元素的类型都必须相同(都为int、double等)