当前位置: 旋转机 >> 旋转机介绍 >> 数据结构与算法之反转链表,超详细图文解析
有如下链表:
要求对链表进行反转,反转后的链表如下:
01题目解析
反转链表,就是将链表中每一个节点的next引用指向其前驱节点。链表默认自带一个引用,这个引用指向了头节点,记为n1。首先尝试将n1的next引用进行反转:
可以发现,①的next引用指向了空,由于①切断了指向②的引用,导致n1无法移动到②和③,此时可以再引入一个引用,记为n2,n2指向②:
对②进行反转:
这时候③丢失了,是否可以复用现有的引用来访问到③呢,答案是不行的。②的前驱节点需要通过n1来访问,此时需再引入一个新的引用n3,来指向③:
对③进行反转:
这时候三节点链表就完成了反转,题目到这是否就分析结束了呢?再引入一个节点④,如图:
不难发现,④节点又丢失了。再思考,能否复用现有引用,来访问到④,光从上图的结果来看,是不行的,一旦一个节点完成了反转,其后继节点就丢失了,除非创建与链表节点数量一致的引用,每一个引用指向其中一个节点,然后按上述方式对每个节点完成反转。这种方式显然不够优雅,那能否在反转下一个节点之前,先将引用后移,再反转呢?
接下来我们尝试边反转,边移动引用。通过上述分析,反转链表至少要3个引用,可以得出移动的时机是在反转③的时候,我们在反转③之前,先后移引用,保证不丢失④:
然后反转③:
我们需要指定一个引用,专门用来反转节点next指向。显然指定中间引用n2是合适的,n1指向着n2的前驱节点,n3指向着n2的后继节点,这样可以既完成反转,又不会丢失后续的节点。因此,我们在反转③之后,继续后移引用,使得n2指向④,完成对④的反转:
这里将移动和反转做了合并,可以看到,现在整个链表已经完成了反转。
02代码实现
现在,我们只需将上述的分析结果翻译成代码即可。经过分析可知,反转链表一共需要三个引用,为了清晰直观,依次记为prev、node、next,node用于反转节点next指针。每当完成一次反转,三个引用便整体向后移动一个节点。代码实现如下:
publicstaticNodereverse(Nodenode){if(node==null
node.next==null){returnnode;}Nodeprev=null;Nodenext=node.next;//next指向空时,只需再进行最后一次反转while(next!=null){//反转节点node.next=prev;//引用后移prev=node;node=next;next=next.next;}//反转最后一个节点node.next=prev;//返回反转后的链表头引用returnnode;}
需要注意的是,当next引用指向空时,末尾最后一个节点还未反转,所以在循环外要再反转一次。
另外,这里必须将处理好的node引用在方法中返回出去,通过拿方法的返回值来获取反转后的链表。如果仍然使用传入的node,会发现node只剩下一个节点。有如下测试代码:
//定义链表:1-2-3Nodenode=newNode(1);node.next=newNode(2);node.next.next=newNode(3);System.out.println("原始链表:");show(node);//反转链表NoderNode=reverse(node);System.out.println("反转后:");show(node);show(rNode);
结果如下:
原始链表:[Node{num=1,next=2},Node{num=2,next=3},Node{num=3,next=}]反转后:[Node{num=1,next=}][Node{num=3,next=2},Node{num=2,next=1},Node{num=1,next=}]
这是因为在Java中传递的是值,而不是引用。反转后的图例如下:
在传递node时,是将node保存的内存地址复制了一份,传给了方法参数node,方法中对node的移动,不会影响方法外的node。
反转链表至此完成,在解决链表相关问题时,要时刻注意不能丢失节点,在修改节点next或者prev指针时,都要保证仍能访问到其他节点,如果发现无法复用现有引用,可以尝试新增引用。保证了这一点之后,剩下的就是按题目要求实现即可。