前言之前详细讲过线性表(序列表和链表)。当时的链表主要是单链表,但实际上,双链表在实际应用中的应用更像是LinkedList。双链表和单链表的区别从逻辑上讲,它们都是线性表的链式实现。主要区别在于节点结构的结构不同,导致操作上有些差异。单向链表:单向链表的一个结点,有存放数据的数据,有下一个驱动结点(指针)。即如果这个单向链表要进行一些遍历操作,就必须经过前节点->后节点。双链表:双链表的一个结点有存放数据的data和一个后续结点next(指针),和单链表一样,但它还有一个前驱结点pre(指针)。双链表结构的设计上面讲单链表的时候,我们设计的是有前导节点的链表,错过了没有前导节点的运行方式。在这里,我们不设计和实现没有前导节点的双链表。而上面的单链表实现的时候是没有尾指针的,这里我们设计一个带尾指针的双链表。所以我们构建的双向链表是:无头结点,尾指针(tail),双向链表。对于node节点:classnode{Tdata;nodepre;nodenext;publicnode(){}publicnode(Tdata){this.data=data;}}对于链表:publicclassdoubleList{privatenodehead;//头节点privatenodetail;//尾节点privateintlength;//各种方法}具体操作分析链表的主要操作是增删改查。添加或删除时,不仅需要考虑链表是否为首节点,还需要考虑头部插入、尾部插入、中间插入等多种插入和删除形式。一些细节也很重要(防止链表崩溃出错)。如果对这个理解不够深入的话,很容易出错,也很难排错。当然,链表的查找和按位修改操作要比增删操作简单很多。一开始就初始化双链表,头指针指向null。所以对于这个没有头节点的双链表。它的头部总是指向第一个真实有效的节点。tail也指向最后一个有效节点。一开始head=null,tail=head,此时链表为空,等待节点插入。publicdoubleList(){head=null;tail=head;length=0;}InsertanemptylinkedlistInsertforanemptylinkedlist.特别考虑添加第一个元素。因为当链表为空时,head和tail都为null。但是head和tail需要实际指向链表中真正的数据(head指针不需要考虑)。所以这个时候,新建一个节点,让head和tail等于它。nodeteamNode=newnode(data);if(isEmpty()){head=teamNode;tail=teamNode;}headinsertion为头部插入。步骤很简单,只要考虑头节点的变化即可。新插入的节点nodehead前驱指向nodenodereardrivepointstoheadhead指向node。(此时head只代表第二个节点,head需要代表第一个节点所以换个方向)Tailinsertion:用于尾部插入。只考虑尾节点tail节点的变化。新建一个插入节点node,前驱节点指向尾部,后驱指向节点,尾部指向节点。(此时tail只表示倒数第二个节点,tail需要表示最后一个节点所以指向node)Insertbynumber是数字插入。要考虑查找和插入这两个步骤,插入与头尾无关。1新建一个插入节点node2找到上一个要插入的节点preNode。而下一个节点nextNode3节点backdrive指向nextNode,nextNode前驱指向node(此时node和后面连接链表,但与前面的处理状态分开)4preNodebackdrive指向node。节点前驱指向preNode(此时完成了完整的插入操作)。整个过程的动态图是:只删除单个节点,不分头删尾。当删除单个节点时,需要重新初始化链表!if(length==1)//只有一个元素{head=null;tail=head;length--;}头删除头删除需要注意的是,当删除不为空时,头删除只相关到头节点。过程大致分为:1.头节点的后向驱动节点的pre指针变为null。(head后面的节点指向head,但是要删除第一个,让后面的跟head断绝关系)2.head节点指向head.next(这样head就指向第一个节点我们需要,并且前面的节点删除成功。如果有C++等语言,可以删除第一个孤立节点,然后释放,但Java会自动释放)尾部删除尾部删除需要注意的是,当删除不是空,尾部删除只与尾节点有关。记住,在一个普通的链表中,我们需要找到尾节点的前驱节点来删除尾节点。需要遍历整张表,而双向链表可以直接从尾节点往前遍历。尾部删除是删除双向链表中的最后一个节点,即尾指针指向的节点。思路和headdeletion是一样的。具体步骤为:tail.pre.next=null尾节点的前一个节点(pre)tail的尾节点等于nulltail=tail.pre尾节点指向它的前驱节点。这时候由于步骤1next,尾节点已经为null。完成删除普通删除普通删除需要掌握。普通删除应该妥善处理要删除节点的上下文。具体过程如下:1:找到待删除节点的前驱节点prenode(prenode.next为待删除节点)2:prenode.next.next.pre=prenode。(将待删除节点的aftnode的pre指针指向prenode,相当于aftnode.pre=prenode)3:prenode.next=prenode.next.next;此时nodeSkipped表示删除成功。整个删除过程的动态图为:实现与测试通过以上思路,简单实现一下双链表。当然有些地方命名不规范,执行效率有待提高。主要目的是为了让大家明白。代码(代码贴成图片,需要源码的可以看原文或者发给朋友):测试:结论在插入删除这一步,可能很多人看不懂因为流程繁琐,但实际上这个操作的写法可能会有所不同,但本质操作是一样的,所以看到其他版本的差异是正常的。可能还有很多人对一堆next.next不会混淆,那我就教大家一招,如果在等号右边,那么代表一个节点,如果在等号左边等号的一边,那么除了最后一个.next代表一个节点。例如node.next.next.next可以看作是(node.next.next).next。在做数据结构和算法链表相关的题时,可能会把不同的题分配给不同的节点来完成插入和删除操作。在这种情况下,您必须小心以防止损坏链表结构。代码运行可能有一些优化空间,请指正!如果你有所收获