演算法知識梳理(9) - 連結串列演算法第一部分

澤毛發表於2017-12-21

一、概要

本文介紹了有關連結串列的演算法的Java程式碼實現,所有程式碼均可通過 線上編譯器 直接執行,演算法目錄:

  • 新建連結串列
  • 反轉連結串列(遞迴和非遞迴實現)
  • 獲得連結串列倒數第k個結點
  • 獲得連結串列的中間結點
  • 刪除連結串列結點
  • 交換連結串列結點

在本章的討論當中,所有的連結串列都包含一個頭結點Node,頭結點不儲存資料,其next指標指向第一個普通連結串列結點,每個普通連結串列結點包含一個int型別的資料項。

演算法知識梳理(9) - 連結串列演算法第一部分

二、程式碼實現

2.1 新建連結串列

問題描述

輸入一個int型別的陣列,通過該陣列建立一個連結串列,並列印出該連結串列的所有元素。

解決思路

首先我們建立一個首結點header,之後通過遍歷陣列p的方式獲得陣列中元素並建立對應的結點node,並進行兩步操作:

  • 將首結點的當前後繼結點,作為新結點的新後繼結點
  • 將新結點作為首結點的新後繼結點

因此,最終構建出來的連結串列中結點順序是和原陣列相反的。

程式碼實現

class Untitled {
	
	static class Node {
		public Node next;
		public int value;
	}
	
	static Node createList(int p[], int len) {
		Node header = new Node();
		Node curNode = null;
		for (int i=0; i<len; i++) {
			curNode = new Node();
			curNode.value = p[i];
			//將舊的第一個普通連結串列結點作為新結點的next。
			curNode.next = header.next;
			//將新結點作為第一個普通連結串列結點。
			header.next = curNode;
		}
		return header;
	}
	
	static void printList(Node header) {
		if (header != null) {
			Node node = header.next;
			while (node != null) {
				System.out.println("value=" + node.value);
				node = node.next;
			}
		}
	}
	
	public static void main(String[] args) {
		int p[] = {1,2,3,4,5};
		Node header = createList(p, p.length);
		printList(header);
	}
}
複製程式碼

執行結果

>> value=5
>> value=4
>> value=3
>> value=2
>> value=1
複製程式碼

2.2 反轉連結串列

問題描述

將輸入的連結串列進行反轉,例如在2.1中建立的連結串列為header->5->4->3->2->1,那麼反轉後的連結串列為header->1->2->3->4->5

解決思路

這裡我們介紹兩種方式:非遞迴實現和遞迴實現。

實現程式碼

class Untitled {
	
	static class Node {
		public Node next;
		public int value;
	}
	
	static Node createList(int p[], int len) {
		Node header = new Node();
		Node curNode = null;
		for (int i=0; i<len; i++) {
			curNode = new Node();
			curNode.value = p[i];
			//將舊的第一個普通連結串列結點作為新結點的next。
			curNode.next = header.next;
			//將新結點作為第一個普通連結串列結點。
			header.next = curNode;
		}
		return header;
	}
	
	//(1)非遞迴實現
	static void reverseList(Node header) {
		if (header == null) {
			return;
		}
		//curNode表示待反轉的結點。
		Node curNode = header.next;
		//nextNode表示待反轉結點的下一個結點。
		Node nextNode = null;
		//curHeader表示已經完成反轉的連結串列部分的第一個普通結點。
		Node curHeader = null;
		while (curNode != null) {
			nextNode = curNode.next;
			curNode.next = curHeader;
			curHeader = curNode;
			curNode = nextNode;
		}
		header.next = curHeader;
	}
	
	//(2)遞迴實現
	static void reverseListDi(Node header) {
		if (header == null) {
			return;
		}
		reverseListDiCore(header.next, header);
	}
	
	static Node reverseListDiCore(Node header, Node listHeader) {
		if (header.next == null) {
			listHeader.next = header;
			return header;
		}
		//下一個結點。
		Node nextNode = header.next;
		//對下一個結點進行反轉。
		Node reverseNode = reverseListDiCore(nextNode, listHeader);
		//重新確立當前結點和下一個結點的關係。
		reverseNode.next = header;
		header.next = null;
		return header;
	}
		
	static void printList(Node header) {
		if (header != null) {
			Node node = header.next;
			while (node != null) {
				System.out.println("value=" + node.value);
				node = node.next;
			}
		}
	}
	
	public static void main(String[] args) {
		int p[] = {1,2,3,4,5};
		Node header = createList(p, p.length);
		reverseListDi(header);
		printList(header);
	}
}
複製程式碼

執行結果

>> value=1
>> value=2
>> value=3
>> value=4
>> value=5
複製程式碼

2.3 獲得連結串列的倒數第 k 個結點

問題描述

輸入一個連結串列,返回該連結串列的匯入第k個結點(不包括首結點,最後一個結點為倒數第1個結點),如果連結串列的長度小於k,那麼返回null

解決思路

採用 快慢指標 的思想,讓fast先走k步,然後slow指標開始和fast指標一起走,當fast位於最後一個結點時,那麼slow所在的位置就是倒數第k個結點。

程式碼實現

class Untitled {
    
    static class Node {
        public Node next;
        public int value;
    }
    
    static Node createList(int p[], int len) {
        Node header = new Node();
        Node curNode = null;
        for (int i=0; i<len; i++) {
            curNode = new Node();
            curNode.value = p[i];
            //將舊的第一個普通連結串列結點作為新結點的next。
            curNode.next = header.next;
            //將新結點作為第一個普通連結串列結點。
            header.next = curNode;
        }
        return header;
    }
    
	static Node getLastKNode(Node header, int k) {
		if (k < 1) {
			return null;
		}
		Node fast = header;
		Node slow = header;
		int step = 0;
		while (fast != null && fast.next != null) {
			fast = fast.next;
			step++;
			if (step >= k) {
				slow = slow.next;
			}
		}
		return slow != header ? slow : null;
	}
        
    static void printList(Node header) {
        if (header != null) {
            Node node = header.next;
			StringBuilder builder = new StringBuilder();
            while (node != null) {
				builder.append(String.valueOf(node.value));
                node = node.next;
				if (node != null) {
					builder.append("->");
				}
            }
			System.out.println(builder.toString());
        }
    }
    
    public static void main(String[] args) {
        int p[] = {1,2,3,4,5};
        Node header = createList(p, p.length);
		printList(header);
        Node kNode = getLastKNode(header, 4);
		System.out.println("KNode=" + (kNode != null ? kNode.value : ""));
    }
}
複製程式碼

執行結果

>> 5->4->3->2->1
>> KNode=4
複製程式碼

2.4 獲得連結串列的中間結點

問題描述

輸入一個連結串列,獲得連結串列的中間結點:

  • 如果連結串列的長度為1,那麼返回唯一的一個結點
  • 如果連結串列的長度為偶數,那麼返回結點為其第len/2個結點,其中len為連結串列的長度
  • 如果連結串列的長度為奇數,那麼len/2的值為x.5,取第x.5+0.5個結點作為返回結點

解決思路

2.3類似,採用 快慢指標 的方式,fast每次走兩步,而slow每次走一步,當fast遍歷到尾結點時,slow所處的位置就是中間結點。

實現程式碼

class Untitled {
    
    static class Node {
        public Node next;
        public int value;
    }
    
    static Node createList(int p[], int len) {
        Node header = new Node();
        Node curNode = null;
        for (int i=0; i<len; i++) {
            curNode = new Node();
            curNode.value = p[i];
            //將舊的第一個普通連結串列結點作為新結點的next。
            curNode.next = header.next;
            //將新結點作為第一個普通連結串列結點。
            header.next = curNode;
        }
        return header;
    }
    
	static Node geMiddleNode(Node header) {
		if (header == null || header.next == null) {
			return null;
		}
		Node fast = header;
		Node slow = header;
		while (fast != null) {
			fast = fast.next;
			if (fast != null) {
				fast = fast.next;
			} else {
				break;
			}
			slow = slow.next;
		}
		return slow;
	}
        
    static void printList(Node header) {
        if (header != null) {
            Node node = header.next;
			StringBuilder builder = new StringBuilder();
            while (node != null) {
				builder.append(String.valueOf(node.value));
                node = node.next;
				if (node != null) {
					builder.append("->");
				}
            }
			System.out.println(builder.toString());
        }
    }
    
    public static void main(String[] args) {
        int p[] = {1,2,3};
        Node header = createList(p, p.length);
		printList(header);
        Node kNode = geMiddleNode(header);
		System.out.println("KNode=" + (kNode != null ? kNode.value : ""));
    }
}
複製程式碼

2.5 刪除連結串列結點

問題描述

輸入一個連結串列的頭結點header,並給出位於該連結串列中的一個結點dNode,要求從連結串列中刪除該結點。

解決思路

這個問題最容易想到的做法就是找到待刪除結點的前驅結點和後繼結點,讓前驅結點的next指向後繼結點來實現刪除,但是對於 待刪除結點不是尾結點 的情況,我們可以採用一個小技巧:取出待刪除結點的後繼結點,再刪除該後繼結點,這樣就避免了尋找前驅結點的過程。

實現程式碼

class Untitled {

	static class Node {
		public Node next;
		public int value;
	}

	static Node createList(int p[], int len) {
		Node header = new Node();
		Node curNode = null;
		for (int i=0; i<len; i++) {
			curNode = new Node();
			curNode.value = p[i];
			//將舊的第一個普通連結串列結點作為新結點的next。
			curNode.next = header.next;
			//將新結點作為第一個普通連結串列結點。
			header.next = curNode;
		}
		return header;
	}

	static Node getLastKNode(Node header, int k) {
		if (k < 1) {
			return null;
		}
		Node fast = header;
		Node slow = header;
		int step = 0;
		while (fast != null && fast.next != null) {
			fast = fast.next;
			step++;
			if (step >= k) {
				slow = slow.next;
			}
		}
		return slow != header ? slow : null;
	}

	static void deleteNode(Node header, Node dNode) {
		if (header == null && dNode != null) {
			return;
		}
		if (dNode.next != null) { 
			//如果不是尾結點,那麼取其後繼結點的值替換待刪除結點。
			Node rNode = dNode.next;
			dNode.value = rNode.value;
			dNode.next = rNode.next;
		} else {
			//如果是尾結點,那麼只能採用遍歷的方式。
			Node node = header;
			while (node.next != null && node.next.next != null) {
				node = node.next;
			}
			node.next = null;
		}
	}

	static void printList(Node header) {
		if (header != null) {
			Node node = header.next;
			StringBuilder builder = new StringBuilder();
			while (node != null) {
				builder.append(String.valueOf(node.value));
				node = node.next;
				if (node != null) {
					builder.append("->");
				}
			}
			System.out.println(builder.toString());
		}
	}

	public static void main(String[] args) {
		int p[] = {1,2,3};
		Node header = createList(p, p.length);
		printList(header);
		Node kNode = getLastKNode(header, 3);
		System.out.println("KNode=" + (kNode != null ? kNode.value : ""));
		deleteNode(header, kNode);
		printList(header);
	}
}
複製程式碼

執行結果

>> 3->2->1
>> KNode=2
>> 3->1
複製程式碼

2.6 交換連結串列結點

問題描述

給定一個單連結串列的頭結點header,和兩個待交換的連結串列結點nodeAnodeB,交換這兩個連結串列結點

解決思路

互動連結串列結點的關鍵,在於找到這兩個結點的前驅和後繼結點,修改它們和對應結點的引用關係,這裡需要注意的是 交換結點相鄰 的情況。

程式碼實現

class Untitled {
    
    static class Node {
        public Node next;
        public int value;
    }
    
    static Node createList(int p[], int len) {
        Node header = new Node();
        Node curNode = null;
        for (int i=0; i<len; i++) {
            curNode = new Node();
            curNode.value = p[i];
            //將舊的第一個普通連結串列結點作為新結點的next。
            curNode.next = header.next;
            //將新結點作為第一個普通連結串列結點。
            header.next = curNode;
        }
        return header;
    }
    
    static Node getLastKNode(Node header, int k) {
        if (k < 1) {
            return null;
        }
        Node fast = header;
        Node slow = header;
        int step = 0;
        while (fast != null && fast.next != null) {
            fast = fast.next;
            step++;
            if (step >= k) {
                slow = slow.next;
            }
        }
        return slow != header ? slow : null;
    }
	
	static void swapNode(Node header, Node nodeA, Node nodeB) {
		if (header == null || nodeA == null || nodeB == null) {
			return;
		}
		if (nodeA == header || nodeB == header) {
			return;
		}
		if (nodeA == nodeB) {
			return;
		}
		//找到nodeA的前驅結點
		Node preA = header;
		while (preA.next != nodeA) {
			preA = preA.next;
		}
		//找到nodeB的前驅結點
		Node preB = header;
		while (preB.next != nodeB) {
			preB = preB.next;
		}
		//nodeA和nodeB的後繼結點
		Node postA = nodeA.next;
		Node postB = nodeB.next;
		//nodeA是nodeB的後繼結點
		if (preB == nodeA) {
			nodeA.next = postB;
			nodeB.next = nodeA;
			preA.next = nodeB;
		//nodeB是nodeA的後繼結點	
		} else if (preA == nodeB) {
			nodeB.next = postA;
			nodeA.next = nodeB;
			preB.next = nodeA;
		//nodeA和nodeB不相鄰
		} else {
			preA.next = nodeB;
			nodeB.next = postA;
			preB.next = nodeA;
			nodeA.next = postB;
		}
	
	}
        
    static void printList(Node header) {
        if (header != null) {
            Node node = header.next;
            StringBuilder builder = new StringBuilder();
            while (node != null) {
                builder.append(String.valueOf(node.value));
                node = node.next;
                if (node != null) {
                    builder.append("->");
                }
            }
            System.out.println(builder.toString());
        }
    }
    
    public static void main(String[] args) {
        int p[] = {1,2,3,4,5};
        Node header = createList(p, p.length);
        printList(header);
        Node nodeA = getLastKNode(header, 5);
		Node nodeB = getLastKNode(header, 1);
		swapNode(header, nodeA, nodeB);
		printList(header);
    }
}
複製程式碼

執行結果

>> 5->4->3->2->1
>> 1->4->3->2->5
複製程式碼

相關文章