單鏈表是一種常見、重要的數據結構,而且隨着時間飛逝,也衍生出了諸多針對單鏈表的操做算法,例如,今天本文中即將會聊到的 單鏈表的反轉操做 。下面會結合一些圖片詳細講解下單鏈表的數據結構,以及經過三種方式(遞歸、雙指針法、循環遍歷)進行單鏈表的反轉。java
單鏈表是一種線性結構,它是由一個個 節點(Node)組成的。而且每一個節點(Node)是由一塊 數據域(data)和一塊 指針域(next)組成的。
①、節點(Node)結構圖以下:node
- 節點的數據域:data數據域通常就是用來存放數據的 。(注:data域須要指定類型,只能存放指定類型的數據,不能什麼東西都放,是否是呀; 那代碼中是怎麼實現的呢? 使用 泛型 。)
- 節點的指針域:next指針域通常就是存放的指向下一個節點的指針;這個指針實際上是一個內存地址,由於Node節點對象是存放在JVM中的堆內存中,因此節點的next指針域中存放就是下一個節點在堆內存中的地址;而在代碼中對象的內存地址是賦值給其引用變量了,因此指針域中存放的是下一個節點對象的引用變量。
②、單鏈表結構圖以下:(下圖是由三個節點構成的單鏈表)算法
如有所思,en en en . . . . . . 好像單鏈表的知識在腦海中清晰了些呀;那要不咱們馬不停蹄,趕忙把單鏈表的數據結構代碼弄出來,而後再思索下怎麼進行反轉, en en en. . . .. . . 嘿嘿!
建立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,終於寫完,本文中着重講解了下 遞歸 的調用過程,由於遞歸通常是不太好理解的 。
一切看文章不點贊都是「耍流氓」,嘿嘿ヾ(◍°∇°◍)ノ゙!開個玩笑,動一動你的小手,點贊就完事了,你每一個人出一份力量(點贊 + 評論)就會讓更多的學習者加入進來!很是感謝! ̄ω ̄=