1.链表与数组
需要存储多个元素时,可使用数组或链表。
数组 | 链表 | 小结 | |
---|---|---|---|
概念 | 数组的元素都在一起,使用数组意味着所有数据在内存中都是相连的,数组的元素带编号,编号从0开始。 | 链表的元素是分开的,其中每个元素都存储了下一个元素的地址。链表中的元素可存储在内存的任何地方。 | |
添加新元素 | 在数组中添加新元素可能很麻烦:如果没有了空间,就得移到内存的其他地方,因此添加新元素的速度会很慢。 | 在链表中添加元素很容易:只需将其放入内存,并将其地址存储到前一个元素中。 | 链表添加元素更容易。 |
读取元素 | 需要随机地读取元素时,数组的效率很高,因为可迅速找到数组的任何元素。 | 需要同时读取所有元素时,链表的效率很高:你读取第一个元素,根据其中的地址再读取第二个元素,以此类推。但如果你需要跳跃,链表的效率真的很低。 | 数组的读取速度更快。 |
插入元素 | 使用数组时,必须将后面的元素都向后移。如果没有足够的空间,可能还得将整个数组复制到其他地方! | 使用链表时,插入元素很简单,只需修改它前面的那个元素指向的地址。 | 链表的插入速度更快。 |
删除元素 | 使用数组时,删除元素后,必须将后面的元素都向前移。 | 使用链表时,只需修改前一个元素指向的地址即可。 | 链表的删除速度更快。 |
访问方式 | 随机访问:意味着可直接跳到第十个元素。 | 顺序访问:从第一个元素开始逐个地读取元素。 | 数组更常用。 |
2.选择排序
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。
https://baike.baidu.com/item/%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F/9762418?fr=aladdin
示例:将数组元素按从小到大的顺序排列
# Finds the smallest value in an array
def findSmallest(arr):
# Stores the smallest value
smallest = arr[0]
# Stores the index of the smallest value
smallest_index = 0
for i in range(1, len(arr)):
if arr[i] < smallest:
smallest_index = i
smallest = arr[i]
return smallest_index
# Sort array
def selectionSort(arr):
newArr = []
for i in range(len(arr)):
# Finds the smallest element in the array and adds it to the new array
smallest = findSmallest(arr)
newArr.append(arr.pop(smallest))
return newArr
print(selectionSort([5, 3, 6, 2, 10]))
练习
2.1 假设你要编写一个记账的应用程序。你每天都将所有的支出记录下来,并在月底统计支出,算算当月花了多少钱。因此,你执行的插入操作很多,但读取操作很少。该使用数组还是链表呢?
2.2 假设你要为饭店创建一个接受顾客点菜单的应用程序。这个应用程序存储一系列点菜单。服务员添加点菜单,而厨师取出点菜单并制作菜肴。这是一个点菜单队列:服务员在队尾添加点菜单,厨师取出队列开头的点菜单并制作菜肴。[插图]你使用数组还是链表来实现这个队列呢?(提示:链表擅长插入和删除,而数组擅长随机访问。在这个应用程序中,你要执行的是哪些操作呢?)
2.3 我们来做一个思考实验。假设Facebook记录一系列用户名,每当有用户试图登录Facebook时,都查找其用户名,如果找到就允许用户登录。由于经常有用户登录Facebook,因此需要执行大量的用户名查找操作。假设Facebook使用二分查找算法,而这种算法要求能够随机访问——立即获取中间的用户名。考虑到这一点,应使用数组还是链表来存储用户名呢?
2.4 经常有用户在Facebook注册。假设你已决定使用数组来存储用户名,在插入方面数组有何缺点呢?具体地说,在数组中添加新用户将出现什么情况?
2.5 实际上,Facebook存储用户信息时使用的既不是数组也不是链表。假设Facebook使用的是一种混合数据:链表数组。这个数组包含26个元素,每个元素都指向一个链表。例如,该数组的第一个元素指向的链表包含所有以A打头的用户名,第二个元素指向的链表包含所有以B打头的用户名,以此类推。假设Adit B在Facebook注册,而你需要将其加入前述数据结构中。因此,你访问数组的第一个元素,再访问该元素指向的链表,并将Adit B添加到这个链表末尾。现在假设你要查找Zakhir H。因此你访问第26个元素,再在它指向的链表(该链表包含所有以z打头的用户名)中查找Zakhir H。请问,相比于数组和链表,这种混合数据结构的查找和插入速度更慢还是更快?你不必给出大O运行时间,只需指出这种新数据结构的查找和插入速度更快还是更慢。
答案
2.1 在这里,你每天都在列表中添加支出项,但每月只读取支出一次。数组的读取速度快,而插入速度慢;链表的读取速度慢,而插入速度快。由于你执行的插入操作比读取操作多,因此使用链表更合适。另外,仅当你要随机访问元素时,链表的读取速度才慢。鉴于你要读取所有的元素,在这种情况下,链表的读取速度也不慢。因此,对这个问题来说,使用链表是不错的解决方案。
2.2 使用链表。经常要执行插入操作(服务员添加点菜单),而这正是链表擅长的。不需要执行(数组擅长的)查找和随机访问操作,因为厨师总是从队列中取出第一个点菜单。
2.3 有序数组。数组让你能够随机访问——立即获取数组中间的元素,而使用链表无法这样做。要获取链表中间的元素,你必须从第一个元素开始,沿链接逐渐找到这个元素。
2.4 数组的插入速度很慢。另外,要使用二分查找算法来查找用户名,数组必须是有序的。假设有一个名为Adit B的用户在Facebook注册,其用户名将插入到数组末尾,因此每次插入用户名后,你都必须对数组进行排序!
2.5 查找时,其速度比数组慢,但比链表快;而插入时,其速度比数组快,但与链表相当。因此,其查找速度比数组慢,但在各方面都不比链表慢。本书后面将介绍另一种混合数据结构——散列表。这个练习应该能让你对如何使用简单数据结构创建复杂的数据结构有大致了解。Facebook实际使用的是什么呢?很可能是十多个数据库,它们基于众多不同的数据结构:散列表、B树等。数组和链表是这些更复杂的数据结构的基石。
《算法图解》- Aditya Bhargava