数据结构之链表

栏目: 数据库 · 发布时间: 5年前

内容简介:链表和数组一样,都是编程语言中经常用到的数据结构。相比数组,链表是一种稍微复杂一点的数据结构,我们可以两者对比着来看数组需要一块

链表和数组一样,都是编程语言中经常用到的数据结构。

相比数组,链表是一种稍微复杂一点的数据结构,我们可以两者对比着来看

数组需要一块 连续的内存空间 来存储数据,对内存的要求比较高,如果我们申请一个100MB大小的数组,如果内存中没有一个连续的100MB的空间,即使内存中剩余的空间大小大于100MB,也会申请失败

链表就不一样了,它不需要一块连续的内存空间,它是 通过‘指针’把一组零散的内存块串联起来 所以如果剩余内存如果大于100MB,即使不是连续的也没问题。

链表有很多种,比较常见的是:单链表、双向链表、循环链表。

单链表:

数据结构之链表

链表是通过指针把一组零散的内存块串联起来,我们把内存块称为链表的‘结点’为了把所有的结点串起来,每个链表的结点除了存储数据之外,还需要记录链上的下一个结点的地址。我们把这个记录下一个结点的地址的指针叫 做‘后继指针 next’

从上面的图中可以看到,有两个结点是特殊的,那就是第一个结点和最后一个结点,即 头结点尾结点 ,头结点用来记录地阿里表的基地址,有了它我们就可以遍历得到整条链表,而为节点最后指向的不是下一个结点,而是 一个空的地址null

和数组一样,链表也支持数据的查找、删除、插入操作。不过执行的效率他们之间是不同的,对于插入和删除操作,链表的效率要高,对于根据下标查询操作,数组的效率就高了。

从上一篇数组我们知道,数组在插入和删除数据的时候,为了保证内存数据的连续性,需要做大量的数据搬移,而在链表中插入或者删除一个数据,我们并不需要保证内存的连续性,也就不用进行大量的数据搬移,因为链表的存储空间本来就不是连续的。我们只需要考虑相邻的结点的指针改变就好了,所以删除和插入操作在链表中是非常快速的。

但是链表想要随机访问第k个元素,就没有数组那么高效了,因为链表中的数据并不是连续的,所以不能像数组那样,根据首地址和下标,通过寻址公式就能直接计算出对应的内存地址,而是需要根据指针一个结点一个结点的依次遍历,直到找到相应的结点。

我们可以把链表想象成一个队伍,每个人都只直到自己的后面是谁,所以,当我们想要直到排在第N个位置的人是谁的时候,只能从第一个开始一个一个往下数。

循环链表:

数据结构之链表

循环链表是一种特殊的单链表,实际上,循环链表也很简单,它跟单链表唯一的区别就是它的尾结点,单链表中是指向一个空地址,表示这是最后一个结点了,而循环链表的尾结点指向链表的头结点。

和单链表相比,它的优点就是从链尾到链头比较方便,当要处理的数据具有环形结构特点的时候,适合采用循环链表,比如 约瑟夫问题

双向链表:

数据结构之链表

跟单链表只有一个方向不同,双向链表支持两个方向,每个节点上除了有一个‘后继指针next’指向后面的结点外,还有一个‘前驱指针prev’指向前面的结点。

双向链表需要额外的空间来来存储后继结点和前驱结点的地址。所以如果存储同样多的数据,双向链表要不单链表占用更多的内存空间。虽然两个指针比较浪费空间,但是可以支持双向遍历,这样也带来了操作上的灵活性。

从表结构上看,双向链表既知道前驱结点也知道后继结点,插入和删除结点等操作在某些情况下比单链表更加简单高效。

那吗些地方会更高效呢

比如删除操作,一般我们删除一个数据无外乎两种情况:

一个是删除节点中值等于某个给定值的结点,

一个是删除给定指针指向的结点

第一种情况,不管是单向还是双向链表,其实都是一样的,都得从头结点开始一个一个的遍历比对,直到找到相对应的值,然后杉树。

第二种情况就不一样了,我们已经找到了要删除的结点,但是删除某个结点,我们需要直到其前驱结点,让其前驱结点的next指向其后继结点的prve,但是单链表不能直接获取其前驱结点,为了找到其前驱结点,还得从头开始遍历链表。但是双向链表就不同了,双向链表中的结点已经保存了其前驱接单的指针,不需要再遍历查找了。所以这种情况下双向链表效率比单向的高。

同理当我们想要插入一个元素的时候,跟上面的删除操作一样,第二种情况下,双向链表更有优势。

对于一个有序的链表来说,双向链表的按值查询的效率也比单链表的要高,因为我们可以记录上次查找的位置p,下次查询的时候,根据查找的值于p的值比较来决定往前查找还是往后查找,而单链表就只能往后查找。

从上面的分析我们知道,双向链表在很多时候都比单链表效率高,这也是为什么实际的软件开发中,双向链表尽管比较费内存,但是比单链表应用更加广泛的原因。java中LinkedHashMap这个容器,其内部原理就是一个双向链表。

这里有一个用时间换空间的设计思想,当内存空间充足的时候,如果我们追求更快的代码执行速度,就可以选择空间复杂度交高时间复杂度较低的算法结构,反之,如果内存紧缺,就可以选择用时间换空间的设计思路了。

实际上缓存就是一个利用空间换时间的设计思想,如果我们把数据存储在硬盘上会比较节省内存,但是每次查找数据访问硬盘会比较慢,如果我们通过缓存技术,事先把数据加载到内存中,虽然会耗费一定的内存空间,但是每次查询数据的时间就快了。

双向循环链表:

双向链表结构如下图

数据结构之链表

使用链表实现一个LRU缓存淘汰算法:

维护一个有序的单链表,最早访问的放在尾部,当有一个新的数据被访问的时候,从链表的头部开始遍历链表:

(1)遍历此链表,如果此数据之前已经被缓存在链表中了,就删除它,然后把新的放在链表头部。

(2)遍历链表,如果数据没有在缓存链表中

<2.1>如果缓存未满,将此节点直接插入到链表的头部

<2.2>如果缓存满了,删除尾部结点,把新的数据插入到链表的头部。

OK这样就使用链表实现了一个LRU缓存。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Algorithm Design Manual

The Algorithm Design Manual

Steven S Skiena / Springer / 2011-11-14 / GBP 55.07

....The most comprehensive guide to designing practical and efficient algorithms.... Written by a well-known algorithms researcher who received the IEEE Computer Science and Engineering Teaching Aw......一起来看看 《The Algorithm Design Manual》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

在线进制转换器
在线进制转换器

各进制数互转换器

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器