学Java怎么能不懂链表?通俗易懂
小白: 庆哥庆哥,链表是什么啊?😂
庆哥: 呦西,那么爱学习啊,那咱今天就来把链表怼一怼吧,争取以后再也不学链表啦😎
小白: 为啥以后再也不学啦,难道链表没什么用吗?😁
庆哥: 那必须不啊,链表不仅有用而且很有用,必须好好掌握,为啥以后不再学了呢?因为经过这次的学习,你就再也忘不掉啦😎
小白: 那庆哥,我可先说好了,我之前可是对链表一窍不通啊😂
庆哥: 那没事,谁不学那都是不会滴,学了不就毁了嘛,接下来咱就一起学习链表,攻克这个知识点,记住,有什么不懂得要赶紧问哦。
小白: 好呀,我的第一个问题来啦,什么是链表啊🤣
庆哥: 首先就从“链表”二字来看,你觉得是个啥?想一想。链表?链子?表?😀,有想法没😂,有的时候对于名字啊,为了便于大众的理解,名字一般就简单的体现了它是个啥,所以链表是俩字,一个链一个表,那什么是链什么是表呢?
1️⃣啥是链表?拆开来看😂
小白: 这个啊,我想想,链是不是就是我们常见的链子啊😂
庆哥: 是啊,就是这样的😂。,大铁链子,不不,大金链子
你看,就是这个。怎么样,看到这个大金链子,有没有觉得自己对于“链”这个词有点意思了,知道它大概是个什么玩意了吧。 小白: 嗯嗯,是的,链子嘛,就像这个大金链子一样,一环扣一环的😁,那啥是表呢?我记得之前常说这个表啊,什么数据表,对,还有之前说的哈希表,这个表是个啥啊? 庆哥: 的确,我们之前一直都在说什么这表那表的,但是啥是表呢?难道是我们戴的那种手表吗?钟表?当然不是啦🤣,咱们这说的表一般是指:
按照一定顺序排列的元素集合,那就是表
啥意思呢?也就是说啊,咱们这里的表指的也是一些数据集合,啥是数据集合嘞,就可以理解成一堆数据😂,只不过这数据人家有一定的顺序。
小白: 顺序?是不是那种按照12345这样排序啊😁
庆哥: 这里的排序啊,是这样的,它嘞,有一个前驱后继的概念,啥意思咧,看图:
其实也好明白,说白了,在这个表中存储的也就是数据,这些数据是按照顺序排列的,这里的顺序的理解是这样的,就是表中的每个元素就是一个数据吧,这些个数据,它的前面也有数据,后面也有数据,前面的数据就叫做这个数据的前驱,那后面的嘞,自然就是后继了。
小白: 那每个数据都是这样的吗😃
庆哥: 那可不一定啊,你想想最前面的后最后面的那个,是不是最前面的只有后继,最后面的只有前驱呢?你可以想想那种排队的,最前面那货和最后面那货😁
小白: 嗯嗯,这下对表这个概念有点理解了,说白了,不就是数据集合嘛😂
庆哥: 可以的,就可以这么简单的理解,清楚这个概念之后,再看“链表”,知道是个啥了吗?有没有觉得概念清晰一点了。
小白: 嗯嗯,经过前面的大铁链子还有这个表的理解,现在对链表是个啥,明白了许多,我现在的理解就是,链表就跟个大铁链子似的,不过每个节点不是大铁环子😂,而是一个数据🤣
庆哥: 哈哈,可以的,这样理解也是可以的,不过你觉得这个每个节点的数据是咋样的呢?
2️⃣链表长啥样?
小白: 经过上面的讲解,我现在对链表的理解,它就是这样的:链表嘛,就跟大铁链子似的,一个扣着一个,每一环其实都是数据。
庆哥: 可以的,这样理解不错,不过还不是太准确,虽然说链表跟大铁链子似的,毕竟人家不是大铁链子啊😂,不是真的像大铁链子那样,一环扣着一环的,大铁链子是物理上存在的,可以互相的一环扣着一环,但是像链表,人家是个数据结构,不是那么可以看得见摸得着的,所以,人家的链接可不是像大铁链子那样,那么,你想想,对于链表而言,它的每个数据是怎样链接的呢?
小白: 这个啊,让我想想😒,数据之间互相链接起来?咋链呢?不能手牵手吧🤣,哦哦,我想起来了,可以用指针啊,就是用个指针指向下一个数据,这样也行吧?就像这样:
行吗?😃
庆哥: 必须可以啊,实际上,链表中就是依靠指针互相链接的,那么你对指针了解的多少呢?
小白: 指针啊,掌握的不是太好,这个是在学习C语言中学的,在java中没有指针吧,好像java中的引用和指针类似。
庆哥: 是的,指针可以说是c语言中的精髓啊,比较难理解,而且容易出错,所以java语言在设计的时候号称吸收C语言的特定,摒弃其难理解且容易出错的指针,实际上,在java中的引用就和指针类似,在学习链表的过程中,理解指针的概念很重要,这将有助于对链表的理解和掌握。
小白: 嗯嗯,我也知道指针的重要性,但是,对于指针的话,我觉得理解起来好费劲啊,也不知道是不是自己没有找到好的方法去理解,反正对于指针的概念总觉得模模糊糊的🤣。
庆哥: 指针确实是个值得研究的东西,这次我也不能花大篇幅去给你说指针,这里咱们针对这个链表去把这里的指针给弄清楚就行了,我们先来看个图:
这个图应该是比较清楚的吧,这个图中链表才是链表的真是样子,不是这样哦😁
小白: 嗯嗯,单独看你花的这个链表和我这个有点像啊
详细讲解链表节点
庆哥: 确实如此,你画的这个基本上可以说就是一个链表的样子,但是我觉得这里有个很重要的点必须画出来,那就是链表中的每个节点不单单是一个数据,而是基本的包括两个部分(这里暂时说的是单链表),一部分是实际存储的数据,一部分则是next指针,也就是指向下一个数据节点的指针,所以,以下这个图对理解链表比较重要:
就是这个,也就是说在链表中啊,每个节点包含两部分,就像上图那样,一个是data,一个next,data就相当于实际存储的数据值,而next指针的,也是存储的一个数值,这个数值是下一个数据的内存地址。
小白: 这个?有点不明白啊,data是保存的实际的数据值这个知道,不过对于这个next还是有点模糊啊。
庆哥: 嗯嗯,那好,我们现在来看这个图:首先左边的这个数据1知道是个啥吧,它就相当于一个链表中的一个节点,下面是一个完整的链表:
很容易看出来,这样的一个节点有两部分组成,一个是data,一个next,那么,data是啥呢?next又是啥呢?首先你对这个图了解吗?
小白: 这是个数据在内存中的存储吧,有点模糊。😂
庆哥: 那我给你大白话的讲一下,首先嘞,这里有个内存,内存是啥,可以供我们存储数据吧,那好,我们现在要存储数据了,比如我们要存储3个数据,现在就要向内存申请三块地方供我们存储,于是内存给我们划分了三块地方,我们分别存上三个数据,就如上图中那样,分别存储数据123,这个知道吧?
小白: 嗯嗯,你这说的很清楚,明白了。😀
庆哥: 那好,咱们继续,现在你既然给内存要了三块地址,那么内存总得给这三块地址编个号吧,毕竟这三块地方被使用了,里面还存放着数据,假如有人要要这些数据的话,我好告诉它去哪里找这些数据啊,于是乎,这三块地方被编上号了,就是图中的那样,这个明白吧😏
小白: 嗯嗯,原来是这么回事啊,经过你这么一讲,我倒是明白的很呐😂
庆哥: 嗯嗯,这里还需要进一步说明,我们之前不是说了吗,链表中的一个数据节点包含两个部分,一个data和一个next,比如我们要在链表中存储一个数值5,那么它就是这样的:
看到了没,在申请的三块地方,第一块地方被命名成0x0000,这块内存空间存放的就是数据1,数据1嘞,其实分为两部分,一部分存储数值5,一部分则存储next指针,也就是0x0001,这不就是第二块内存空间的地址嘛,我们根据数据1中存储的next指针也即是0x0001就可以找到第二块内存空间,从而找到数据2啊。
咋样,明白不?😅
链表像数组?
小白: 这块经你这么说,确实明白了很多,不过这里我怎么感觉链表有点像数组啊,内存空间是连续分布的吗?你看这0x0001,0x0002😂
庆哥: 确实,你这个问题问的很好,我们知道数组是按照顺序排列的,在内存中申请空间也是一整块的,连续的,链表也是这样吗?答案当然是不,其实,链表它的常态是这样的:
你看,跟上面的那样有什么区别吗?
小白: 嗯嗯,不一样的就是内存中申请的内存空间不是连续的了,这个比较散,咦,不对啊,在上面那个连续的地方,数据1的next指针存放的是0x0001,指向的就是数据2,那么现在这样不对吧🤣
庆哥: 可以啊,看的很仔细嘛,确实,这里的数据1的next指针存储的空间地址就不是0x0001了,因为要指向数据2,那么数据1的next就应该存放的是数据2的内存地址,也就是0x0008,就是这样:
咋样,这下清楚了吧?
链表的特点有啥?
小白: 嗯嗯,明白了,那链表和数组有啥区别呢?也就是说,我想知道的是链表有啥特点啊?😂
庆哥: 想知道链表和数组的区别是吧,那你知道数组有啥特点不?
小白: 数组啊,它比较明显的特点不就是支持随机读取吗,也就是读取的效率比较高,因为可以使用下标来直接访问,而且数组的内存空间都是连续的,大概是这个样子的:
这里是一片内存空间,红色的代表一个数组的空间地址分布,它都是连续的,占用一整块的内存空间。
庆哥: 可以啊,数组基本就是这样的,不过关于数组后面我们单独会进行探讨,这里这样的理解已经可以了,现在我们知道数组是这样的,那链表的,它是这样的:
看见了吧,链表的分布式没有顺序的,非连续的,也就是随心所欲,想在哪在哪,那怎么找到下一个的,就是靠指针连接的。那根据这个图,想一下,链表有啥特点嘞😲
小白: 想想啊,对于数组,内存分布式连续分布的,支持下标随机读取,如果要插入删除的话,则需要进行数据移动啥的,所以对于数组吧,读取效率高,但是插入删除操作就不咋滴了,再看这个链表,因为内存分布不连续,想在哪就在哪,只要有空地就可以用,那按照这样说,链表的插入删除效率比较高啊😂
庆哥: 那是为啥嘞?
小白: 我是这样想的,就比如链表的插入吧,就是新增加一个元素呗,就像这样:
就像这样,中间那个深红色的是新增加的,那么只需要把指针指向更改一下就ok啦😂
庆哥: 哈哈,说的很正确啊,666啊,那链表的读取嘞?😁
小白: 读取吗?这个?我想想啊,数组可以支持下标直接读取,那效率比较高,这个链表没啥下标,那么只能是找到一个数据之后只能根据当前数据保存的下一个数据的指针找到下一个数据,其他的也是依次类推吧😁
庆哥: 嗯嗯,说的很对的😎,所以说啊,对于链表来说,插入删除这类操作比较快速,但是读取的话就不像数组那样,只能一个一个的找啦😂
小白: 嗯嗯,那这就是我们所说的链表了吗?😁
还有双向链表嘞
庆哥: 对,你这一问,我倒是要好好说说的了,上面我们讨论的这种链表,就是这样的:
这个实际上叫做单链表,除了这个还有双向链表,那双向链表是啥嘞,你自己想猜一下,这个双向链表是个啥?😃
小白: 单向链表,双向链表?我想想,哦哦,是不是这样啊,单向链表我们上面的图显示的是那些箭头都朝一个方向,那上双向链表是不是还有相反方向的箭头呢?😊 就像这样:
是不是这样啊?😂
庆哥: 嗯嗯,理解的很对,那你知道这箭头都是啥嘛?😂
小白: 这个不就是代表指针指向嘛😁
庆哥: 是的啊,那么你看我们之前的图:这里我们之前说的,next指针存放的是下一个数据的内存空间的地址,通过这个next可以找到下一个数据,那么你再看你画的这个:
有没有发现问题,这样的双箭头,next到底存的啥呢?😂存放的下一个数据的内存空间地址嘛?那好,那由data指向next的又是咋回事呢?难道data中也存放空间地址嘛?不对吧,人家data存放的是实际数据啊。
小白: 你这样一说,好像是的啊😂,迷糊了🤣
庆哥: 它其实是这样的:
看到了不,这里多了一个prev,这是啥呢?想一想
小白: 你这样一画,我就明白很多了,这个next代表指向后面的指针,那么这个prev是不是就是指向前面的指针啊😁
庆哥: 嗯嗯,是的,next和prev都是用来存放空间地址的,next就像我们之前说的存放的是后一个数据的内存空间地址,而prev就是存放的前一个数据的内存空间地址。
小白: 哦哦,这也就是双向链表啊,那么,这两端的null是咋回事啊😯
庆哥: 这个啊,也简单啊,你想啊,最前的那个数据,他前面是不是没有数据了,那prev不就是指向null了,那么最后的那个数据同理啊😃
小白: 吆西,懂了懂了😁
庆哥: 那么你知道啥是循环链表嘛?😎
小白: 啥,还有循环链表🤣,我想想啊,循环的话是不是头尾相连啊,就像这样:
是不是?😃
庆哥: 可以的,完全正确。
小白: 感觉今天学到好多知识啊,那么链表就这些内容了吗?
3️⃣链表的基本操作
查找操作
庆哥: 为了加深理解,我们再来看看链表的一些基本操作,首先嘞,我们来看看链表的查找操作,要知道这里的查找,其实就是查找某个节点,这里我们还拿单向链表来说吧,看下面的这个链表:
假如现在我们要查找数据2这个节点,怎么查找呢,首先我们得知道,链表可不像数组那样,可以通过下标来直接访问,对于链表来说,如果要查找数据的话,首先要从头结点开始查找,这里头结点就是数据1
然后判断是不是自己需要的数据,如果不是根据next找到下一个节点:
以此来进行链表数据的查找,知道找到我们需要的额数据。
更新数据
那么对链表的更新操作呢?你觉得链表的更新是怎样的?
小白: 更新数据啊,我觉得吧,首先应该也是找到需要更新的那个数据吧,找到之后再把该数据进行更换,更换的话应该比较简单吧,直接将该节点数据换一下就行吧,就像这样:
比如要更改数据2,找到这个节点之后直接把新的数据替换掉旧的就可以。是不是这样?😁
庆哥: 可以啊,理解的很正确啊,对于链表的更新操作就是这样,下面我们再来看链表的插入操作,关于链表的插入操作,我们需要好好的学习下,因为链表的优势就是在这里,插入删除的效率比较高,简单点就是把指针的指向改变一下,下面我们来集中的看下链表的插入操作。
插入数据
这里需要知道的是链表的插入操作分为三种情况,你想一下有哪三种情况😏
小白: 既然是链表的插入操作,我先来看看链表的这个图吧
按照这个图来说的话,插入的话,那应该就是插入的位置不同,按照插入的位置,那应该是插入在第一个位置,插入到中间或者是插入到最后一个位置😂
庆哥: 分析的很对啊😉,其实就是分为
1.头部插入2.中间插入3.尾部插入
也就是这三种插入情况,下面我们来逐一看一下,首先是这个头部插入,顾名思义,头部插入嘛,也就是你新增加的那个数据是排在第一位的,像这样:
看图感觉也是很简单的,这里要注意的就是要把新插入的这个数据的next指针指向原来的头结点,自然的,现在新插入的这个数据就变成了新的头结点了。
下面再来看中间插入,这个中间插入相对来说较为复杂一点,怎么回事呢?我们来看:
看到了吧,对于中间插入,也是指针的更换,这里是要在数据2和3之间插入一个新的节点,那么数据2的next指针就不再指向数据3了,而是指向新插入的这个数据,而新插入的这个数据的next指针则指向原有的数据3,这就完成了数据节点的插入操作。
紧接着我们再来看最后的一个插入,也就是尾部插入了,尾部插入应该是最简单的一个,看图就知道了直接将最后一个数据节点的next指针指向新加入的数据节点即可。
怎么样,这下对链表的插入有所了解了吧😎
删除数据
小白: 嗯嗯,这下感觉明白了很多啊,也更加清晰了对这些概念,是不是就剩下最后一个删除操作啦,我觉得这个删除操作和插入操作很类似啊,我来说一下,你看看对不对。
首先对于删除操作应该也是分为三种情况
1.头部删除2.中间删除3.尾部删除
对于这个头部删除的话,其实也就是把第一个头结点删除掉,然后第二个节点现在成了头结点,而中间删除的话是这样:
比如这里要删除数据3,那么就需要将数据2的next指针指向数据4,而不再是数据3了,那么对于尾部删除的话,也比较简单,就是删除掉最后一个数据节点,倒数第二个的数据结点成了最后一个,自然它的next指针就指向null了。
我理解的对吗?😂
4️⃣总结
庆哥: 厉害厉害,理解的非常对,对于链表的删除操作就是这样的,好了到了这里关于链表咱们大致就说的差不多了,接下来稍微总结一下。
其实对于链表吧,它包含的知识点还有很多,如果要深入研究的话,那要花费的时间还有很多,所以,学习链表还是急不得,要一步步来,慢慢去学习,而且对于链表的学习,我们最后是要回手写链表的,关于这些内容,咱们以后会慢慢去探讨。
对于链表的理解,我个人觉得很重要的一点就是下面这个图:
也就是说,链表中的每个节点其实是包含两部分,一个是data,一个是next,当然咱这里是针对单链表来说的,这个data好理解,就是存储实际数据的,而这个next呢?它也是存放数据的,只不过存放的是一个内存空间的地址,这个内存空间地址值就是下一个结点的内存空间地址,链表就是这样互相链接起来的。
所以,链表不像数组那样,需要一个连续的内存地址,只要还有内存空间还有,那么链表就可以一直存储,有种见缝插针的感觉,反正结点之间是依靠指针链接的。
而对于链表的基本操作,说简单点,就是指针的指向发生了改变,当然,很多事情都是说着容易做着难,很多人虽然对链表清楚了,但是真的让上手去写一个链表就直接懵逼了😂,这是大家都会遇到的问题,别担心,后续我们再继续一起学习。
慢慢来,不着急,这次就先到这了,回见!😁
觉得不错,点个赞呗!