寫鏈表代碼是最考驗 邏輯思惟能力的,指針來回指,指着指着就指迷糊,在寫代碼以前先注意這麼幾個問題。
2019年3月20日 查漏補缺,新增循環鏈表的實現
2019年3月18日 查漏補缺,新增雙向鏈表的實現
有些語言有「指針」的概念,好比 C 語言;有些語言沒有指針,取而代之的是「引用」,好比 Java、Python。無論是「指針」仍是「引用」,實際上,它們的意思都是同樣的,都是存儲所指對象的內存地址。
node
將某個變量賦值給指針,實際上就是將這個變量的地址賦值給指針,或者反過來講,指針中存儲了這個變量的內存地址,指向了這個變量,經過指針就能找到這個變量。
git
最多見的指針代碼應該是這個了,p->next=q。它的意思是說p 結點中的 next 指針存儲了 q 結點的內存地址。github
由於操做不當的時候,很容易形成指針丟失。好比a ->b之間插入一個c,同時不注意的時候很容易這麼寫緩存
//聲明一個p;
bash
p->next = c; // 將 p 的 next 指針指向 c 結點;
測試
c->next = p->next; // 將 c 的結點的 next 指針指向 b 結點;
ui
這就很容易讓鏈表斷成兩半,形成指針丟失。正確的寫法是將二三兩行代碼順序換過來便可。this
和插入同樣,若是不及時釋放內存空間,聚沙成塔,很容易形成內存泄漏。spa
哨兵節點主要是處理邊界問題的,並不直接參與業務邏輯,當引入哨兵節點的時候,無論鏈表是否是空,head 指針都會一直指向這個哨兵結點。咱們也把這種有哨兵結點的鏈表叫帶頭鏈表。相反,沒有哨兵結點的鏈表就叫做不帶頭鏈表。3d
以單鏈表爲例,全部源碼均已上傳至github:連接
public class Node {
public int data;
public Node next;
public Node(int data, Node next) {
this.data = data;
this.next = next;
}
}複製代碼
順序插入
public void insertTail(int value) {
Node node = new Node(value, null);
if (head == null) {
head = node;
} else {
Node q = head;
// 找到最後next一個爲不空節點並賦值
while (q.next != null) {
q = q.next;
}
// 注意,順序不可反,不然鏈表就斷開了
// 很精髓
node.next = q.next;
q.next = node;
}
}複製代碼
刪除,難點就一行q.next = q.next.next;
public void deleteByNode(int value) {
if (head == null)
return;
Node p = head;
Node q = null;
// 從鏈表中找到要刪除的value
while (p != null && p.data != value) {
q = p;
p = p.next;
}
if (p == null)
return;
if (q == null) {
head = head.next;
} else {
// 刪除節點,其實就是把要刪除的值prev節點指向他的next節點
// 很精髓
q.next = q.next.next;
}
}複製代碼
查詢,分兩個,一個是根據下標查詢,一個是根據value查詢
public Node findByIndex(int index) {
Node p = head;
int pos = 0;
while (p != null && pos != index) {
p = p.next;
++pos;
}
return p;
}複製代碼
public Node findByValue(int value) {
Node p = head;
while (p != null && p.data != value) {
p = p.next;
}
return p;
}複製代碼
測試結果:
該實現的思想是:從前日後反轉各個結點的指針域的指向。
將當前節點cur的next節點 緩存到nextNode,而後更改當前節點指針指向上一結點prevNode。也就是說在反轉當前結點指針指向前,先把當前結點的指針域用nextNode臨時保存,以便下一次使用
public Node reverse(Node node) {
Node resNode = null;
Node prevNode = null;
Node curNode = node;
while (curNode != null) {
Node nextNode = curNode.next;
if (nextNode == null) {
resNode = curNode;
}
curNode.next = prevNode;
prevNode = curNode;
curNode = nextNode;
}
return resNode;
}複製代碼
測試結果:
該檢測的核心思想是:快慢指針法。在這裏,滿指針每走一步,快指針走兩步,能夠用數學概括法來考慮,首先,若是鏈表是個環,因此相遇的過程能夠看做是快指針從後邊追趕慢指針的過程。那麼
1:快指針與慢指針之間差一步。此時繼續日後走,慢指針前進一步,快指針前進兩步,二者相遇。
2:快指針與慢指針之間差兩步。此時繼續日後走,慢指針前進一步,快指針前進兩步,二者之間相差一步,轉化爲第一種狀況。
...
N:快指針與慢指針之間差N步。此時繼續日後走,慢指針前進一步,快指針前進兩步,二者之間相差(N+1-2)-> N-1步。
代碼以下:
public boolean checkCircle(Node node) {
if (node == null)
return false;
// 快慢指針法
Node slow = node;
Node fast = node.next;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (slow == fast)
return true;
}
return false;
}複製代碼
測試結果:
該實現也是用到了快慢指針法。
代碼以下:
public Node findMiddleByNode(Node node) {
if (node == null)
return null;
// 快慢指針法
Node fast = node.next;
Node slow = node;
// 快指針走完一圈,慢指針半圈
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}複製代碼
測試結果以下:
該實現依舊用到了快慢指針法,不過和以前的有一點區別。以node長度爲n,刪倒數第k(k<n)個爲例。首先,第一個while先計算若是正着刪,須要讓慢指針走幾步,計算爲n-k。第二個while循環則經過遍歷快指針計算定位須要走n-k步,獲得preNode。而後判斷是否爲空,不爲空則常規方法刪除。
public Node deleteLastKByNode(Node node, int k) {
// 快慢指針法
Node fast = node;
// 計數
int i = 1;
while (fast != null && i < k) {
fast = fast.next;
++i;
}
if (fast == null)
return node;
Node slow = node;
Node prevNode = null;
while (fast.next != null) {
fast = fast.next;
prevNode = slow;
slow = slow.next;
}
if (prevNode == null) {
node = node.next;
} else {
// 刪除
prevNode.next = prevNode.next.next;
}
return node;
}複製代碼
測試結果:
該實現的思路是,首先比較兩個有序鏈表,若是pNode爲空,直接返回qNode,反之qNode爲空,則返回pNode,而後比較p和q,將最小的節點賦值給resNode,同時將最小的節點向後移動一位。設置一個node指向resNode節點,用於方便鏈接其它節點,在繼續比較p和q,一樣選出小的那個節點,將該節點設爲合併後的鏈表的第二個節點,用node.next表示該節點,一直重複上述過程,直到p和q有一個爲null,而後再將不爲null的節點放入新鏈表後便可。
public Node mergeNode(Node pNode, Node qNode) {
if (pNode == null)
return qNode;
if (qNode == null)
return pNode;
Node p = pNode;
Node q = qNode;
Node resNode = null;
if (p.data < q.data) {
resNode = p;
p = p.next;
} else {
resNode = q;
q = q.next;
}
Node node = resNode;
while (p != null && q != null) {
if (p.data < q.data) {
node.next = p;
p = p.next;
} else {
node.next = q;
q = q.next;
}
node = node.next;
}
if (p != null) {
node.next = p;
} else {
node.next = q;
}
return resNode;
}複製代碼
測試結果:
package juejin.lc.linkedList;
/**
* 雙向鏈表類
*/
public class DulNode {
/**
* 數據
*/
public int data;
/**
* 前驅節點
*/
public DulNode prev;
/**
* 後繼節點
*/
public DulNode next;
private DulNode(){}
/**
* 有參構造方法
*
* @param data 數據
*/
public DulNode(int data) {
this.data = data;
}
}複製代碼
private DulNode head;
private DulNode tail;
private int count;
private DulLinkList() {
head = null;
tail = null;
count = 0;
}複製代碼
添加到鏈表頭部
private void insertToHead(int data) {
DulNode dulNode = new DulNode(data);
if (count == 0) {
head = dulNode;
tail = dulNode;
} else {
DulNode prev = head;
prev.prev = dulNode;
dulNode.next = head;
head = dulNode;
}
++count;
}複製代碼
添加到鏈表尾部
private void insertToTail(int data) {
DulNode dulNode = new DulNode(data);
if (count == 0) {
head = dulNode;
tail = dulNode;
} else {
DulNode next = tail;
dulNode.prev = next;
next.next = dulNode;
tail = dulNode;
}
++count;
}複製代碼
添加到鏈表指定位置
private void insertByIndex(int index, int data) {
if (index > count) {//放入鏈表尾部
insertToTail(data);
} else {
DulNode resNode = selectByIndex(index);
if (null != resNode) {
System.out.println("根據index索引所得返回值爲:" + resNode.data);
DulNode dulNode = new DulNode(data);
DulNode prev = resNode.prev;
prev.next = dulNode;
dulNode.prev = prev;
dulNode.next = resNode;
resNode.prev = dulNode;
}
}
++count;
}複製代碼
刪除鏈表頭部
private DulNode deleteHead() {
DulNode p = head;
if (count > 0) {
head = head.next;
head.prev = null;
--count;
}
return p;
}複製代碼
刪除鏈表尾部
private DulNode deleteTail() {
DulNode q = tail;
if (count > 0) {
tail = tail.prev;
tail.next = null;
--count;
}
return q;
}複製代碼
刪除鏈表指定位置
private void deleteByIndex(int index) {
if (index > count)
return;
//刪除這邊加了個小技巧,若是是鏈表頭部或者尾部,直接刪除,中間值開始查找
if (index == 0) {
head = head.next;
head.prev = null;
} else if (index == count - 1) {
tail = tail.prev;
tail.next = null;
} else {
DulNode resNode = selectByIndex(index);
if (null != resNode) {
System.out.println("要刪除的元素爲:" + resNode.data);
DulNode prev = resNode.prev;
DulNode next = resNode.next;
prev.next = next;
next.prev = prev;
resNode.prev = null;
resNode.next = null;
}
}
--count;
}複製代碼
根據指定索引獲取節點
private DulNode selectByIndex(int index) {
if (index > count) return null;
int num = 0;
DulNode dulNode = head;
while (num < index) {
dulNode = dulNode.next;
++num;
}
return dulNode;
}複製代碼
根據指定節點獲取下標
private int indexOf(int data) {
int num = 0;
DulNode dulNode = head;
while (num < count && null != dulNode) {
if (data == dulNode.data) {
return num;
}
dulNode = dulNode.next;
++num;
}
return -1;
}複製代碼
測試代碼:
DulLinkList dulLinkList = new DulLinkList();
dulLinkList.insertToHead(3);
dulLinkList.insertToHead(2);
dulLinkList.insertToHead(1);
dulLinkList.insertToTail(5);
dulLinkList.insertToTail(6);
dulLinkList.insertToTail(7);
dulLinkList.insertToTail(8);
dulLinkList.insertToTail(9);
dulLinkList.printAll();
System.out.println("刪除頭部,並返回");
DulNode resHead = dulLinkList.deleteHead();
System.out.println("返回值:" + resHead.data);
dulLinkList.insertByIndex(3, 4);
dulLinkList.printAll();
System.out.println("刪除尾部,並返回");
DulNode resTail = dulLinkList.deleteTail();
System.out.println("返回值:" + resTail.data);
dulLinkList.printAll();
int data = 5;
int res = dulLinkList.indexOf(data);
System.out.println("數據" + data + "的下標 " + (res != -1 ? res : "不存在"));
int delData = 5;
dulLinkList.deleteByIndex(delData);
dulLinkList.printAll();複製代碼
測試結果:
/**
* 頭結點
*/
private Node head;
/**
* 尾結點
*/
private Node tail;
/**
* 數據數量
*/
private int count;
/**
* 無參構造方法
*/
private CircleLinkList() {
head = null;
tail = null;
count = 0;
}複製代碼
private void insertHead(int data) {
Node node = new Node(data, null);
if (count == 0) {
head = node;
tail = node;
} else {
node.next = head;
head = node;
tail.next = head;
}
++count;
}複製代碼
private void insertTail(int data) {
Node node = new Node(data, null);
if (count == 0) {
head = node;
tail = node;
} else {
tail.next = node;
node.next = head;
head = node;
}
++count;
}複製代碼
private void insertByIndex(int index, int data) {
if (index > count) {
insertTail(data);
} else {
Node resNode = findByIndex(index);
if (null != resNode) {
System.out.println("返回節點的值:" + resNode.data);
resNode.next = new Node(data, resNode.next);
++count;
}
}
}複製代碼
private void delete(int index) {
if (index > count) return;
if (index == 0) {
Node node = head;
head = head.next;
node.next = null;
tail.next = head;
} else if (index == count - 1) {
Node resNode = findByIndex(index);
if (null != resNode) {
Node node = tail;
tail = resNode;
node.next = null;
tail.next = head;
}
} else {
Node resNode = findByIndex(index - 1);//查找該節點的前一個節點
if (null != resNode) {
System.out.println("返回節點的值:" + resNode.data);
resNode.next = resNode.next.next;
}
}
--count;
}複製代碼
private Node findByIndex(int index) {
if (index > count) return null;
Node node = head;
int num = 0;
while (num < index) {
node = node.next;
++num;
}
return node;
}複製代碼
public static void main(String[] args) {
CircleLinkList circleLinkList = new CircleLinkList();
circleLinkList.insertHead(3);
circleLinkList.insertHead(2);
circleLinkList.insertHead(1);
circleLinkList.insertTail(5);
circleLinkList.insertTail(6);
circleLinkList.insertTail(7);
circleLinkList.insertTail(8);
circleLinkList.insertByIndex(3,4);
circleLinkList.printAll();
LinkedListAlgo linkedListAlgo = new LinkedListAlgo();
boolean result = linkedListAlgo.checkCircle(circleLinkList.head);
System.out.println("檢測環返回值:" + result);
circleLinkList.delete(1);
circleLinkList.printAll();
}複製代碼
您的點贊和關注是對我最大的支持,謝謝!