【算法笔记1】数组和链表
我现在有一个代办事项的列表:

现在我想把这个列表的三个事项存进内存里,每个事项占用一个内存单元。内存大概长这样:

可以看到,这三个事项被存进了内存的前三个单元中,这是三个连续的存储单元。
除了将它们存进连续的存储单元中,我们还可以将它们存进三个不相邻的单元中,像这样:

这里有一个问题,那就是,我们把这三个待办事项存进内存后,如果日后想看,怎样找到它们呢?
我们都知道,内存是按照地址找到其中存储的内容的,比如第一个内存单元的地址是0,第二个是1......
如果这些事项被存进了连续的内存单元,那么只需要知道第一个事项的地址,其余的事项都会被找到,因为它们的地址是依次递增的;
如果这些事项被存储在分散的内存单元中,则需要知道每个事项的地址,一种方法是,不仅存储每个事项,还存储它下一个事项的所在的地址。
以上第一种情况中的数据结构称为数组,第二种称为链表。
链表
链表中的每个元素不仅存储了数据内容,还存储了下一个元素的地址。这就像一个寻宝游戏,你先到第一个房间,拿到了第一个宝物,并且这个房间里面有一张纸条写着,下一个宝物在103房间,于是,你又前往103房间,拿到了第二个宝物,这时你又发现了一张纸条,写着下一个宝物在225房间......以此类推,你会找到最后一个宝物。
接着看前面待办事项的例子,假如我发现我没有茶叶了,因此,我要把“卖茶叶”这件事列入待办事项,并且想把它列在“喝茶”之前。这相当于在链表中插入一个元素。

在内存中应该怎么做呢,首先把“买茶叶”作为一个节点,它存储着“买茶叶”这个事项,和“喝茶”的节点的地址,然后将“买茶叶”节点存入内存,最后把玩地滚球(什么是地滚球??)这个节点中存储的地址改成“买茶叶”节点的地址。可以看到,不管在这个待办事项列表里插入什么事项,也不管在哪个位置插入,这个插入的动作,操作次数都是一个常数。
如果要删除一个事项呢,其实操作和插入原理是一样的,只是把节点的添加变成了删去。还有需要注意的是,插入操作不一定成功,因为可能没有多余的内存单元,但是删除操作肯定会成功。
如果要找到一个事项呢?就像上面的寻宝游戏一样,不管要找链表中的哪个元素,都必须从第一个元素找起。
数组
因为数组的元素在内存中是连续存储的,所以我们知道每个元素的地址,因此数组不需要链表那样存储下一个节点的地址,而只需要存储数据内容。
假如我有保持记录待办事项的习惯,并且要把它们存进内存之中,假如我选择用数组,那我就得在内存中给它们分配一些连续的存储单元。这就遇到了一个问题,我该分配多少存储单元呢?如果分配太多,可能用不完,浪费了内存,如果分配的太少又不够用,这时如果来了一个新事项,那所有的事项全得“搬家”到一个足够大的连续存储空间里(也有可能内存里已经没有这么大的连续存储空间了)。
再来考虑数组的插入与删除。假如我要在数组里插入一个新元素,就像上面在“喝茶”之前加入“买茶叶”事项一样,再次注意,数组是一串连续的内存单元,那么,要插入位置的所有后面的数组元素都得向后移动一个存储单元,这样就会空出一个存储单元,让新元素“住”进去。
数组的删除操作与插入操作原理相同,要删除位置的所有后面的数组元素都会向前移动一个存储单元。
说了数组半天,好像都说的是它的缺点。但是事实上,在实际应用中,数组应用的比链表多很多。这是因为数组支持随机访问,比如我想得到(也就是读取)数组的第二个元素,那只要将数组的第一个元素的地址(首地址)加2,就得到了它的地址,这个操作次数是一个常数。与此相比,链表就只能顺序访问,也就是说,不管想读取哪个元素,在链表中只能从第一个元素读起。
结论时间:

参考资料:《算法图解》[美] Aditya Bhargava 译者:袁国忠
浙公网安备 33010602011771号