連結串列:單向連結串列
阿新 • • 發佈:2021-08-09
簡介
單向連結串列是連結串列的一種,它由多個結點組成,每個結點都由一個數據域和一個指標域組成,資料域用來儲存資料, 指標域用來指向其後繼結點。連結串列的頭結點的資料域不儲存資料,指標域指向第一個真正儲存資料的結點。
程式碼實現:
public class SinglyLinkedList<T> implements Iterable<T>{ //記錄頭結點 private Node<T> head; //記錄連結串列的長度 private int N; //結點類 private static class Node<T> { //儲存資料 T item; //下一個結點 Node<T> next; public Node(T item, Node<T> next) { this.item = item; this.next = next; } } public SinglyLinkedList() { //初始化頭結點、 this.head = new Node<>(null, null); //初始化元素個數 this.N=0; } //清空連結串列 public void clear() { head.next=null; this.N=0; } //獲取連結串列的長度 public int length() { return N; } //判斷連結串列是否為空 public boolean isEmpty() { return N==0; } //獲取指定位置i出的元素 public T get(int i) { //通過迴圈,從頭結點開始往後找,依次找i次,就可以找到對應的元素 Node<T> n = head.next; for(int index=0;index<i;index++){ n=n.next; } return n.item; } //向連結串列中新增元素t public void add(T t) { //找到當前最後一個結點 Node<T> n = head; while(n.next!=null){ n=n.next; } //建立新結點,儲存元素t //讓當前最後一個結點指向新結點 n.next= new Node<>(t, null); //元素的個數+1 N++; } //向指定位置i出,新增元素t public void add(int i, T t) { //找到i位置前一個結點 Node<T> pre = head; for(int index=0;index<=i-1;index++){ pre=pre.next; } //找到i位置的結點 Node<T> curr = pre.next; //建立新結點,並且新結點需要指向原來i位置的結點 //原來i位置的前一個節點指向新結點即可 pre.next= new Node<>(t, curr); //元素的個數+1 N++; } //刪除指定位置i處的元素,並返回被刪除的元素 public T remove(int i) { //找到i位置的前一個節點 Node<T> pre = head; for(int index=0;index<=i-1;i++){ pre=pre.next; } //要找到i位置的結點 Node<T> curr = pre.next; //找到i位置的下一個結點 Node<T> nextNode = curr.next; //前一個結點指向下一個結點 pre.next=nextNode; //元素個數-1 N--; return curr.item; } //查詢元素t在連結串列中第一次出現的位置 public int indexOf(T t) { //從頭結點開始,依次找到每一個結點,取出item,和t比較,如果相同,就找到了 Node<T> n = head; for(int i=0;n.next!=null;i++){ n=n.next; if (n.item.equals(t)){ return i; } } return -1; } @Override public Iterator<T> iterator() { return new LIterator(); } private class LIterator implements Iterator<T>{ private Node<T> n; public LIterator(){ this.n=head; } @Override public boolean hasNext() { return n.next!=null; } @Override public T next() { n = n.next; return n.item; } } }
單鏈表反轉
使用遞迴可以完成反轉,遞迴反轉其實就是從原連結串列的第一個存資料的結點開始,依次遞迴呼叫反轉每一個結點, 直到把最後一個結點反轉完畢,整個連結串列就反轉完畢。
程式碼實現:
//用來反轉整個連結串列 public void reverse(){ //判斷當前連結串列是否為空連結串列,如果是空連結串列,則結束執行,如果不是,則呼叫過載的reverse方法完成反轉 if (isEmpty()){ return; } reverse(head.next); } private Node<T> reverse(Node<T> curr){ if(curr.next == null){ head.next = curr; return curr; } Node<T> pre = reverse(curr.next); pre.next = curr; curr.next = null; return curr; }
快慢指標
快慢指標指的是定義兩個指標,這兩個指標的移動速度一塊一慢,以此來製造出自己想要的差值,這個差值可以讓我們找到連結串列上相應的結點。一般情況下,快指標的移動步長為慢指標的兩倍。
中間值問題
下面有一個需求:找出連結串列的中間值
public class FastSlowTest { public static void main(String[] args) throws Exception { //建立結點 Node<String> first = new Node<>("aa", null); Node<String> second = new Node<>("bb", null); Node<String> third = new Node<>("cc", null); Node<String> fourth = new Node<>("dd", null); Node<String> fifth = new Node<>("ee", null); Node<String> six = new Node<>("ff", null); Node<String> seven = new Node<>("gg", null); //完成結點之間的指向 first.next = second; second.next = third; third.next = fourth; fourth.next = fifth; fifth.next = six; six.next = seven; //查詢中間值 String mid = getMid(first); System.out.println("中間值為:"+mid); } /** * @param first 連結串列的首結點 * @return 連結串列的中間結點的值 */ public static String getMid(Node<String> first) { return null; } //結點類 private static class Node<T> { //儲存資料 T item; //下一個結點 Node<T> next; public Node(T item, Node<T> next) { this.item = item; this.next = next; } } }
使用快慢指標即可解決:最開始,slow與fast指標都指向連結串列第一個節點,然後slow每次移動一個指標,fast每次移動兩個指標。
程式碼實現如下:
/**
* @param first 連結串列的首結點
* @return 連結串列的中間結點的值
*/
public static String getMid(Node<String> first) {
return getMid(first, first);
}
private static String getMid(Node<String> slowNode, Node<String> fastNode) {
if(fastNode.next == null) return slowNode.item;
fastNode = fastNode.next.next;
slowNode = slowNode.next;
return getMid(slowNode, fastNode);
}
單向連結串列是否有環
下面有一個需求:判斷連結串列中是否有環
使用快慢指標,如果快慢指標最後有快指標節點等於慢指標(相遇),說明有環
public class CircleListCheckTest {
public static void main(String[] args) throws Exception {
//建立結點
Node<String> first = new Node<String>("aa", null);
Node<String> second = new Node<String>("bb", null);
Node<String> third = new Node<String>("cc", null);
Node<String> fourth = new Node<String>("dd", null);
Node<String> fifth = new Node<String>("ee", null);
Node<String> six = new Node<String>("ff", null);
Node<String> seven = new Node<String>("gg", null);
//完成結點之間的指向
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
//產生環
seven.next = third;
//判斷連結串列是否有環
boolean circle = isCircle(first);
System.out.println("first連結串列中是否有環:"+circle);
}
/**
* 判斷連結串列中是否有環
* @param first 連結串列首結點
* @return ture為有環,false為無環
*/
public static boolean isCircle(Node<String> first) {
return false;
}
//結點類
private static class Node<T> {
//儲存資料
T item;
//下一個結點
Node<T> next;
public Node(T item, Node<T> next) {
this.item = item;
this.next = next;
}
}
}
程式碼實現:
/**
* 判斷連結串列中是否有環
* @param first 連結串列首結點
* @return ture為有環,false為無環
*/
public static boolean isCircle(Node<String> first) {
return isCircle(first, first);
}
private static boolean isCircle(Node<String> slowNode, Node<String> fastNode) {
if(fastNode.next == null) return false;
slowNode = slowNode.next;
fastNode = fastNode.next.next;
if(slowNode.item.equals(fastNode.item)) return true;
return isCircle(slowNode, fastNode);
}
非遞迴實現:
/**
* 判斷連結串列中是否有環
* @param first 連結串列首結點
* @return ture為有環,false為無環
*/
public static boolean isCircle(Node<String> first) {
//定義快慢指標
Node<String> fast = first;
Node<String> slow = first;
//遍歷連結串列,如果快慢指標指向了同一個結點,那麼證明有環
while(fast!=null && fast.next!=null){
//變換fast和slow
fast = fast.next.next;
slow = slow.next;
if (fast.equals(slow)){
return true;
}
}
return false;
}
單向連結串列環入口問題
現有一個需求:需要找打單向連結串列中環的入口
public class CircleListInTest {
public static void main(String[] args) throws Exception {
Node<String> first = new Node<String>("aa", null);
Node<String> second = new Node<String>("bb", null);
Node<String> third = new Node<String>("cc", null);
Node<String> fourth = new Node<String>("dd", null);
Node<String> fifth = new Node<String>("ee", null);
Node<String> six = new Node<String>("ff", null);
Node<String> seven = new Node<String>("gg", null);
//完成結點之間的指向
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
//產生環
seven.next = third;
//查詢環的入口結點
Node<String> entrance = getEntrance(first);
System.out.println("first連結串列中環的入口結點元素為:"+entrance.item);
}
/**
* 查詢有環連結串列中環的入口結點
* @param first 連結串列首結點
* @return 環的入口結點
*/
public static Node<String> getEntrance(Node<String> first) {
return null;
}
//結點類
private static class Node<T> {
//儲存資料
T item;
//下一個結點
Node<T> next;
public Node(T item, Node<T> next) {
this.item = item;
this.next = next;
}
}
}
思路:
當快慢指標相遇時,我們可以判斷到連結串列中有環,這時重新設定一個新指標指向連結串列的起點,且步長與慢指標一樣 為1,則慢指標與“新”指標相遇的地方就是環的入口。證明這一結論牽涉到數論的知識,這裡略,只講實現
程式碼實現如下:
public static Node<String> getEntrance(Node<String> first) {
//定義快慢指標
Node<String> fast = first;
Node<String> slow = first;
Node<String> temp = null;
//遍歷連結串列,如果快慢指標指向了同一個結點,那麼證明有環
while(fast!=null && fast.next!=null){
//變換fast和slow
fast = fast.next.next;
slow = slow.next;
if (fast.equals(slow)){
temp = first;
continue;
}
if(temp != null){
temp = temp.next;
if(temp.item.equals(slow.item))
return temp;
}
}
return null;
}