在技術面試中,單鏈表的操作經常會被問到,比如一些常見的問題:
- 單鏈表反轉
- 鏈表中環的檢測
- 兩個有序表的合併
- 刪除鏈表倒數第n個節點
- 求鏈表的中間節點
接下來的文章,我對這些操作的實現算法做了一些總結,具體實現的編程語言是Java。
今天第一篇,先講講如何實現單鏈表反轉。
實現思路
第一步,從頭結點開始遍歷鏈表,找到尾結點。
第二步,從尾結點開始,反向修改每個節點的next引用,直到頭結點。
第三步,修改頭結點的next引用爲null。
實現代碼
定義節點結構。
/**
* 鏈表ADT
*
* @author wangtao
* @param <T>
*/
public class LinkADT<T> {
/**
* 單鏈表節點
*
* @author wangtao
* @param <T>
*/
private static class SingleNode<T> {
public SingleNode<T> next;
public T data;
public SingleNode(T data) {
this.data = data;
}
public T getNextNodeData() {
return next != null ? next.data : null;
}
}
}
第一種實現方案,使用遞歸思路,易懂但是不夠簡潔。
/**
* 單鏈表反轉 version1
*
* @param node
* 當前節點
* @param pre
* 前一個節點
* @return
*/
public static SingleNode reverseV1(SingleNode node, SingleNode pre) {
if (node == null) {
return node;
} else {
// 反轉後的頭結點
SingleNode headNode;
// next爲空,說明是尾節點
if (node.next == null) {
// 修改next引用
node.next = pre;
// 指定反轉後的頭節點
headNode = node;
} else {
// 非尾節點,繼續遞歸
headNode = reverseV1(node.next, node);
//
node.next = pre;
}
return headNode;
}
}
驗證結果。
/**
* 打印鏈表信息
*
* @param node
*/
public static void printLink(SingleNode node) {
System.out.println("current node data:" + node.data + ", next node data:" + node.getNextNodeData());
if (node.next != null) {
printLink(node.next);
} else {
System.out.println("-------------");
}
}
public static void main(String[] args) {
SingleNode<Integer> s1 = new SingleNode<>(1);
SingleNode<Integer> s2 = new SingleNode<>(2);
SingleNode<Integer> s3 = new SingleNode<>(3);
SingleNode<Integer> s4 = new SingleNode<>(4);
s1.next = s2;
s2.next = s3;
s3.next = s4;
printLink(s1);
SingleNode<Integer> firstNode = reverseV1(s1, null);
printLink(firstNode);
}
打印結果,鏈表被正確反轉。
current node data:1, next node data:2
current node data:2, next node data:3
current node data:3, next node data:4
current node data:4, next node data:null
-------------
current node data:4, next node data:3
current node data:3, next node data:2
current node data:2, next node data:1
current node data:1, next node data:null
-------------
爲什麼說前面的方案不夠簡潔呢?因爲參數中有當前節點的前一個節點,例如下面的代碼
SingleNode<Integer> firstNode = reverseV1(s1, null);
下面來看看更加簡潔的第二種實現方案。
/**
* 單鏈表反轉 version2
*
* @param node
* @return
*/
public static SingleNode reverseV2(SingleNode node) {
if (node == null || node.next == null) {
return node;
} else {
SingleNode headNode = reverseV2(node.next);
node.next.next = node;
node.next = null;
return headNode;
}
}
重點是下面這行代碼,將下一個節點的next引用修改爲當前節點。
乍一看以爲有bug,仔細分析之後發現沒問題,如果想不明白,建議跟一次斷點。
node.next.next = node;
總結
單鏈表反轉看上去簡單,但是在沒有準備的情況想要寫對也不容易。如果肯花時間,抽一兩個小時來寫一寫代碼,相信大家都能掌握。
完整代碼請參考:
https://github.com/wanf425/Algorithm/blob/master/src/com/wt/adt/LinkADT.java
下一篇文章,我會繼續總結如何實現鏈表中環的檢測。
博文地址
https://www.taowong.com/blog/2018/10/14/data-strutctures-and-algorithm-01.html