單鏈表反轉?面試官你肯定要問這個嗎?

前言:

單鏈表是一種常見、重要的數據結構,而且隨着時間飛逝,也衍生出了諸多針對單鏈表的操做算法,例如,今天本文中即將會聊到的 單鏈表的反轉操做

下面會結合一些圖片詳細講解下單鏈表的數據結構,以及經過三種方式(遞歸、雙指針法、循環遍歷)進行單鏈表的反轉。java

數據結構:

一、單鏈表的數據結構:

單鏈表是一種線性結構,它是由一個個 節點(Node)組成的。而且每一個節點(Node)是由一塊 數據域(data)和一塊 指針域(next)組成的。      

①、節點(Node)結構圖以下:node

  1. 節點的數據域:data數據域通常就是用來存放數據的 。(注:data域須要指定類型,只能存放指定類型的數據,不能什麼東西都放,是否是呀; 那代碼中是怎麼實現的呢? 使用 泛型 。)
  2. 節點的指針域:next指針域通常就是存放的指向下一個節點的指針;這個指針實際上是一個內存地址,由於Node節點對象是存放在JVM中的堆內存中,因此節點的next指針域中存放就是下一個節點在堆內存中的地址;而在代碼中對象的內存地址是賦值給其引用變量了,因此指針域中存放的是下一個節點對象的引用變量

②、單鏈表結構圖以下:(下圖是由三個節點構成的單鏈表算法

如有所思,en en en . . . . . . 好像單鏈表的知識在腦海中清晰了些呀;那要不咱們馬不停蹄,趕忙把單鏈表的數據結構代碼弄出來,而後再思索下怎麼進行反轉, en en en. . . .. . . 嘿嘿!

代碼:

一、Node節點類:

建立Node節點類,節點類中而且額外提供了兩個方法(單鏈表的建立方法、單鏈表的遍歷放歌);

注意:單鏈表的建立方法 createLinkedList( ):Node節點的插入方式爲 尾插法 , 其實還有 頭插法 方式;數據結構

擴展:鏈表中節點的插入方式還在 HashMap 中使用到了,在 JDK 1.7 時是頭插法,JDK 1.8時是尾插法工具

/**
 * @PACKAGE_NAME: com.lyl.linklist
 * @ClassName: Node  節點類
 * @Description:  單鏈表的組成元素:Node節點
 * @Date: 2020-05-30 16:18
 **/
public class Node<T> {
    // 節點的數據域
    public T data;
    // 節點的指針域
    public Node next;

    /**
     * 構造方法
     * @param data 數據域值
     */
    public Node(T data) {
        this.data = data;
    }
    
    
     /**
     * 建立 單鏈表 (尾插法)
     * @return  返回頭結點
     */
    public static Node createLinkedList(){
        // 頭節點
        Node<String> head;

        Node<String> n = new Node<String>("111");
        Node<String> n1 = new Node<String>("222");
        Node<String> n2 = new Node<String>("333");
        // 指定頭節點
        head = n;
        n.next = n1;
        n1.next = n2;
        // 返回頭結點
        return head;
    }
    
    
    /**
     * 鏈表遍歷
     * @param node
     */
    public static void traverse(Node node) {
        while (node != null) {
            System.out.print(node.data + " --> ");
            node = node.next;
        }
        System.out.print("null");
        System.out.println();
    }
}

二、單鏈表反轉:

①、遞歸實現反轉:
/**
 * @PACKAGE_NAME: com.lyl.linklist
 * @ClassName: ReverseByRecursiveTest
 * @Description: 使用遞歸實現單鏈表反轉
 * @Date: 2020-05-30 17:01
 **/
public class ReverseByRecursiveTest {

    /**
     * 使用 遞歸 實現單鏈表反轉
     * @param head  鏈表的 頭節點
     * @return  返回反轉後的 head 頭結點
     */
    public static Node reverse(Node head) {
        if (head == null || head.next == null) {
            return head;
        }
        // 獲取頭結點的下個節點,使用temp臨時節點存儲
        Node temp = head.next;
        // 遞歸調用
        Node node = reverse(head.next);
        // 將頭節點的下一個節點的指針域指向頭節點
        temp.next = head;
        // 將頭節點的指針域置爲null
        head.next = null;
        return node;
    }

    // test
    public static void main(String[] args) {
        // 建立單鏈表
        Node head = Node.createLinkedList();
        // 遍歷新建立的單鏈表
        System.out.print("新建立的單鏈表: ");
        Node.traverse(head);
        // 遞歸反轉單鏈表
        Node newHead = reverse(head);
        // 遍歷反轉後的單鏈表
        System.out.print("反轉後的單鏈表: ");
        Node.traverse(newHead);
    }
}
運行輸出:

新建立的單鏈表: 111 --> 222 --> 333 --> null
反轉後的單鏈表: 333 --> 222 --> 111 --> null學習

圖解遞歸方法的調用過程:this

(1)、首先將頭結點(data域爲 111 節點)傳入 reverse( ) 方法中,並將方法壓入棧:spa

(2)、當執行到 Node node = reverse(head.next); 將 data域爲 222 的節點傳入 reverse( ) 方法中,並將方法壓入棧:3d

(3)、當執行到 Node node = reverse(head.next); 將 data域爲 333 的節點傳入 reverse( ) 方法中,並將方法壓入棧; 而後當執行到 if 判斷時,發現 data 域爲 333 的節點的 next 指針域指向的下一個節點爲 null,此時方法返回當前head頭結點(data域爲 333 的節點):指針

(4)、當 reverse(333) ; 方法出棧時,此時會繼續執行 reverse(222) ; 繼續執行遞歸調用後面的代碼,而且執行完後, reverse(222) 方法出棧:

(5)、當 reverse(222) ; 方法出棧時,此時會繼續執行 reverse(111) ; 繼續執行遞歸調用後面的代碼,而且執行完後, reverse(111) 方法出棧:

(6)、當reverse(111) 方法出棧了,那麼此時遞歸調用結束, 最終堆中的單鏈表的結構如圖:

遞歸調用終於寫完了,這個圖太費勁了,花費了太多時間了;畫圖所使的工具是: ProcessOn
②、循環遍歷+輔助空間 實現反轉:
/**
 * @PACKAGE_NAME: com.lyl.linklist
 * @ClassName: ReverseByTraverseTest
 * @Description:  使用 循環遍歷+輔助空間 進行單鏈表反轉
 * @Date: 2020-05-30 19:11
 **/
public class ReverseByTraverseTest {

    /**
     * 使用 遍歷+輔助空間 進行鏈表反轉
     * @param head
     * @return 返回反轉後的 head 頭結點
     */
    public static Node reverse(Node head) {
        // list集合 輔助空間
        List<Node> list = new ArrayList<Node>();

        while (head != null) {
            list.add(head);
            head = head.next;
        }

        for (int i = list.size() - 1; i > 0; i--) {
            Node n = list.get(i);
            Node n1 = list.get(i-1);
            n.next = n1;
            n1.next = null;
        }
        // 返回頭結點
        return list.get(list.size() - 1);
    }


    // test
    public static void main(String[] args) {
        // 建立單鏈表
        Node head = Node.createLinkedList();
        // 遍歷新建立的單鏈表
        System.out.print("新建立的單鏈表: ");
        Node.traverse(head);
        // 遞歸反轉單鏈表
        Node newHead = reverse(head);
        // 遍歷反轉後的單鏈表
        System.out.print("反轉後的單鏈表: ");
        Node.traverse(newHead);
    }
}
③、雙指針+輔助臨時節點 實現反轉:
/**
 * @PACKAGE_NAME: com.lyl.linklist
 * @ClassName: ReverseByDoublePointerTest
 * @Description:  使用 雙指針+輔助臨時節點 實現單鏈表反轉
 * @Date: 2020-05-30 19:17
 **/
public class ReverseByDoublePointerTest {

    /**
     * 使用 雙指針+輔助臨時節點 進行鏈表反轉
     * @param head
     * @return 返回反轉後的 head 頭結點
     */
    public static Node reverse(Node head) {
        // 當前節點指針
        Node current ;
        // 前一節點指針
        Node previous;
        // 當前節點指針初始化指向頭結點
        current = head;
        // 前一節點指針初始化爲 null
        previous = null;

        while(current != null){
            // 輔助的臨時節點, 存儲當前節點的下一個節點
            Node temp = current.next;
            // 當前節點的下一個節點指向了前一個節點指針指向的節點
            current.next = previous;
            // 而後 前一節點指針向前移動一個節點,此時和當前節點指針都指向了當前節點
            previous = current;
            // 當前節點指針也向前移動一個節點,也就是移動到了當前節點的下一個節點,就是臨時節點指向的節點
            current = temp;
        }
        // 返回頭結點
        return previous;
    }


    // test
    public static void main(String[] args) {
        // 建立單鏈表
        Node head = Node.createLinkedList();
        // 遍歷新建立的單鏈表
        System.out.print("新建立的單鏈表: ");
        Node.traverse(head);
        // 遞歸反轉單鏈表
        Node newHead = reverse(head);
        // 遍歷反轉後的單鏈表
        System.out.print("反轉後的單鏈表: ");
        Node.traverse(newHead);
    }
}

end,終於寫完,本文中着重講解了下 遞歸 的調用過程,由於遞歸通常是不太好理解的 。

❤不要忘記留下你學習的足跡 [點贊 + 收藏 + 評論]嘿嘿ヾ

一切看文章不點贊都是「耍流氓」,嘿嘿ヾ(◍°∇°◍)ノ゙!開個玩笑,動一動你的小手,點贊就完事了,你每一個人出一份力量(點贊 + 評論)就會讓更多的學習者加入進來!很是感謝! ̄ω ̄=
相關文章
相關標籤/搜索