关于单链表反转的一点整理

单链表的反转困扰了我好几天了。今天终于一通百通了,特地记录一下,免得以后又忘记了。脑子笨,只能靠这种办法了。

之前网上的一种做法是这样的:

 1 public void reversList(){
 2             Node pre = null;
 3             Node next = null;
 4             while (head != null) {
 5                 next = head.next;
 6                 head.next = pre;
 7                 pre = head;
 8                 head = next; 
 9             }
10                 head = pre;
11 }

 

 核心的代码就是这一段:

但实际上做种做法是错误的。我们先看看反转之后的情况。

一遍一遍遍历的过程是这样的。每一行就是一次遍历。

1 Node [data=4, next=Node [data=3, next=Node [data=2, next=Node [data=1, next=Node [data=null, next=null]]]]]
2 Node [data=3, next=Node [data=2, next=Node [data=1, next=Node [data=null, next=null]]]]
3 Node [data=2, next=Node [data=1, next=Node [data=null, next=null]]]
4 Node [data=1, next=Node [data=null, next=null]]
5 Node [data=null, next=null]

 

这个实际上我没办法遍历,我是用最笨的办法一点一点还原出来的,如下:

1 System.out.println(list.getHead());
2 System.out.println(list.getHead().next);
3 System.out.println(list.getHead().next.next);
4 System.out.println(list.getHead().next.next.next);
5 System.out.println(list.getHead().next.next.next.next);

 

我们再看看链表在反转之前是什么样子的,同样适用一样的笨办法:

1 Node [data=null, next=Node [data=1, next=Node [data=2, next=Node [data=3, next=Node [data=4, next=null]]]]]
2 Node [data=1, next=Node [data=2, next=Node [data=3, next=Node [data=4, next=null]]]]
3 Node [data=2, next=Node [data=3, next=Node [data=4, next=null]]]
4 Node [data=3, next=Node [data=4, next=null]]
5 Node [data=4, next=null]

现在看来,反转前后,链表中有效的值只是大概相等而已,实际上并不一致。其在首尾节点上也是不一样的。

这种思路一开始是从head开始遍历开始反转的,但是其实head是一个指针节点,真正有效的节点是head.next,应该从这个开始遍历替换,所以真正应该遍历的当前节点其实是 head.next。而应该先保存的当前节点的下一个节点其实是 head.next.next。这是第一个错误。

第二个错误是:用来保存前一个节点的变量,不应该是Node  pre = null;而应该是Node pre = new Node(null)。这两种写法,完全是两个不同的东西。Node  pre = null 完全就是只有一个栈空间里的引用而已,根本没有指向堆里的空间,事实上这就不是一个对象。而Node pre = new Node(null)这种写法,是一种实打实的对象,是真正的引用指向了堆里面的空间了。所以当第二步  head.next = pre;(正确的是:head.next.next = pre.next)的时候,得到的结果就完全不一样了。如果pre=null;那就是把前一个有效节点都置为null了,这个对象就死了,真正要做的是断开前一个节点与后一个节点之间的指针连接,所以要置为null的是 pre.next,pre的指针域是null,而不是pre本身是null。这是两码事。这里我第一次看到网上的答案时,还不明白,为什么不可以直接直接Node  pre = null,这下才算是明白。

第一步第二步都错了之后,后面的步骤肯定也是错的了。

那为什么最后还可以得出一个对的假象呢?即:

Node [data=4, next=Node [data=3, next=Node [data=2, next=Node [data=1, next=Node [data=null, next=null]]]]]

最后得到的链表是这个,

这就是因为第二次循环之后,pre的next域,被恰好填充了对象,但是这个对象却是一个错误的对象如:

Node [data=1, next=Node [data=null, next=null]]

具体怎么错,以后慢慢研究吧。

正确的思路应该是,从head的next域指向的第一个有效节点开始遍历。可以直接在原来的链表上进行遍历替换。最后用反转之后的链表的第一个有效节点再接上head.next,这样head的指针域就指向了反转之后的链表。如下:

 1  public void reversList(){
 2          Node pre = new Node(null);
 3         //Node pre = null;错误写法
 4             Node next = null;
 5          
 6             while (head.next != null) {//从第一个有效节点开始遍历
 7                 next = head.next.next;//先记录当前节点的下一个节点
 8                 head.next.next = pre.next;//将当前节点的指针域置空
 9                 pre.next = head.next;//将下一节点的指针域,指向当前节点,完成节点交换
10                 head.next = next;//往后移动
11             }
12            
13                 head.next = pre.next;//最后把反转后的节点与头结点联系上。最终得到完整的反转之后的链表。
14 }

 

或者也。可以新创建一个新的链表,然后遍历老链表后,把节点添加到新链表的第一个有效节点位置。最后返回新的链表,或者用新的链表覆盖head。也许这样比较容易理解:

public void reversList(){
             Node temp = head.next;//设置一个临时变量保存第一个有效节点
             Node newHead = new Node(null);//设置一个新的头结点
             Node next = null;//用于保存下一个节点
             
             while(temp != null){
                 next = temp.next;//保存当前节点的下一个节点
                 temp.next = newHead.next;//将当前节点的指针域置空
                 newHead.next = temp;//始终把当前节点插入到新链表的第一个有效节点的位置
                 temp = next;//向后移动一个节点
             }
            
             head = newHead;//最后把新的链表的头节点赋值给原节点的头结点,完成两个链表的合并
              //   head.next = newHead.next; 这种写法也是一个一样的效果,但是行为是不一样的行为。
        }        

 如果还有不对,欢迎指正。

posted @ 2019-08-13 23:46  lukely  阅读(396)  评论(2编辑  收藏  举报