數據結構第一節就是鏈表。鏈表由多個node節點組成,每個node節點包含數據和一個指針。指針指向下一個節點。
組裝鏈表
經常問單鏈表的算法,那你首先要定下來鏈表的結構,而不是直接思考算法。為了方便使用,我們固定一個哨兵作為
頭節點。數據節點都在頭節點之后。
/**
* @author Ryan Miao
*/
@Data
static class Node {
//是否是head節點。 true-YES
private Boolean head;
private Integer data;
private Node next;
}
那么,我們創建的一個節點是這樣的
Node head = new Node();
head.setData(-1);
head.setHead(true);
Node node = new Node();
node.setData(123);
node.setHead(false);
所以,我們首先要創建一個數組1 2 3 4 5 6 7 8 9
。
private Node toNode(Integer[] arr) {
Node head = new Node();
head.setData(-1);
head.setHead(true);
Node tail = head;
for (int i = 0; i < arr.length; i++) {
Node node = new Node();
node.setData(arr[i]);
node.setNext(null);
node.setHead(false);
// append to tail
tail.next = node;
// set tail to next
tail = node;
}
return head;
}
private Node makeNode(Integer... arr) {
return toNode(arr);
}
makeNode(1,2,3,4,5,6,7,8,9);
為了方便展示,寫一個鏈表遍歷的方法,用來打印鏈表結構:
private static void printNode(Node head) {
Node p = head.next;
while (p != null) {
System.out.print(p.getData());
p = p.next;
if (p != null) {
System.out.print("->");
}
}
System.out.println();
}
鏈表插入
插入節點tmp. 先找到要插入的位置,然后構造插入節點tmp。讓tmp指向后面的節點。前一個節點指向tmp。
@Test
public void testInsert() {
Node node = makeNode(3, 4, 5, 6, 7, 8, 9);
System.out.println("--------origin--------");
printNode(node);
// insert 10 between 4 and 5
Node p = node;
while (p != null) {
if (p.getData() == 4) {
Node tmp = new Node();
tmp.setData(10);
tmp.next = p.next;
p.next = tmp;
break;
}
p = p.next;
}
System.out.println("--------inserted--------");
printNode(node);
}
打印結果:
--------origin--------
3->4->5->6->7->8->9
--------inserted--------
3->4->10->5->6->7->8->9
鏈表刪除
鏈表刪除首先要找到要刪除的節點,將pre指向next。
@Test
public void deleteNode() {
Node node = makeNode(3, 4, 5, 6, 7, 8, 9);
System.out.println("--------origin--------");
printNode(node);
//delete 5
Node head = node;
Node p = node;
while (p != null) {
if (p.getData() == 5) {
//if the first is 5, skip
head.next = p.next;
break;
}
Node pre = p;
p = p.next;
if (p != null && p.getData().equals(5)) {
pre.next = p.next;
break;
}
}
System.out.println("---------deleted---------");
printNode(head);
}
打印結果
--------origin--------
3->4->5->6->7->8->9
---------deleted---------
3->4->6->7->8->9
鏈表反轉
鏈表最常問的算法就是反轉了。目前有兩個常見的方式,一個是頭插入法,新建一個head,遍歷原來的head,插入新鏈表。
一個是就地反轉。將鏈表看成兩部分,左邊是新鏈表,右邊是舊鏈表。每次從右邊取出一個,插入昨天的頭部,最終全部插入左邊。實現整體反轉。
頭插法
private Node headInsert(Node head) {
if (head.next == null) {
return head;
}
// new node head
final Node newHead = new Node();
newHead.setHead(true);
newHead.setData(head.getData());
// pointer
Node p = head;
while (p.next != null) {
// 暫存取下的節點
Node tmp = p.next;
// 原來的鏈表指針移動到下一個
p.next = p.next.next;
// 取下的節點 指向 新鏈表的頭節點之后
tmp.next = newHead.next;
// 新鏈表指向 插入的節點
newHead.next = tmp;
}
return newHead;
}
打印結果:
=========origin==========
3->4->5->6->7->8->9
---------head insert--------
9->8->7->6->5->4->3
就地反轉
private Node inverse(Node head) {
if (head == null) {
return null;
}
// 左邊鏈表的tail節點
Node leftTail = head.next;
if (leftTail == null) {
return head;
}
// 左邊鏈表的head節點
Node leftHead = head.next;
// 當前的指針右邊原始鏈表的第一個節點
Node pCur = leftTail.next;
if (pCur == null) {
leftTail.next = head;
head.next = null;
return leftTail;
}
while (pCur != null) {
// 左邊鏈表tail指向 右邊鏈表的下個節點
leftTail.next = pCur.next;
// 右邊鏈表的當前第一個節點指向昨天鏈表的head
pCur.next = leftHead;
// head指向插入的節點
head.next = pCur;
// 右邊鏈表指針移動下一個節點
pCur = leftTail.next;
}
return head;
}
打印結果:
=========origin==========
3->4->5->6->7->8->9
---------inverse--------
9->8->7->6->5->4->3
完整代碼如下:
package com.test.algorithm.link;
import lombok.Data;
import org.junit.Test;
/**
* @author Ryan Miao
* @see https://github.com/Ryan-Miao/l4Java/blob/master/src/test/java/com/test/algorithm/link/%E5%8D%95%E9%93%BE%E8%A1%A8%E5%8F%8D%E8%BD%AC.java
*/
public class 單鏈表反轉 {
/**
* @author Ryan Miao
*/
@Data
static class Node {
private Boolean head;
private Integer data;
private Node next;
}
@Test
public void testInsert() {
Node node = makeNode(3, 4, 5, 6, 7, 8, 9);
System.out.println("--------origin--------");
printNode(node);
// insert 10 between 4 and 5
Node p = node;
while (p != null) {
if (p.getData() == 4) {
Node tmp = new Node();
tmp.setData(10);
tmp.next = p.next;
p.next = tmp;
break;
}
p = p.next;
}
System.out.println("--------inserted--------");
printNode(node);
}
@Test
public void deleteNode() {
Node node = makeNode(3, 4, 5, 6, 7, 8, 9);
System.out.println("--------origin--------");
printNode(node);
//delete 5
Node head = node;
Node p = node;
while (p != null) {
if (p.getData() == 5) {
//if the first is 5, skip
head.next = p.next;
break;
}
Node pre = p;
p = p.next;
if (p != null && p.getData().equals(5)) {
pre.next = p.next;
break;
}
}
System.out.println("---------deleted---------");
printNode(head);
}
private Node makeNode(Integer... arr) {
return toNode(arr);
}
@Test
public void testInverse() {
Integer[] arr = new Integer[]{
3, 4, 5, 6, 7, 8, 9
};
inverse(arr);
headInsert(arr);
Integer[] arr2 = new Integer[]{
1
};
headInsert(arr2);
inverse(arr2);
Integer[] arr3 = new Integer[]{
1, 2
};
inverse(arr3);
headInsert(arr3);
}
private void headInsert(Integer[] arr) {
Node head = toNode(arr);
System.out.println("=========origin==========");
printNode(head);
Node inverse = headInsert(head);
System.out.println("---------head insert--------");
printNode(inverse);
}
private Node headInsert(Node head) {
if (head.next == null) {
return head;
}
// new node head
final Node newHead = new Node();
newHead.setHead(true);
newHead.setData(head.getData());
// pointer
Node p = head;
while (p.next != null) {
// 暫存取下的節點
Node tmp = p.next;
// 原來的鏈表指針移動到下一個
p.next = p.next.next;
// 取下的節點 指向 新鏈表的頭節點之后
tmp.next = newHead.next;
// 新鏈表指向 插入的節點
newHead.next = tmp;
}
return newHead;
}
private void inverse(Integer[] arr) {
Node head = toNode(arr);
System.out.println("=========origin==========");
printNode(head);
Node inverse = inverse(head);
System.out.println("---------inverse--------");
printNode(inverse);
}
private Node toNode(Integer[] arr) {
Node head = new Node();
head.setData(-1);
head.setHead(true);
Node tail = head;
for (int i = 0; i < arr.length; i++) {
Node node = new Node();
node.setData(arr[i]);
node.setNext(null);
tail.next = node;
tail = node;
}
return head;
}
private Node inverse(Node head) {
if (head == null) {
return null;
}
// 左邊鏈表的tail節點
Node leftTail = head.next;
if (leftTail == null) {
return head;
}
// 左邊鏈表的head節
Node leftHead = head.next;
// 當前的指針右邊原始鏈表的第一個節點
Node pCur = leftTail.next;
if (pCur == null) {
leftTail.next = head;
head.next = null;
return leftTail;
}
while (pCur != null) {
// 左邊鏈表tail指向 右邊鏈表的下個節點
leftTail.next = pCur.next;
// 右邊鏈表的當前第一個節點指向昨天鏈表的head
pCur.next = leftHead;
// head指向插入的節點
head.next = pCur;
// 右邊鏈表指針移動下一個節點
pCur = leftTail.next;
}
return head;
}
private static void printNode(Node head) {
Node p = head.next;
while (p != null) {
System.out.print(p.getData());
p = p.next;
if (p != null) {
System.out.print("->");
}
}
System.out.println();
}
}