算法通关村第二关 —— 终于学会链表反转

目录

链表反转

方法一 建立虚拟头结点辅助反转

方法二 直接操作链表实现反转

方法三 递归实现

总结

链表反转

链表反转是一个出现频率特别高的算法题,在各大高频题排名网站也长期占领前三,所以链表反转是我们学习链表最重要的问题,没有之一,今天我们就一起来学习解决链表反转问题吧。

方法一 建立虚拟头结点辅助反转

在前面的文章我们已经了解过了链表元素的插入删除,会发现如何处理头结点是个比较麻烦的问题,为此可以先建立一个虚拟结点dummyNode, 并且令dummyNode.next = head, 这样我们就始终可以得到我们的头结点了。

对于链表反转问题,例如如下链表{1 -> 2 -> 3 -> 4 -> 5}, 我们先建立虚拟节点dummy,并令dummy.next = node(1), 然后我们每次从旧的链表中拆下下一个结点并接到dummy后面,再调整线路就好了。接下来执行同样的操作,完成最后一步之后返回新的头结点dummy.next即可,如下图所示:

算法通关村第二关 —— 终于学会链表反转_第1张图片

具体实现代码如下:

public static ListNode reverseListByDummyNotCreate(ListNode head) {
    // 建立虚拟节点并指向头结点
    ListNode dummy = new ListNode(-1);
    // 建立临时结点,该节点为即将插入的结点
    ListNode cur = head;
    while (cur != null) {
        ListNode next = cur.next;
        // 将旧的头结点,即dummy.next连接到旧头结点的下一个结点后面,第一次执行则是将null赋给结点                
        1的下一个结点
        cur.next = dummy.next;
        // 令dummy指向新插入的结点,成为新的头结点
        dummy.next = cur;
        // cur指向下一个要插入的结点
        cur = next;
    }
    // 返回新的头结点
    return dummy.next;
}

方法二 直接操作链表实现反转

建立虚拟节点的方法虽然好理解应用也广,但可能会被面试官禁止。原因是不借助虚拟结点的方式更难,更能考察面试者的能力。下面我们来学习一种通过直接操作链表实现反转的方法,先看一下下面这幅流程图,我们边看边来探讨这个方法。

算法通关村第二关 —— 终于学会链表反转_第2张图片

这个方法我们建立了三个重要节点,一个是pre,表示调整后新链表的表头。第二个是cur,用来指向旧链表的头结点,也就是即将要插入到新链表表头成为新的pre结点。最后一个结点是next,指向下一个要调整的结点。整个流程其实很好理解,便是每次把链表的表头拆下来,建立一个新的链表,而新链表的头结点就是我们当前插入的结点,直到全部都插入后,链表便实现了反转。

具体实现代码如下:

public static ListNode reverseListSimple(ListNode head) {
    // 建立调整后链表的头结点和旧链表的头结点
    ListNode prev = null;
    ListNode curr = head;
    while (curr != null) {
        // next指向下一个要调整的结点,即旧链表头结点的下一个结点
        ListNode next = curr.next;
        // 令即将插入的结点指向新链表的头结点,成为新的头结点prev
        curr.next = prev;
        prev = curr;
        // 调整后即将调整的结点则变为刚插入结点的下一个结点
        curr = next;
    }
    // 返回新链表的头结点
    return prev;
}

方法三 递归实现

递归的实现方法理解起来可能不如前两个直观,但是了解了也可以让我们更好的了解链表的数据结构,所以下面我们也来学习一下吧!由于是递归方法,所以我们先上代码,然后再来解释原理。

public static ListNode reverseListByRecurse(ListNode head) {
    if (head == null || head.next == null) {
        // 如果head为空或尾结点,则将返回head,因为反转过后尾结点就是新的头结点
        return head;
    }
    // 递归步骤,我们下面再细讲
    ListNode newHead = reverseListByRecurse(head.next);
    // 交换head结点和head.next结点的位置,此时head变成新的尾结点,指向null。
    head.next.next = head;
    head.next = null;
    // 返回新的头结点
    return newHead;
}

我们以链表{1 -> 2 -> 3 -> 4}为例子,那么一开始运行时,head为1,然后通过递归进到最里面一层,则head为4,此时运行最后一层递归,由于head.next == null,所以直接返回该结点,成为新链表的头结点。

接下来回到上一层递归,此时head为3,那么令head.next.next = head, 即为让3的下一个结点指向3,然后让head.next=null, 此时交换了4和3的位置,4为新链表的头结点,3为新链表的尾结点。后面也是一样的道理,再回到上一层递归后head就变成2了,此时即为把2拼到3后面,这样执行下去最终就把链表实现了反转,然后返回新链表的头结点。

算法通关村第二关 —— 终于学会链表反转_第3张图片

总结

链表反转真的非常重要,因为他考察了我们对链表结构的了解程度,以及对链表操作方法的熟练程度,所以是我们一定要学会的题型之一,相信通过我这篇文章,大家都可以很清晰地学会链表反转了。

你可能感兴趣的:(数据结构,链表)