看不懂的數據結構-鏈表深度刨析

歐克!歐克!小劉今天帶你們來學習一下鏈表 ,你要是學不會,你來捶我java

img

img

一、鏈表(Linked List)介紹

1.一、內存結構

  • 內存上來看:鏈表存儲空間 不連續(不像數組)

1.二、邏輯結構

  • 邏輯上來看:鏈表屬於 線性結構

1.三、鏈表特色

  • 鏈表是以節點的方式來存儲,是 鏈式存儲
  • data 域存放數據,next 域 指向下一個節點
  • 鏈表分 帶頭節點的鏈表和 沒有頭節點的鏈表, 根據實際的需求來肯定

二、鏈表應用場景

2.一、水滸英雄榜

  • 使用帶 head 頭的 單向鏈表實現【水滸英雄排行榜管理】

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.三、鏈表定義

  • DummyHead : 頭結點不存放數據,僅僅做爲當前鏈表的入口
  • head 字段的值不能改變,一旦改變,就 丟失了整個鏈表的入口,咱們也就沒法經過 head 找到鏈表了
class SingleLinkedList {

	private HeroNode head = new HeroNode(0, "", "");

	public HeroNode getHead() {
		return head;
	}

2.四、遍歷鏈表

2.4.一、代碼思路

  • 定義輔助變量 temp ,至關於一個指針,指向 當前節點
  • 什麼時候遍歷完成? temp == null 代表當前節點爲 null ,即表示已到鏈表末尾
  • 如何遍歷? temp = temp.next ,每次輸出當前節點信息以後,temp 指針後移

2.4.二、代碼實現

  • 遍歷鏈表
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.五、尾部插入

2.5.一、代碼思路

  • 定義輔助變量 temp ,至關於一個指針,指向 當前節點
  • 如何在鏈表末尾插入節點?
    • 首先須要遍歷鏈表,找到鏈表最後一個節點,當 temp.next == null時,temp 節點指向鏈表最後一個節點
    • 而後在 temp 節點以後插入節點便可: *temp.next = heroNode

2.5.二、代碼實現

  • 在鏈表尾部插入節點
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.六、按順序插入

2.6.一、代碼思路

  • 定義輔助變量 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.二、代碼實現

  • 按照英雄排名的順序進行插入
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.七、修改節點信息

2.7.一、代碼思路

  • 定義輔助變量 temp ,至關於一個指針,指向 當前節點
  • 如何找到指定節點? *temp.no = newHeroNode.no

2.7.二、代碼實現

  • 修改指定節點信息
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.八、刪除節點

2.8.一、代碼思路

  • 定義輔助變量 temp ,至關於一個指針,指向 當前節點
  • 如何找到待刪除的節點?遍歷鏈表,當 temp.next == no 時,temp.next 節點就是待刪除的節點
  • 如何刪除? temp = temp.next.next 便可刪除 temp.next 節點,該節點沒有引用指向它,會被垃圾回收機制回收

2.8.二、代碼實現

  • 刪除指定節點
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.九、總結

  • 遍歷鏈表,執行操做時,判斷條件有時候是 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.二、代碼實現

  • 求單鏈表中有效節點的個數
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.二、查找單鏈表中的倒數第 k 個結點

3.2.一、代碼思路

  • 查找單鏈表中的倒數第k個結點 【新浪面試題】
    • 首先,獲取整個鏈表中元素的個數 size
    • 在使用 for 循環定位至倒數第 index(形參) 個節點,返回便可
    • for 循環的條件應如何肯定?for (int i = 0; i < x; i++) 中 x 的值應是多少?咱們須要定位至倒數第 index 個節點,在 for 循環以前,咱們已經定位置首節點,還需再走 (size - index ) 步,定位至倒數第 index 個節點
    • 舉例說明:鏈表中一共有 4 個元素,想要定位至倒數第 2 個節點,那麼須要在首節點以後走兩步,到達倒數第 2 個節點

3.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=&#x5B8B;&#x6C5F;, nickName=&#x53CA;&#x65F6;&#x96E8;]
HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickName=&#x7389;&#x9E92;&#x9E9F;]
HeroNode [no=3, name=&#x5434;&#x7528;, nickName=&#x667A;&#x591A;&#x661F;]
HeroNode [no=4, name=&#x6797;&#x51B2;, nickName=&#x8C79;&#x5B50;&#x5934;]
res=HeroNode [no=3, name=&#x5434;&#x7528;, nickName=&#x667A;&#x591A;&#x661F;]

3.三、單鏈表的反轉

3.3.一、代碼思路

  • 單鏈表的反轉【騰訊面試題,有點難度】
    • 定義一個新的頭結點 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.二、代碼實現

  • 單鏈表的反轉
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();
}
  • 程序運行結果
&#x539F;&#x6765;&#x94FE;&#x8868;&#x7684;&#x60C5;&#x51B5;~~
HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickName=&#x53CA;&#x65F6;&#x96E8;]
HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickName=&#x7389;&#x9E92;&#x9E9F;]
HeroNode [no=3, name=&#x5434;&#x7528;, nickName=&#x667A;&#x591A;&#x661F;]
HeroNode [no=4, name=&#x6797;&#x51B2;, nickName=&#x8C79;&#x5B50;&#x5934;]
&#x53CD;&#x8F6C;&#x5355;&#x94FE;&#x8868;~~
HeroNode [no=4, name=&#x6797;&#x51B2;, nickName=&#x8C79;&#x5B50;&#x5934;]
HeroNode [no=3, name=&#x5434;&#x7528;, nickName=&#x667A;&#x591A;&#x661F;]
HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickName=&#x7389;&#x9E92;&#x9E9F;]
HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickName=&#x53CA;&#x65F6;&#x96E8;]

3.四、單鏈表的反轉(個人代碼)

3.4.一、代碼思路

  • 單鏈表的反轉【騰訊面試題,有點難度】
    • 原鏈表爲 cur 指向 next ,反轉鏈表不就是把 next 指向 cur 嗎?
    • 因爲 next 指向 cur 時,next 將 丟失其下一節點的地址,因此須要先將 nnext 保存起來
    • next ==null 時鏈表已經反轉完畢,最後將頭結點指向 cur 節點便可

3.4.二、代碼實現

  • 單鏈表的反轉
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();
}
  • 程序運行結果
&#x539F;&#x6765;&#x94FE;&#x8868;&#x7684;&#x60C5;&#x51B5;~~
HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickName=&#x53CA;&#x65F6;&#x96E8;]
HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickName=&#x7389;&#x9E92;&#x9E9F;]
HeroNode [no=3, name=&#x5434;&#x7528;, nickName=&#x667A;&#x591A;&#x661F;]
HeroNode [no=4, name=&#x6797;&#x51B2;, nickName=&#x8C79;&#x5B50;&#x5934;]
&#x53CD;&#x8F6C;&#x5355;&#x94FE;&#x8868;~~
HeroNode [no=4, name=&#x6797;&#x51B2;, nickName=&#x8C79;&#x5B50;&#x5934;]
HeroNode [no=3, name=&#x5434;&#x7528;, nickName=&#x667A;&#x591A;&#x661F;]
HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickName=&#x7389;&#x9E92;&#x9E9F;]
HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickName=&#x53CA;&#x65F6;&#x96E8;]

3.五、從尾到頭打印單鏈表

3.5.一、棧的基本使用

  • 測試代碼
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.二、代碼思路

  • 從尾到頭打印單鏈表 【百度,要求方式1:反向遍歷 。 方式2:Stack棧】
    • 方式一:先將單鏈表進行反轉操做,而後再遍歷輸出,問題: 破壞原鏈表結構,不可取
    • 方式二:遍歷鏈表,去除節點壓入棧中,利用棧 先進後出的特色,實現逆序打印

3.5.三、代碼實現

  • 從尾到頭打印單鏈表
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());
}
  • 程序運行結果
&#x539F;&#x6765;&#x94FE;&#x8868;&#x7684;&#x60C5;&#x51B5;~~
HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickName=&#x53CA;&#x65F6;&#x96E8;]
HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickName=&#x7389;&#x9E92;&#x9E9F;]
HeroNode [no=3, name=&#x5434;&#x7528;, nickName=&#x667A;&#x591A;&#x661F;]
HeroNode [no=4, name=&#x6797;&#x51B2;, nickName=&#x8C79;&#x5B50;&#x5934;]
&#x6D4B;&#x8BD5;&#x9006;&#x5E8F;&#x6253;&#x5370;&#x5355;&#x94FE;&#x8868;, &#x6CA1;&#x6709;&#x6539;&#x53D8;&#x94FE;&#x8868;&#x7684;&#x7ED3;&#x6784;~~
HeroNode [no=4, name=&#x6797;&#x51B2;, nickName=&#x8C79;&#x5B50;&#x5934;]
HeroNode [no=3, name=&#x5434;&#x7528;, nickName=&#x667A;&#x591A;&#x661F;]
HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickName=&#x7389;&#x9E92;&#x9E9F;]
HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickName=&#x53CA;&#x65F6;&#x96E8;]

3.六、合併兩個有序的單鏈表

3.6.一、代碼思路

  • 合併兩個有序的單鏈表,合併以後的鏈表依然有序【課後練習】

3.6.二、代碼實現

3.七、單向鏈表全部代碼

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.一、與單向鏈表的比較

  • 單向鏈表, 查找的方向只能是一個方向, 而雙向鏈表能夠向前或者向後查找
  • 單向鏈表不能自我刪除, 須要靠輔助節點 , 而雙向鏈表, 則能夠 自我刪除, 因此前面咱們單鏈表刪除時節點, 老是找到 temp ,temp 是待刪除節點的 前一個節點(認真體會)

4.二、鏈表節點定義

  • 在單向鏈表節點的基礎上,增長 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.三、鏈表定義

  • 定義整個鏈表的頭結點,做爲鏈表的入口
class DoubleLinkedList {

	private HeroNode head = new HeroNode(0, "", "");

	public HeroNode getHead() {
		return head;
	}

4.四、鏈表遍歷

4.4.一、代碼思路

  • 定義輔助變量 temp ,至關於一個指針,指向 當前節點 ,用於遍歷鏈表
  • 什麼時候中止 while 循環? temp == null :已經遍歷至鏈表尾部

4.4.二、代碼實現

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.五、尾部插入

4.5.一、代碼思路

  • 定義輔助變量 temp ,至關於一個指針,指向 當前節點
  • 什麼時候中止 while 循環? temp.next == null :temp 節點已是鏈表最後一個節點,在 temp 節點以後插入 heroNode 節點便可
  • 如何插入?
    • temp.next 指向新的尾節點 heroNode : temp.next = heroNode;
    • heroNode .pre 指向舊的尾節點 temp : *heroNode.pre = temp;

4.5.二、代碼實現

  • 在鏈表尾部插入節點
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.六、按順序插入

4.6.一、代碼思路

  • 定義輔助變量 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 節點以後】方案

4.6.二、代碼實現

  • 代碼
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.七、修改節點信息

4.7.一、代碼思路

  • 定義輔助變量 temp ,至關於一個指針,指向 當前節點
  • 如何找到指定節點? *temp.no == no

4.7.二、代碼實現

  • 修改指定節點的信息
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.八、刪除節點

4.8.一、代碼思路

  • 定義輔助變量 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.二、代碼實現

  • 刪除指定節點
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.九、雙向鏈表測試

4.9.一、測試代碼

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.二、程序運行結果

&#x53CC;&#x5411;&#x94FE;&#x8868;&#x7684;&#x6D4B;&#x8BD5;
HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickname=&#x53CA;&#x65F6;&#x96E8;]
HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickname=&#x7389;&#x9E92;&#x9E9F;]
HeroNode [no=3, name=&#x5434;&#x7528;, nickname=&#x667A;&#x591A;&#x661F;]
HeroNode [no=5, name=&#x6797;&#x51B2;, nickname=&#x8C79;&#x5B50;&#x5934;]
&#x6309;&#x987A;&#x5E8F;&#x63D2;&#x5165;&#x540E;&#x7684;&#x60C5;&#x51B5;
HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickname=&#x53CA;&#x65F6;&#x96E8;]
HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickname=&#x7389;&#x9E92;&#x9E9F;]
HeroNode [no=3, name=&#x5434;&#x7528;, nickname=&#x667A;&#x591A;&#x661F;]
HeroNode [no=4, name=Heygo, nickname=Heygogo]
HeroNode [no=5, name=&#x6797;&#x51B2;, nickname=&#x8C79;&#x5B50;&#x5934;]
HeroNode [no=6, name=Oneby, nickname=Onebyone]
&#x4FEE;&#x6539;&#x540E;&#x7684;&#x94FE;&#x8868;&#x60C5;&#x51B5;
HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickname=&#x53CA;&#x65F6;&#x96E8;]
HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickname=&#x7389;&#x9E92;&#x9E9F;]
HeroNode [no=3, name=&#x5434;&#x7528;, nickname=&#x667A;&#x591A;&#x661F;]
HeroNode [no=4, name=Heygo, nickname=Heygogo]
HeroNode [no=5, name=&#x516C;&#x5B59;&#x80DC;, nickname=&#x5165;&#x4E91;&#x9F99;]
HeroNode [no=6, name=Oneby, nickname=Onebyone]
&#x5220;&#x9664;&#x540E;&#x7684;&#x94FE;&#x8868;&#x60C5;&#x51B5;~~
HeroNode [no=1, name=&#x5B8B;&#x6C5F;, nickname=&#x53CA;&#x65F6;&#x96E8;]
HeroNode [no=2, name=&#x5362;&#x4FCA;&#x4E49;, nickname=&#x7389;&#x9E92;&#x9E9F;]
HeroNode [no=4, name=Heygo, nickname=Heygogo]
HeroNode [no=5, name=&#x516C;&#x5B59;&#x80DC;, nickname=&#x5165;&#x4E91;&#x9F99;]
HeroNode [no=6, name=Oneby, nickname=Onebyone]

4.十、雙向鏈表全部代碼

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.十一、總結

  • 輔助變量 temp ,至關於一個指針,指向 當前節點
  • 若是定位至當前節點會丟失前一個節點的信息,那麼咱們只能定位至待操做節點的前一個節點:使用 temp.next 進行條件判斷

五、單向環形鏈表

5.一、單向環形鏈表應用場景

  • Josephu 問題爲: 設編號爲 1, 2, ... n 的 n 我的圍坐一圈, 約定編號爲 k(1

5.二、單向環形鏈表圖解

5.三、Josephu 問題

  • 用一個不帶頭結點的循環鏈表來處理 Josephu 問題: 先構成一個有 n 個結點的 單循環鏈表, 而後由 k 結點起從 1 開始計數, 計到 m 時, 對應結點從鏈表中刪除, 而後再從被刪除結點的下一個結點又從 1 開始計數, 直到最後一個結點從鏈表中刪除算法結束。

5.四、環形鏈表的構建與遍歷

5.4.一、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.二、單向循環鏈表的定義

  • first 節點爲單向循環鏈表的 首節點,是真實 存放數據的節點,不是頭結點
class CircleSingleLinkedList {

	private Boy first = null;

5.4.三、構建單向循環鏈表

一、代碼思路
  • 長度爲 1 的狀況:
    • 新建立的 boy 節點便是首節點: first = boy;
    • 自封閉(本身構成環形鏈表): first.setNext(first);
    • 此時 first 節點既是首節點,也是尾節點,輔助指針也指向 first : curBoy = first;
  • 長度不爲 1 的狀況:
    • 將 boy 節點添加至環形鏈表的最後: curBoy.setNext(boy); ,curBoy 節點永遠是環形鏈表的尾節點
    • 構成環形鏈表(最): boy.setNext(first);
    • 輔助指針後移,指向環形鏈表的尾節點: *curBoy = boy;

二、代碼實現
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.四、遍歷單向循環鏈表

一、代碼思路
  • 定義輔助變量 curBoy ,至關於一個指針,指向 當前節點
  • 什麼時候退出 while 循環?當 curBoy 已經指向環形鏈表的尾節點: *curBoy.getNext() == first
二、代碼實現
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.五、解決 Josephu 問題

5.5.一、代碼思路

  • 輔助變量 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.二、代碼實現

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.六、Josephu 問題測試

5.6.一、測試代碼

public static void main(String[] args) {

    CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
    circleSingleLinkedList.addBoy(5);
    circleSingleLinkedList.showBoy();

    circleSingleLinkedList.countBoy(1, 2, 3);
}

5.6.二、程序運行結果

&#x5C0F;&#x5B69;&#x7684;&#x7F16;&#x53F7; 1
&#x5C0F;&#x5B69;&#x7684;&#x7F16;&#x53F7; 2
&#x5C0F;&#x5B69;&#x7684;&#x7F16;&#x53F7; 3
&#x5C0F;&#x5B69;&#x7684;&#x7F16;&#x53F7; 4
&#x5C0F;&#x5B69;&#x7684;&#x7F16;&#x53F7; 5
&#x5C0F;&#x5B69;2&#x51FA;&#x5708;
&#x5C0F;&#x5B69;4&#x51FA;&#x5708;
&#x5C0F;&#x5B69;1&#x51FA;&#x5708;
&#x5C0F;&#x5B69;5&#x51FA;&#x5708;
&#x6700;&#x540E;&#x7559;&#x5728;&#x5708;&#x4E2D;&#x7684;&#x5C0F;&#x5B69;&#x7F16;&#x53F7;3

5.七、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.八、總結

  • 操做單向鏈表:對於插入、刪除操做,只能定位至待操做節點的前一個節點,若是定位至當前節點,那麼其上一個節點的信息便會丟失
相關文章
相關標籤/搜索