歐克!歐克!小劉今天帶大家來學習一下連結串列 ,你要是學不會,你來捶我
1、連結串列(Linked List)介紹
1.1、記憶體結構
- 記憶體上來看:連結串列儲存空間 不連續(不像陣列)
1.2、邏輯結構
- 邏輯上來看:連結串列屬於 線性結構
1.3、連結串列特點
- 連結串列是以節點的方式來儲存,是 鏈式儲存
- data 域存放資料,next 域 指向下一個節點
- 連結串列分 帶頭節點的連結串列和 沒有頭節點的連結串列, 根據實際的需求來確定
2、連結串列應用場景
2.1、水滸英雄榜
- 使用帶 head 頭的 單向連結串列實現【水滸英雄排行榜管理】
2.2、連結串列節點定義
- no :英雄編號
- name :英雄名字
- nickName :英雄暱稱
- next :指向下一個 HeroNode 節點
class HeroNode {
public int no;
public String name;
public String nickName;
public HeroNode next;
public HeroNode(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickName = nickname;
}
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + ", nickName=" + nickName + "]";
}
}
2.3、連結串列定義
- DummyHead : 頭結點不存放資料,僅僅作為當前連結串列的入口
- head 欄位的值不能改變,一旦改變,就 丟失了整個連結串列的入口,我們也就無法通過 head 找到連結串列了
class SingleLinkedList {
private HeroNode head = new HeroNode(0, "", "");
public HeroNode getHead() {
return head;
}
2.4、遍歷連結串列
2.4.1、程式碼思路
- 定義輔助變數 temp ,相當於一個指標,指向 當前節點
- 何時遍歷完成? temp == null 表明當前節點為 null ,即表示已到連結串列末尾
- 如何遍歷? temp = temp.next ,每次輸出當前節點資訊之後,temp 指標後移
2.4.2、程式碼實現
- 遍歷連結串列
public void list() {
if (head.next == null) {
System.out.println("連結串列為空");
return;
}
HeroNode temp = head.next;
while (true) {
if (temp == null) {
break;
}
System.out.println(temp);
temp = temp.next;
}
}
2.5、尾部插入
2.5.1、程式碼思路
- 定義輔助變數 temp ,相當於一個指標,指向 當前節點
- 如何在連結串列末尾插入節點?
- 首先需要遍歷連結串列,找到連結串列最後一個節點,當 temp.next == null時,temp 節點指向連結串列最後一個節點
- 然後在 temp 節點之後插入節點即可: *temp.next = heroNode
2.5.2、程式碼實現
- 在連結串列尾部插入節點
public void add(HeroNode heroNode) {
HeroNode temp = head;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
}
temp.next = heroNode;
}
- 測試程式碼
public static void main(String[] args) {
HeroNode hero1 = new HeroNode(1, "宋江", "及時雨");
HeroNode hero2 = new HeroNode(2, "盧俊義", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吳用", "智多星");
HeroNode hero4 = new HeroNode(4, "林沖", "豹子頭");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(hero1);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
singleLinkedList.add(hero4);
singleLinkedList.list();
}
- 程式執行結果
HeroNode [no=1, name=宋江, nickName=及时雨]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=4, name=林冲, nickName=豹子头]
2.6、按順序插入
2.6.1、程式碼思路
- 定義輔助變數 temp ,相當於一個指標,指向 當前節點
- 應該如何執行插入?(待插入節點為 heroNode)
- 首先需要遍歷連結串列,找到連結串列中編號值比 heroNode.no 大的節點,暫且叫它 biggerNode ,然後把 heroNode 插入到 biggerNode 之前即可
- 怎麼找 biggerNode ?當 temp.next.no > heroNode.no 時,這時 temp.next 節點就是 biggerNode 節點。
- 為什麼是 temp.next 節點?只有找到 temp 節點和 temp.next(biggerNode )節點,才能在 temp 節點和 temp.next 節點之間插入 heroNode 節點
- 怎麼插入?
- heroNode .next = temp.next;
- temp.next = heroNode;
2.6.2、程式碼實現
- 按照英雄排名的順序進行插入
public void addByOrder(HeroNode heroNode) {
HeroNode temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.next.no > heroNode.no) {
break;
} else if (temp.next.no == heroNode.no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
System.out.printf("準備插入的英雄的編號 %d 已經存在了, 不能加入\n", heroNode.no);
} else {
heroNode.next = temp.next;
temp.next = heroNode;
}
}
- 測試程式碼
public static void main(String[] args) {
HeroNode hero1 = new HeroNode(1, "宋江", "及時雨");
HeroNode hero2 = new HeroNode(2, "盧俊義", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吳用", "智多星");
HeroNode hero4 = new HeroNode(4, "林沖", "豹子頭");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.addByOrder(hero1);
singleLinkedList.addByOrder(hero4);
singleLinkedList.addByOrder(hero2);
singleLinkedList.addByOrder(hero3);
singleLinkedList.list();
}
- 程式執行結果
HeroNode [no=1, name=宋江, nickName=及时雨]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=4, name=林冲, nickName=豹子头]
2.7、修改節點資訊
2.7.1、程式碼思路
- 定義輔助變數 temp ,相當於一個指標,指向 當前節點
- 如何找到指定節點? *temp.no = newHeroNode.no
2.7.2、程式碼實現
- 修改指定節點資訊
public void update(HeroNode newHeroNode) {
if (head.next == null) {
System.out.println("連結串列為空~");
return;
}
HeroNode temp = head.next;
boolean flag = false;
while (true) {
if (temp == null) {
break;
}
if (temp.no == newHeroNode.no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.name = newHeroNode.name;
temp.nickName = newHeroNode.nickName;
} else {
System.out.printf("沒有找到 編號 %d 的節點,不能修改\n", newHeroNode.no);
}
}
- 測試程式碼
public static void main(String[] args) {
HeroNode hero1 = new HeroNode(1, "宋江", "及時雨");
HeroNode hero2 = new HeroNode(2, "盧俊義", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吳用", "智多星");
HeroNode hero4 = new HeroNode(4, "林沖", "豹子頭");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.addByOrder(hero1);
singleLinkedList.addByOrder(hero4);
singleLinkedList.addByOrder(hero2);
singleLinkedList.addByOrder(hero3);
HeroNode newHeroNode = new HeroNode(2, "小盧", "玉麒麟~~");
singleLinkedList.update(newHeroNode);
singleLinkedList.list();
}
- 程式執行結果
HeroNode [no=1, name=宋江, nickName=及时雨]
HeroNode [no=2, name=小卢, nickName=玉麒麟~~]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=4, name=林冲, nickName=豹子头]
2.8、刪除節點
2.8.1、程式碼思路
- 定義輔助變數 temp ,相當於一個指標,指向 當前節點
- 如何找到待刪除的節點?遍歷連結串列,當 temp.next == no 時,temp.next 節點就是待刪除的節點
- 如何刪除? temp = temp.next.next 即可刪除 temp.next 節點,該節點沒有引用指向它,會被垃圾回收機制回收
2.8.2、程式碼實現
- 刪除指定節點
public void del(int no) {
HeroNode temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.next.no == no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.next = temp.next.next;
} else {
System.out.printf("要刪除的 %d 節點不存在\n", no);
}
}
- 測試程式碼
public static void main(String[] args) {
HeroNode hero1 = new HeroNode(1, "宋江", "及時雨");
HeroNode hero2 = new HeroNode(2, "盧俊義", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吳用", "智多星");
HeroNode hero4 = new HeroNode(4, "林沖", "豹子頭");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(hero1);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
singleLinkedList.add(hero4);
singleLinkedList.del(1);
singleLinkedList.del(4);
singleLinkedList.list();
}
- 程式執行結果
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=3, name=吴用, nickName=智多星]
2.9、總結
- 遍歷連結串列,執行操作時,判斷條件有時候是 temp ,有時候是 temp.next ,Why?
- 對於插入、刪除節點來說,需要知道 當前待操作的節點(heroNode)前一個節點的地址(指標),如果直接定位至當前待操作的節點 heroNode ,那沒得玩。。。因為不知道heroNode 前一個節點的地址,無法進行插入、刪除操作,所以 while 迴圈中的條件使用 temp.next 進行判斷
- 對於更新、遍歷操作來說,我需要的僅僅就只是當前節點的資訊,所以 while 迴圈中的條件使用 temp進行判斷
- 頭結點與首節點
- 參考資料:https://blog.csdn.net/WYpersist/article/details/80288056
- 頭結點是為了操作的統一與方便而設立的,放在第一個元素結點之前,其資料域一般無意義(當然有些情況下也可存放連結串列的長度、用做監視哨等等)。
- 首元結點也就是第一個元素的結點,它是頭結點後邊的第一個結點。
3、單連結串列面試題
3.1、求單連結串列中有效節點的個數
3.1.1、程式碼思路
- 求單連結串列中有效節點的個數:遍歷即可
3.1.2、程式碼實現
- 求單連結串列中有效節點的個數
public static int getLength(HeroNode head) {
if (head.next == null) {
return 0;
}
int length = 0;
HeroNode cur = head.next;
while (cur != null) {
length++;
cur = cur.next;
}
return length;
}
- 測試程式碼
public static void main(String[] args) {
HeroNode hero1 = new HeroNode(1, "宋江", "及時雨");
HeroNode hero2 = new HeroNode(2, "盧俊義", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吳用", "智多星");
HeroNode hero4 = new HeroNode(4, "林沖", "豹子頭");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(hero1);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
singleLinkedList.add(hero4);
singleLinkedList.list();
System.out.println("有效的節點個數=" + getLength(singleLinkedList.getHead()));
}
- 程式執行結果
HeroNode [no=1, name=宋江, nickName=及时雨]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=4, name=林冲, nickName=豹子头]
有效的节点个数=4
3.2、查詢單連結串列中的倒數第 k 個結點
3.2.1、程式碼思路
- 查詢單連結串列中的倒數第k個結點 【新浪面試題】
- 首先,獲取整個連結串列中元素的個數 size
- 在使用 for 迴圈定位至倒數第 index(形參) 個節點,返回即可
- for 迴圈的條件應如何確定?for (int i = 0; i < x; i++) 中 x 的值應是多少?我們需要定位至倒數第 index 個節點,在 for 迴圈之前,我們已經定位置首節點,還需再走 (size - index ) 步,定位至倒數第 index 個節點
- 舉例說明:連結串列中一共有 4 個元素,想要定位至倒數第 2 個節點,那麼需要在首節點之後走兩步,到達倒數第 2 個節點
3.2.2、程式碼實現
- 查詢單連結串列中的倒數第k個結點
public static HeroNode findLastIndexNode(HeroNode head, int index) {
if (head.next == null) {
return null;
}
int size = getLength(head);
if (index 0 || index > size) {
return null;
}
HeroNode cur = head.next;
for (int i = 0; i < size - index; i++) {
cur = cur.next;
}
return cur;
}
- 測試程式碼
public static void main(String[] args) {
HeroNode hero1 = new HeroNode(1, "宋江", "及時雨");
HeroNode hero2 = new HeroNode(2, "盧俊義", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吳用", "智多星");
HeroNode hero4 = new HeroNode(4, "林沖", "豹子頭");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(hero1);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
singleLinkedList.add(hero4);
singleLinkedList.list();
HeroNode res = findLastIndexNode(singleLinkedList.getHead(), 2);
System.out.println("res=" + res);
}
- 程式執行結果
HeroNode [no=1, name=宋江, nickName=及时雨]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=4, name=林冲, nickName=豹子头]
res=HeroNode [no=3, name=吴用, nickName=智多星]
3.3、單連結串列的反轉
3.3.1、程式碼思路
- 單連結串列的反轉【騰訊面試題,有點難度】
- 定義一個新的頭結點 reverseHead ,一點一點將連結串列反轉後,再串起來
- 怎麼個串法?
- 在原連結串列中每讀取一個節點(cur),先儲存其下一個節點的地址(next),然後將 cur 節點放在新連結串列的最前面
- 然後執行遍歷: cur = next ,即指標後移
- 遍歷完成後,新連結串列即是反轉後的連結串列
- 如何將 cur 節點插入在新連結串列的最前面
- cur.next = reverseHead.next;
- reverseHead.next = cur;
- while 迴圈終止條件? cur == null :已遍歷至連結串列尾部
- 單連結串列的翻轉可以參考我的這篇博文:https://blog.csdn.net/oneby1314/article/details/107577923
3.3.2、程式碼實現
- 單連結串列的反轉
public static void reversetList(HeroNode head) {
if (head.next == null || head.next.next == null) {
return;
}
HeroNode cur = head.next;
HeroNode next = null;
HeroNode reverseHead = new HeroNode(0, "", "");
while (cur != null) {
next = cur.next;
cur.next = reverseHead.next;
reverseHead.next = cur;
cur = next;
}
head.next = reverseHead.next;
}
- 測試程式碼
public static void main(String[] args) {
HeroNode hero1 = new HeroNode(1, "宋江", "及時雨");
HeroNode hero2 = new HeroNode(2, "盧俊義", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吳用", "智多星");
HeroNode hero4 = new HeroNode(4, "林沖", "豹子頭");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(hero1);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
singleLinkedList.add(hero4);
System.out.println("原來連結串列的情況~~");
singleLinkedList.list();
System.out.println("反轉單連結串列~~");
reversetList(singleLinkedList.getHead());
singleLinkedList.list();
}
- 程式執行結果
原来链表的情况~~
HeroNode [no=1, name=宋江, nickName=及时雨]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=4, name=林冲, nickName=豹子头]
反转单链表~~
HeroNode [no=4, name=林冲, nickName=豹子头]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=1, name=宋江, nickName=及时雨]
3.4、單連結串列的反轉(我的程式碼)
3.4.1、程式碼思路
- 單連結串列的反轉【騰訊面試題,有點難度】
- 原連結串列為 cur 指向 next ,反轉連結串列不就是把 next 指向 cur 嗎?
- 由於 next 指向 cur 時,next 將 丟失其下一節點的地址,所以需要先將 nnext 儲存起來
- next ==null 時連結串列已經反轉完畢,最後將頭結點指向 cur 節點即可
3.4.2、程式碼實現
- 單連結串列的反轉
public static void myReversetList(HeroNode head) {
if (head.next == null || head.next.next == null) {
return;
}
HeroNode cur = head.next;
HeroNode next = cur.next;
cur.next = null;
while (next != null) {
HeroNode nnext = next.next;
next.next = cur;
cur = next;
next = nnext;
}
head.next = cur;
}
- 測試程式碼
public static void main(String[] args) {
HeroNode hero1 = new HeroNode(1, "宋江", "及時雨");
HeroNode hero2 = new HeroNode(2, "盧俊義", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吳用", "智多星");
HeroNode hero4 = new HeroNode(4, "林沖", "豹子頭");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(hero1);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
singleLinkedList.add(hero4);
System.out.println("原來連結串列的情況~~");
singleLinkedList.list();
System.out.println("反轉單連結串列~~");
reversetList(singleLinkedList.getHead());
singleLinkedList.list();
}
- 程式執行結果
原来链表的情况~~
HeroNode [no=1, name=宋江, nickName=及时雨]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=4, name=林冲, nickName=豹子头]
反转单链表~~
HeroNode [no=4, name=林冲, nickName=豹子头]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=1, name=宋江, nickName=及时雨]
3.5、從尾到頭列印單連結串列
3.5.1、棧的基本使用
- 測試程式碼
public static void main(String[] args) {
Stack<String> stack = new Stack();
stack.add("jack");
stack.add("tom");
stack.add("smith");
while (stack.size() > 0) {
System.out.println(stack.pop());
}
}
- 程式執行結果
smith
tom
jack
3.5.2、程式碼思路
- 從尾到頭列印單連結串列 【百度,要求方式1:反向遍歷 。 方式2:Stack棧】
- 方式一:先將單連結串列進行反轉操作,然後再遍歷輸出,問題: 破壞原連結串列結構,不可取
- 方式二:遍歷連結串列,去除節點壓入棧中,利用棧 先進後出的特點,實現逆序列印
3.5.3、程式碼實現
- 從尾到頭列印單連結串列
public static void reversePrint(HeroNode head) {
if (head.next == null) {
return;
}
Stack<HeroNode> stack = new Stack<HeroNode>();
HeroNode cur = head.next;
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
while (stack.size() > 0) {
System.out.println(stack.pop());
}
}
- 測試程式碼
public static void main(String[] args) {
HeroNode hero1 = new HeroNode(1, "宋江", "及時雨");
HeroNode hero2 = new HeroNode(2, "盧俊義", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吳用", "智多星");
HeroNode hero4 = new HeroNode(4, "林沖", "豹子頭");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(hero1);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
singleLinkedList.add(hero4);
System.out.println("原來連結串列的情況~~");
singleLinkedList.list();
System.out.println("測試逆序列印單連結串列, 沒有改變連結串列的結構~~");
reversePrint(singleLinkedList.getHead());
}
- 程式執行結果
原来链表的情况~~
HeroNode [no=1, name=宋江, nickName=及时雨]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=4, name=林冲, nickName=豹子头]
测试逆序打印单链表, 没有改变链表的结构~~
HeroNode [no=4, name=林冲, nickName=豹子头]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=1, name=宋江, nickName=及时雨]
3.6、合併兩個有序的單連結串列
3.6.1、程式碼思路
- 合併兩個有序的單連結串列,合併之後的連結串列依然有序【課後練習】
3.6.2、程式碼實現
- 合併兩個有序的單連結串列,合併之後的連結串列依然有序
- 具體講解見我的一篇部落格:https://blog.csdn.net/oneby1314/article/details/107590876
3.7、單向連結串列所有程式碼
public class SingleLinkedListDemo {
public static void main(String[] args) {
HeroNode hero1 = new HeroNode(1, "宋江", "及時雨");
HeroNode hero2 = new HeroNode(2, "盧俊義", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吳用", "智多星");
HeroNode hero4 = new HeroNode(4, "林沖", "豹子頭");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(hero1);
singleLinkedList.add(hero4);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
System.out.println("原來連結串列的情況~~");
singleLinkedList.list();
System.out.println("反轉單連結串列~~");
reversetList(singleLinkedList.getHead());
singleLinkedList.list();
System.out.println("測試逆序列印單連結串列, 沒有改變連結串列的結構~~");
reversePrint(singleLinkedList.getHead());
singleLinkedList.addByOrder(hero1);
singleLinkedList.addByOrder(hero4);
singleLinkedList.addByOrder(hero2);
singleLinkedList.addByOrder(hero3);
singleLinkedList.list();
HeroNode newHeroNode = new HeroNode(2, "小盧", "玉麒麟~~");
singleLinkedList.update(newHeroNode);
System.out.println("修改後的連結串列情況~~");
singleLinkedList.list();
singleLinkedList.del(1);
singleLinkedList.del(4);
System.out.println("刪除後的連結串列情況~~");
singleLinkedList.list();
System.out.println("有效的節點個數=" + getLength(singleLinkedList.getHead()));
HeroNode res = findLastIndexNode(singleLinkedList.getHead(), 3);
System.out.println("res=" + res);
}
public static void reversePrint(HeroNode head) {
if (head.next == null) {
return;
}
Stack<HeroNode> stack = new Stack<HeroNode>();
HeroNode cur = head.next;
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
while (stack.size() > 0) {
System.out.println(stack.pop());
}
}
public static void reversetList(HeroNode head) {
if (head.next == null || head.next.next == null) {
return;
}
HeroNode cur = head.next;
HeroNode next = null;
HeroNode reverseHead = new HeroNode(0, "", "");
while (cur != null) {
next = cur.next;
cur.next = reverseHead.next;
reverseHead.next = cur;
cur = next;
}
head.next = reverseHead.next;
}
public static void myReversetList(HeroNode head) {
if (head.next == null || head.next.next == null) {
return;
}
HeroNode cur = head.next;
HeroNode next = cur.next;
cur.next = null;
while (next != null) {
HeroNode nnext = next.next;
next.next = cur;
cur = next;
next = nnext;
}
head.next = cur;
}
public static HeroNode findLastIndexNode(HeroNode head, int index) {
if (head.next == null) {
return null;
}
int size = getLength(head);
if (index 0 || index > size) {
return null;
}
HeroNode cur = head.next;
for (int i = 0; i < size - index; i++) {
cur = cur.next;
}
return cur;
}
public static int getLength(HeroNode head) {
if (head.next == null) {
return 0;
}
int length = 0;
HeroNode cur = head.next;
while (cur != null) {
length++;
cur = cur.next;
}
return length;
}
}
class SingleLinkedList {
private HeroNode head = new HeroNode(0, "", "");
public HeroNode getHead() {
return head;
}
public void add(HeroNode heroNode) {
HeroNode temp = head;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
}
temp.next = heroNode;
}
public void addByOrder(HeroNode heroNode) {
HeroNode temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.next.no > heroNode.no) {
break;
} else if (temp.next.no == heroNode.no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
System.out.printf("準備插入的英雄的編號 %d 已經存在了, 不能加入\n", heroNode.no);
} else {
heroNode.next = temp.next;
temp.next = heroNode;
}
}
public void update(HeroNode newHeroNode) {
if (head.next == null) {
System.out.println("連結串列為空~");
return;
}
HeroNode temp = head.next;
boolean flag = false;
while (true) {
if (temp == null) {
break;
}
if (temp.no == newHeroNode.no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.name = newHeroNode.name;
temp.nickName = newHeroNode.nickName;
} else {
System.out.printf("沒有找到 編號 %d 的節點,不能修改\n", newHeroNode.no);
}
}
public void del(int no) {
HeroNode temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.next.no == no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.next = temp.next.next;
} else {
System.out.printf("要刪除的 %d 節點不存在\n", no);
}
}
public void list() {
if (head.next == null) {
System.out.println("連結串列為空");
return;
}
HeroNode temp = head.next;
while (true) {
if (temp == null) {
break;
}
System.out.println(temp);
temp = temp.next;
}
}
}
class HeroNode {
public int no;
public String name;
public String nickName;
public HeroNode next;
public HeroNode(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickName = nickname;
}
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + ", nickName=" + nickName + "]";
}
}
4、雙向連結串列
4.1、與單向連結串列的比較
- 單向連結串列, 查詢的方向只能是一個方向, 而雙向連結串列可以向前或者向後查詢
- 單向連結串列不能自我刪除, 需要靠輔助節點 , 而雙向連結串列, 則可以 自我刪除, 所以前面我們單連結串列刪除時節點, 總是找到 temp ,temp 是待刪除節點的 前一個節點(認真體會)
4.2、連結串列節點定義
- 在單向連結串列節點的基礎上,增加 pre ,用於指向前一個節點
class HeroNode {
public int no;
public String name;
public String nickname;
public HeroNode next;
public HeroNode pre;
public HeroNode(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
}
}
4.3、連結串列定義
- 定義整個連結串列的頭結點,作為連結串列的入口
class DoubleLinkedList {
private HeroNode head = new HeroNode(0, "", "");
public HeroNode getHead() {
return head;
}
4.4、連結串列遍歷
4.4.1、程式碼思路
- 定義輔助變數 temp ,相當於一個指標,指向 當前節點 ,用於遍歷連結串列
- 何時停止 while 迴圈? temp == null :已經遍歷至連結串列尾部
4.4.2、程式碼實現
public void list() {
if (head.next == null) {
System.out.println("連結串列為空");
return;
}
HeroNode temp = head.next;
while (true) {
if (temp == null) {
break;
}
System.out.println(temp);
temp = temp.next;
}
}
4.5、尾部插入
4.5.1、程式碼思路
- 定義輔助變數 temp ,相當於一個指標,指向 當前節點
- 何時停止 while 迴圈? temp.next == null :temp 節點已經是連結串列最後一個節點,在 temp 節點之後插入 heroNode 節點即可
- 如何插入?
- temp.next 指向新的尾節點 heroNode : temp.next = heroNode;
- heroNode .pre 指向舊的尾節點 temp : *heroNode.pre = temp;
4.5.2、程式碼實現
- 在連結串列尾部插入節點
public void add(HeroNode heroNode) {
HeroNode temp = head;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
}
temp.next = heroNode;
heroNode.pre = temp;
}
4.6、按順序插入
4.6.1、程式碼思路
- 定義輔助變數 temp ,相當於一個指標,指向 當前節點
- 我們將 heroNode 節點插入到 temp 節點之後還是 temp 節點之前?
- 如果插入到 temp 節點之後:
- 判斷條件: temp.next.no > heroNode.no ,即 temp 的下一個節點的值比 heroNode 節點的值大,所以需要將 heroNode 插入到 temp 節點之後
- while 迴圈終止條件:
- temp.next == null :temp 節點已經是連結串列的尾節點
- temp.next.no > heroNode.no :heroNode 節點的值介於 temp 節點的值和 temp 下一個節點的值之間
- temp.next.no == heroNode.no :heroNode 節點的值等於 temp 下一個節點的值,不能進行插入
- 如果插入到 temp 節點之前:
- 判斷條件: temp.no > heroNode.no ,即 temp 節點的值比 heroNode 節點的值大,所以需要將 heroNode 插入到 temp 節點之前
- 存在的問題:如果需要在連結串列尾部插入 heroNode 節點,即需要在 null 節點之前插入 heroNode 節點, 定位至 null 節點將丟失其前一個節點的資訊(除非使用一個變數儲存起來),所以跳出迴圈的判斷條件為:temp.next == null
- 所以我們選取:【插入到 temp 節點之後】方案
- 如果插入到 temp 節點之後:
4.6.2、程式碼實現
- 程式碼
public void addByOrder(HeroNode heroNode) {
HeroNode temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.next.no > heroNode.no) {
break;
} else if (temp.next.no == heroNode.no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
System.out.printf("準備插入的英雄的編號 %d 已經存在了, 不能加入\n", heroNode.no);
} else {
heroNode.next = temp.next;
if(temp.next != null) {
temp.next.pre = heroNode;
}
temp.next = heroNode;
heroNode.pre = temp;
}
}
4.7、修改節點資訊
4.7.1、程式碼思路
- 定義輔助變數 temp ,相當於一個指標,指向 當前節點
- 如何找到指定節點? *temp.no == no
4.7.2、程式碼實現
- 修改指定節點的資訊
public void update(HeroNode newHeroNode) {
if (head.next == null) {
System.out.println("連結串列為空~");
return;
}
HeroNode temp = head.next;
boolean flag = false;
while (true) {
if (temp == null) {
break;
}
if (temp.no == newHeroNode.no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.name = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
} else {
System.out.printf("沒有找到 編號 %d 的節點,不能修改\n", newHeroNode.no);
}
}
4.8、刪除節點
4.8.1、程式碼思路
- 定義輔助變數 temp ,相當於一個指標,指向 當前節點
- while 迴圈的終止條件?由於 temp 節點就是待刪除節點,所以終止條件是: temp == null
- 為何雙向連結串列,可以實現 自我刪除?定位至待刪除的節點 temp ,由於temp 節點有其前一個節點和後一個節點的資訊,所以可實現自我刪除
- 如何刪除?
- temp 的前一個節點的 next 域指向 temp 的後一個節點: temp.pre.next = temp.next;
- temp 的後一個節點的 pre 域指向 temp 的前一個節點: temp.next.pre = temp.pre;
- 有個地方需要注意,如果 temp 已經是連結串列尾節點,temp 已經沒有下一個節點
- 這時只需要將 temp 的前一個節點的 next 指向 null 即可
- 所以 temp.next.pre = temp.pre; 執行的前提條件是 *temp.next != null
4.8.2、程式碼實現
- 刪除指定節點
public void del(int no) {
if (head.next == null) {
System.out.println("連結串列為空,無法刪除");
return;
}
HeroNode temp = head.next;
boolean flag = false;
while (true) {
if (temp == null) {
break;
}
if (temp.no == no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.pre.next = temp.next;
if (temp.next != null) {
temp.next.pre = temp.pre;
}
} else {
System.out.printf("要刪除的 %d 節點不存在\n", no);
}
}
4.9、雙向連結串列測試
4.9.1、測試程式碼
public static void main(String[] args) {
System.out.println("雙向連結串列的測試");
HeroNode hero1 = new HeroNode(1, "宋江", "及時雨");
HeroNode hero2 = new HeroNode(2, "盧俊義", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吳用", "智多星");
HeroNode hero4 = new HeroNode(5, "林沖", "豹子頭");
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
doubleLinkedList.add(hero1);
doubleLinkedList.add(hero2);
doubleLinkedList.add(hero3);
doubleLinkedList.add(hero4);
doubleLinkedList.list();
doubleLinkedList.addByOrder(new HeroNode(4, "Heygo", "Heygogo"));
doubleLinkedList.addByOrder(new HeroNode(6, "Oneby", "Onebyone"));
System.out.println("按順序插入後的情況");
doubleLinkedList.list();
HeroNode newHeroNode = new HeroNode(5, "公孫勝", "入雲龍");
doubleLinkedList.update(newHeroNode);
System.out.println("修改後的連結串列情況");
doubleLinkedList.list();
doubleLinkedList.del(3);
System.out.println("刪除後的連結串列情況~~");
doubleLinkedList.list();
}
4.9.2、程式執行結果
双向链表的测试
HeroNode [no=1, name=宋江, nickname=及时雨]
HeroNode [no=2, name=卢俊义, nickname=玉麒麟]
HeroNode [no=3, name=吴用, nickname=智多星]
HeroNode [no=5, name=林冲, nickname=豹子头]
按顺序插入后的情况
HeroNode [no=1, name=宋江, nickname=及时雨]
HeroNode [no=2, name=卢俊义, nickname=玉麒麟]
HeroNode [no=3, name=吴用, nickname=智多星]
HeroNode [no=4, name=Heygo, nickname=Heygogo]
HeroNode [no=5, name=林冲, nickname=豹子头]
HeroNode [no=6, name=Oneby, nickname=Onebyone]
修改后的链表情况
HeroNode [no=1, name=宋江, nickname=及时雨]
HeroNode [no=2, name=卢俊义, nickname=玉麒麟]
HeroNode [no=3, name=吴用, nickname=智多星]
HeroNode [no=4, name=Heygo, nickname=Heygogo]
HeroNode [no=5, name=公孙胜, nickname=入云龙]
HeroNode [no=6, name=Oneby, nickname=Onebyone]
删除后的链表情况~~
HeroNode [no=1, name=宋江, nickname=及时雨]
HeroNode [no=2, name=卢俊义, nickname=玉麒麟]
HeroNode [no=4, name=Heygo, nickname=Heygogo]
HeroNode [no=5, name=公孙胜, nickname=入云龙]
HeroNode [no=6, name=Oneby, nickname=Onebyone]
4.10、雙向連結串列所有程式碼
public class DoubleLinkedListDemo {
public static void main(String[] args) {
System.out.println("雙向連結串列的測試");
HeroNode hero1 = new HeroNode(1, "宋江", "及時雨");
HeroNode hero2 = new HeroNode(2, "盧俊義", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吳用", "智多星");
HeroNode hero4 = new HeroNode(5, "林沖", "豹子頭");
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
doubleLinkedList.add(hero1);
doubleLinkedList.add(hero2);
doubleLinkedList.add(hero3);
doubleLinkedList.add(hero4);
doubleLinkedList.list();
doubleLinkedList.addByOrder(new HeroNode(0, "Kobe", "BlackMamba"));
doubleLinkedList.addByOrder(new HeroNode(4, "Heygo", "Heygogo"));
doubleLinkedList.addByOrder(new HeroNode(6, "Oneby", "Onebyone"));
System.out.println("按順序插入後的情況");
doubleLinkedList.list();
HeroNode newHeroNode = new HeroNode(5, "公孫勝", "入雲龍");
doubleLinkedList.update(newHeroNode);
System.out.println("修改後的連結串列情況");
doubleLinkedList.list();
doubleLinkedList.del(3);
System.out.println("刪除後的連結串列情況~~");
doubleLinkedList.list();
}
}
class DoubleLinkedList {
private HeroNode head = new HeroNode(0, "", "");
public HeroNode getHead() {
return head;
}
public void list() {
if (head.next == null) {
System.out.println("連結串列為空");
return;
}
HeroNode temp = head.next;
while (true) {
if (temp == null) {
break;
}
System.out.println(temp);
temp = temp.next;
}
}
public void add(HeroNode heroNode) {
HeroNode temp = head;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
}
temp.next = heroNode;
heroNode.pre = temp;
}
public void addByOrder(HeroNode heroNode) {
HeroNode temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.next.no > heroNode.no) {
break;
} else if (temp.next.no == heroNode.no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
System.out.printf("準備插入的英雄的編號 %d 已經存在了, 不能加入\n", heroNode.no);
} else {
heroNode.next = temp.next;
if(temp.next != null) {
temp.next.pre = heroNode;
}
temp.next = heroNode;
heroNode.pre = temp;
}
}
public void update(HeroNode newHeroNode) {
if (head.next == null) {
System.out.println("連結串列為空~");
return;
}
HeroNode temp = head.next;
boolean flag = false;
while (true) {
if (temp == null) {
break;
}
if (temp.no == newHeroNode.no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.name = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
} else {
System.out.printf("沒有找到 編號 %d 的節點,不能修改\n", newHeroNode.no);
}
}
public void del(int no) {
if (head.next == null) {
System.out.println("連結串列為空,無法刪除");
return;
}
HeroNode temp = head.next;
boolean flag = false;
while (true) {
if (temp == null) {
break;
}
if (temp.no == no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.pre.next = temp.next;
if (temp.next != null) {
temp.next.pre = temp.pre;
}
} else {
System.out.printf("要刪除的 %d 節點不存在\n", no);
}
}
}
class HeroNode {
public int no;
public String name;
public String nickname;
public HeroNode next;
public HeroNode pre;
public HeroNode(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
}
}
4.11、總結
- 輔助變數 temp ,相當於一個指標,指向 當前節點
- 如果定位至當前節點會丟失前一個節點的資訊,那麼我們只能定位至待操作節點的前一個節點:使用 temp.next 進行條件判斷
5、單向環形連結串列
5.1、單向環形連結串列應用場景
- Josephu 問題為: 設編號為 1, 2, ... n 的 n 個人圍坐一圈, 約定編號為 k(1
5.2、單向環形連結串列圖解
5.3、Josephu 問題
- 用一個不帶頭結點的迴圈連結串列來處理 Josephu 問題: 先構成一個有 n 個結點的 單迴圈連結串列, 然後由 k 結點起從 1 開始計數, 計到 m 時, 對應結點從連結串列中刪除, 然後再從被刪除結點的下一個結點又從 1 開始計數, 直到最後一個結點從連結串列中刪除演算法結束。
5.4、環形連結串列的構建與遍歷
5.4.1、Boy 節點的定義
- Boy 節點就是個普普通通的單向連結串列節點
class Boy {
private int no;
private Boy next;
public Boy(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Boy getNext() {
return next;
}
public void setNext(Boy next) {
this.next = next;
}
}
5.4.2、單向迴圈連結串列的定義
- first 節點為單向迴圈連結串列的 首節點,是真實 存放資料的節點,不是頭結點
class CircleSingleLinkedList {
private Boy first = null;
5.4.3、構建單向迴圈連結串列
1、程式碼思路
- 長度為 1 的情況:
- 新建立的 boy 節點即是首節點: first = boy;
- 自封閉(自己構成環形連結串列): first.setNext(first);
- 此時 first 節點既是首節點,也是尾節點,輔助指標也指向 first : curBoy = first;
- 長度不為 1 的情況:
- 將 boy 節點新增至環形連結串列的最後: curBoy.setNext(boy); ,curBoy 節點永遠是環形連結串列的尾節點
- 構成環形連結串列(最): boy.setNext(first);
- 輔助指標後移,指向環形連結串列的尾節點: *curBoy = boy;
2、程式碼實現
public void addBoy(int nums) {
if (nums < 1) {
System.out.println("nums的值不正確");
return;
}
Boy curBoy = null;
for (int i = 1; i nums; i++) {
Boy boy = new Boy(i);
if (i == 1) {
first = boy;
first.setNext(first);
curBoy = first;
} else {
curBoy.setNext(boy);
boy.setNext(first);
curBoy = boy;
}
}
}
5.4.4、遍歷單向迴圈連結串列
1、程式碼思路
- 定義輔助變數 curBoy ,相當於一個指標,指向 當前節點
- 何時退出 while 迴圈?當 curBoy 已經指向環形連結串列的尾節點: *curBoy.getNext() == first
2、程式碼實現
public void showBoy() {
if (first == null) {
System.out.println("沒有任何小孩~~");
return;
}
Boy curBoy = first;
while (true) {
System.out.printf("小孩的編號 %d \n", curBoy.getNo());
if (curBoy.getNext() == first) {
break;
}
curBoy = curBoy.getNext();
}
}
5.5、解決 Josephu 問題
5.5.1、程式碼思路
- 輔助變數 helper :helper 永都指向 環形連結串列的尾節點,環形連結串列的尾節點永遠都指向首節點,可得出: helper.getNext() == first
- 如何將 helper 定位至環形連結串列的尾節點?
- 初始化時,讓 helper = first ,此時 helper 指向環形連結串列的首節點
- while 迴圈終止條件? helper.getNext() == first :此時 helper 已經移動至環形連結串列的尾節點
- 如何定位至第 startNo 個節點?如果想要定位至第 2 個節點,那麼則需要讓 first 和 helper 都移動 1 步,所以讓 first 和 helper 都移動 (startNo - 1)步即可
- 如何數 nums 下?讓 first 和 helper 都移動 (nums - 1)步即可
- 如何實現出圈?
- 我們需要將 first 指向的節點出圈,first 前一個節點的地址在 helper 中存著(環形連結串列)
- 先讓 first 後移一步: first = first.getNext;
- 出圈: helper.setNext(first); ,原來的 first 節點由於沒有任何引用,便會被垃圾回收機制回收
- while 迴圈終止條件?圈中只剩一人: *helper == first
5.5.2、程式碼實現
public void countBoy(int startNo, int countNum, int nums) {
if (first == null || startNo < 1 || startNo > nums) {
System.out.println("引數輸入有誤, 請重新輸入");
return;
}
Boy helper = first;
while (true) {
if (helper.getNext() == first) {
break;
}
helper = helper.getNext();
}
for (int j = 0; j < startNo - 1; j++) {
first = first.getNext();
helper = helper.getNext();
}
while (true) {
if (helper == first) {
break;
}
for (int j = 0; j < countNum - 1; j++) {
first = first.getNext();
helper = helper.getNext();
}
System.out.printf("小孩%d出圈\n", first.getNo());
first = first.getNext();
helper.setNext(first);
}
System.out.printf("最後留在圈中的小孩編號%d \n", first.getNo());
}
5.6、Josephu 問題測試
5.6.1、測試程式碼
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(5);
circleSingleLinkedList.showBoy();
circleSingleLinkedList.countBoy(1, 2, 3);
}
5.6.2、程式執行結果
小孩的编号 1
小孩的编号 2
小孩的编号 3
小孩的编号 4
小孩的编号 5
小孩2出圈
小孩4出圈
小孩1出圈
小孩5出圈
最后留在圈中的小孩编号3
5.7、Josephu 問題所有程式碼
public class Josepfu {
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(5);
circleSingleLinkedList.showBoy();
circleSingleLinkedList.countBoy(1, 2, 3);
}
}
class CircleSingleLinkedList {
private Boy first = null;
public void addBoy(int nums) {
if (nums < 1) {
System.out.println("nums的值不正確");
return;
}
Boy curBoy = null;
for (int i = 1; i nums; i++) {
Boy boy = new Boy(i);
if (i == 1) {
first = boy;
first.setNext(first);
curBoy = first;
} else {
curBoy.setNext(boy);
boy.setNext(first);
curBoy = boy;
}
}
}
public void showBoy() {
if (first == null) {
System.out.println("沒有任何小孩~~");
return;
}
Boy curBoy = first;
while (true) {
System.out.printf("小孩的編號 %d \n", curBoy.getNo());
if (curBoy.getNext() == first) {
break;
}
curBoy = curBoy.getNext();
}
}
public void countBoy(int startNo, int countNum, int nums) {
if (first == null || startNo < 1 || startNo > nums) {
System.out.println("引數輸入有誤, 請重新輸入");
return;
}
Boy helper = first;
while (true) {
if (helper.getNext() == first) {
break;
}
helper = helper.getNext();
}
for (int j = 0; j < startNo - 1; j++) {
first = first.getNext();
helper = helper.getNext();
}
while (true) {
if (helper == first) {
break;
}
for (int j = 0; j < countNum - 1; j++) {
first = first.getNext();
helper = helper.getNext();
}
System.out.printf("小孩%d出圈\n", first.getNo());
first = first.getNext();
helper.setNext(first);
}
System.out.printf("最後留在圈中的小孩編號%d \n", first.getNo());
}
}
class Boy {
private int no;
private Boy next;
public Boy(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Boy getNext() {
return next;
}
public void setNext(Boy next) {
this.next = next;
}
}
5.8、總結
- 操作單向連結串列:對於插入、刪除操作,只能定位至待操作節點的前一個節點,如果定位至當前節點,那麼其上一個節點的資訊便會丟失