分享一個關於java算法的問題:怎么合並兩個有序鏈表
這里提供兩種解決方法:1.遞歸實現 ; 2.非遞歸實現
任何一種方式,都要先創建節點類,沒有什么重點,直接寫代碼:

package com.dataClass; /** * @author 新生代菜鳥 */ public class Node { // 數據存儲變量 public int data; // 節點信息存放(指針信息) public Node next; // 構造函數,用來給data傳值 ---這里先不考慮批量插入的問題 public Node(int data) { this.data = data; } }
遞歸實現:
傳入的兩個鏈表是list1和list2,考慮它們是否為空會有三種情況:
- 兩個都是null , 怎么合並都會是一個空鏈表,直接返回null即可
- 兩個中有一個是null , 找到非空的那一個直接返回即可
- 兩個都不是null,此時就是重點了
我們分析第三種情況:
假如:list1為: 1 --> 3 --> 5 -- null ; list2為:2 --> 4 --> 6 --> 8 -- > null;
我們指定head用來存放data小的那個對象,剛開始時list與list實際是指向了兩個鏈表的表頭,即list1.data = 1 , list2.data = 2 ,此時我們發現1 <= 2 ,我們將head指向list1 ,那么新問題就是:head的next指向哪?遞歸算法的魅力就在這,我們不用考慮別的,反正一定是在兩個鏈表剩下的元素中,那么我們將list1.next和list2作為遞歸方法的參數,繼續調用遞歸方法即可 。
我們發現當list1指向null時,list並沒有指向null,就是說本例中list2中還有元素沒有被合並,那么在實際中可能是list1也可能是list2會有元素沒有合並,但其中一個已經已經是null了,此時只需判斷哪個非null,將其合並在后面即可。下面看代碼:

public Node mergeByRecursion(Node list1, Node list2) { //將1,2合並 if (list1== null || list2 == null) { return list1 == null ? list2 : list1; } Node head = null; if (list1.data <=list2.data) { head = list1; head.next = mergeByRecursion(list1.next, list2); } else { head = list2; head.next = mergeByRecursion(list1, list2.next); } return head; }
非遞歸實現:
遞歸算法實現簡單,代碼也容易理解,但是它的弊端也很明顯時間空間開銷都很大,效率低,現在考慮一種比較容易理解的非遞歸算法:
傳入的兩個鏈表是node1和node2 , 開頭和遞歸算法一樣,也是那三種情況,前兩種情況處理也一樣,關鍵在第三種情況,此時我們這么考慮,node1是一個在node1鏈表上的指針,node2是node2鏈表上的一個指針,我們有一個新的指針head,在兩個指針所指都不是null時,我們讓head指向data更小的那個,同樣:
假如list1為: 1 --> 3 --> 5 -- null ; list2為:2 --> 4 --> 6 --> 8 -- > null;
剛開始1 < 2 , 讓head = list1 , 然后list1 = list.next, 使用另一個指針Node point = head 來記注這個合並后鏈表的表頭,
現在再看那么list1.data = 3 , list2.data = 2 , 2 < 3 ,那么head.next = list2 , list2 = list2.next , 在讓head指針也向着后移動,即head = head.next ,
...
其實就是使用四個指針,一個記錄node1上的當前位置,一個記錄node2上的當前位置,一個記錄合並鏈表的表頭,另外一個不斷的指向當前位置下data更小的那一個。看一下代碼:

/** * 非遞歸算法---簡化 * * @param node1測試鏈表1 * @param node2測試鏈表2 * @return 返回合並后的鏈表 */ public Node mergeNoRecursion2(Node node1, Node node2) { // 合並1,2 if (node1 == null || node2 == null) { return node1 == null ? node2 : node1; } // head記錄合並后鏈表的表頭(理解有邊的代碼) Node head = node1.data <= node2.data ? node1 : node2; // list1,list2用來記錄兩鏈表當前位置 Node list1 = head == node1 ? node1 : node2; Node list2 = head == node1 ? node2 : node1; // point用來指向合並鏈表的下一個節點 Node point = head; list1 = list1.next;// 開始時已經指向了list1的表頭,后面的比較list1要從第二個開始 // 使用循環遍歷兩個鏈表,根據list1.data和list2.data的比較結果,決定point下一節點 while (list1 != null && list2 != null) { if (list1.data <= list2.data) { // list1.data <= list2.data 首先更新point的下一節點list1,然后更新list1的位置 point.next = list1; list1 = list1.next; } else { // list1.data > list2.data 首先更新point的下一節點list2,然后更新list2的位置 point.next = list2; list2 = list2.next; } // 更新point的位置為它的下一節點 point = point.next; } /* * 至少有一個為空了,list1是否已經便利完 如果list1遍歷完,則將point.next直接指向list2 * 否則及list2遍歷完,那么point.next直接指向list1 **/ point.next = list1 == null ? list2 : list1; return head; } }
兩種實現已經寫完了,下面就是比較期待又緊張的測試階段了,天靈靈,地靈靈,保佑測試一定過...

public class Test { //輸出函數 public static void syso(Node head) { while (head != null) { System.out.print(head.data + "-->"); head = head.next; } System.out.println("null"); } public static void main(String[] args) { MyNodeList list = new MyNodeList(); //測試遞歸算法 // 測試鏈表1.1 Node node1 = new Node(1); Node node3 = new Node(3); node1.next = node3; Node node5 = new Node(5); node3.next = node5; // 測試鏈表1.2 Node node2 = new Node(2); Node node4 = new Node(4); node2.next = node4; Node node6 = new Node(6); node4.next = node6; // 遞歸方法測試 Node test1 = list.mergeByRecursion(node1, node2); System.out.print("遞歸合並結果:"); syso(test1); //測試非遞歸算法 // 測試鏈表2.1 Node node21 = new Node(1); Node node23 = new Node(3); node21.next = node23; Node node25 = new Node(5); node23.next = node25; // 測試鏈表2.2 Node node22 = new Node(2); Node node24 = new Node(4); node22.next = node24; Node node26 = new Node(6); node24.next = node26; Node node28 = new Node(8); node26.next = node28; Node test2 = list.mergeNoRecursion2(node21, node22); System.out.print("非遞歸合並結果:"); syso(test2); } }
測試結果為:
![]() |