我所知道的數據結構之鏈表

做者前言


你們好,我是阿濠,今篇內容跟你們分享的是數據結構之鏈表,很高興分享到segmentfault與你們一塊兒學習交流,初次見面請你們多多關照,一塊兒學習進步.

1、介紹認識什麼是鏈表?

鏈表是有序的列表,某種程度上避免數組的缺陷,即分配數組時須要開闢一串連續的內存空間,但魚和熊掌不可兼得,鏈表也犧牲了一些數組的優勢:鏈表不能經過下標快速查詢,因此考慮是否須要鏈表的時候須要先考慮是否算法是否常常須要查詢和遍歷前端

好比說單鏈表它在內中存儲圖示:面試

圖片.png

介紹小結:
1)鏈表是以節點的方式來存儲,它是以鏈式存儲
2)每一個節點包含data域、next域 指向下一個節點. .
3)如圖發現鏈表的各個節點地址不必定是連續存放,好比150地址下一節點指向110地址
4)鏈表分帶頭節點的鏈表和沒有頭節點的鏈表,根據實際的需求來肯定算法

2、經過應用示例熟悉單鏈表

題目:使用帶head頭單向鏈表實現-水滸108位英雄排行榜管理數據庫

單鏈表的添加

1)完成對英雄人物的增刪改查操做,注: 刪除和修改,查找
2)第一種方法在添加英雄時,不按照排名直接添加到鏈表的尾部
3)第二種方式在添加英雄時,根據排名將英雄插入到指定位置
(若是有這個排名,則添加失敗,並給出提示)segmentfault

第一種方法實現:建立示例圖(添加/建立)顯示思路分析數組

圖片.png

1.先建立一個head頭節點,做用就是表示單鏈表的頭
2.後面咱們每添加一個節點,就直接加入到鏈表的最後數據結構

遍歷的時候
1.經過一個輔助指針,幫助遍歷整個鏈表ide

英雄代碼編寫以下:學習

//定義英雄節點,每個herNode對象就是一個節點
public 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+"]";
    }
}

鏈表管理英雄節點編寫以下:測試

//鏈表管理英雄節點
public class SingleLinkedList {

    //先初始化一一個頭節點,頭節點不要動,不存放具體的數據
    private HeroNode head = new HeroNode(0, "", "");

    //添加節點到單項鍊表裏
    public void add(HeroNode heroNode) {
        //思路以下:
        //1.找到當前鏈表的最後節點
        //2.找到最後節點的next指向新添加的節點
        //3.使用輔助指向head,而後遍歷找到最後節點
        HeroNode temp=head;
        while(true){
            //找到節點的next指針爲空時退出
            if(temp.next == null){
                break;
            }
            //當前節點的next!=null 則日後移
            temp=temp.next;
        }
        //break結束循環則表明找到
        temp.next=heroNode;
    }
    //顯示鏈表[遍歷]
    public void list() {
        //判斷鏈表是否爲空
        if(head.next == null) {
            System.out.println("鏈表爲空");
            return;
        }
        //分析思路提到頭節點不能動,須要使用臨時遍歷輔助
        HeroNode temp=head.next;
        while (true){
            //若是臨時遍歷的下一節點指向爲空
            //則表明後面沒有值則結束循環
            if(temp == null){
                break;
            }
            //不然則輸出信息,並指向下一個節點繼續
            //由於重寫了toString方法直接能夠輸出temp
            System.out.println(temp);
            //將temp指向下一個節點
            temp=temp.next;
        }
    }
}

Demo測試數據代碼編寫以下:

//進行建立節點數據進行測試
HeroNode heroOne=new HeroNode(1,"宋江","及時雨");
HeroNode heroTwo=new HeroNode(2,"盧俊義","玉麒麟");
HeroNode heroThree=new HeroNode(3,"吳用","智多星");
HeroNode heroFour=new HeroNode(4,"林沖","豹子頭");

//建立鏈表
SingleLinkedList singleLinkedList = new SingleLinkedList();
//加入英雄節點
singleLinkedList.add(heroOne);
singleLinkedList.add(heroTwo);
singleLinkedList.add(heroThree);
singleLinkedList.add(heroFour);
//循環遍歷鏈表
singleLinkedList.list();

結果以下:
HeroNode [ no=1],name=[宋江],nickname=[及時雨]
HeroNode [ no=2],name=[盧俊義],nickname=[玉麒麟]
HeroNode [ no=3],name=[吳用],nickname=[智多星]
HeroNode [ no=4],name=[林沖],nickname=[豹子頭]

此時顯示的結果順序是正確的,若我是修改一下代碼,此時顯示順序是正確的嘛?

singleLinkedList.add(heroOne);
singleLinkedList.add(heroFour);
singleLinkedList.add(heroThree);
singleLinkedList.add(heroTwo);

運行結果以下:
HeroNode [ no=1],name=[宋江],nickname=[及時雨]
HeroNode [ no=4],name=[林沖],nickname=[豹子頭]
HeroNode [ no=3],name=[吳用],nickname=[智多星]
HeroNode [ no=2],name=[盧俊義],nickname=[玉麒麟]

第一個使用是松江,第二個就變成林沖了,爲何?
由於是按照添加的順序來顯示的,說明沒有考慮編號排序

那麼第一種方法添加到鏈表尾部已完成,如今按照第二種方法來完成添加

第二種方法實現:建立示例圖(添加/建立)顯示思路分析

圖片.png

須要按照編號的順序添加
1.首先找到新添加的節點的位置,經過輔助變量(指針),須要遍歷搞定
2.新的節點.next = 原來 temp.next
3.將temp.next=新的節點

(好比說添加新節點數據3 它是存放在數據2後的,即節點數據2的next指針,本來指向節點數據4,如今變成新節點數據3的next節點指向節點數據4,而節點數據2的next節點變成指向新節點數據3)

鏈表管理英雄節點添加新方法代碼編寫以下:

//第二種方式在添加英雄時,根據排名將英雄插入到指定位置
//(若是有這個排名,則添加失敗,並給出提示)
//思路判斷
//1.判斷是否temp遍歷已經是鏈表最後
//2.找到插入節點的合適位置
//3.判斷插入節點是否已有對應的節點
public void addBy0rder (HeroNode heroNode) {
    //由於頭節點不能動,所以咱們仍然經過一個輔助指針(變量)來幫助找到添加的位置
    //由於單鏈表,由於咱們找的temp是位於添加位置的前一個節點,不然插入不了
    HeroNode temp = head;
    boolean flag = false; // 標誌添加的編號是否存在,默認爲false
    while (true) {
        if (temp.next == null) {
            //說明temp已經在鏈表的最後直接添加
            break;
        }
        if (temp.next.no > heroNode.no) {
            //temp的下一個節點的編號>添加節點的編號
            //表明位置找到,就在temp的後面插入
            //好比說節點2 的next指向 節點4
            //此時插入節點3 那麼temp指向節點,此時temp的next是節點4
            //節點4的編號>節點3 表明節點3插入在節點2以後,節點4 以前
            break;
        } else if (temp.next.no == heroNode.no) {
            //說明但願添加的heroNode的編號已然存在
            flag = true;
            break;
        }
        //若三個條件都沒有知足則進行日後移
        temp = temp.next;
    }
    //判斷flag的值,若爲true則表明節點已存在
    if (flag){
        System.out.printf("插入的該英雄編號 %d 已存在,不能再次添加了\n",heroNode.no);
    }else{
        //插入到鏈表中,temp的後面
        //新的節點下一節點指向本來temp的下一節點
        heroNode.next = temp.next;
        //temp的下一節點指向新增的節點
        temp.next  = heroNode;
    }

}

如今咱們再次添加測試數據看看可否成功呢?

//加入按照編號的順序
singleLinkedList.addBy0rder(heroOne);
singleLinkedList.addBy0rder(heroFour);
singleLinkedList.addBy0rder(heroThree);
singleLinkedList.addBy0rder(heroTwo);

運行結果以下:
HeroNode [ no=1],name=[宋江],nickname=[及時雨]
HeroNode [ no=2],name=[盧俊義],nickname=[玉麒麟]
HeroNode [ no=3],name=[吳用],nickname=[智多星]
HeroNode [ no=4],name=[林沖],nickname=[豹子頭]

若此時我再添加一次豹子頭林沖呢?

singleLinkedList.addBy0rder(heroFour);

運行結果以下:
插入的該英雄編號 4 已存在,不能再次添加了

根據第二方法的代碼實現,咱們就能在內存中就將順序排好,這是比數據庫中還快

單鏈表的修改

假如咱們給出一個新的節點,完成對原有節點進行名稱、暱稱修改,那麼如何操做呢?

思路分析:
1.根據編號修改
2.編號不變,名稱,暱稱可變

鏈表管理英雄節點添加新方法代碼編寫以下:

//修改節點的信息,根據no編號來修改,即no編號不能改。
//說明根據newHeroNode的no來修改便可
public void update(HeroNode newHeroNode) {
    //判斷是否空
    //由於head是沒有數據的
    if (head.next == null) {
        System.out.println("鏈表爲空~");
        return;
    }
    //找到須要修改的節點,根據no編號
    //定義一個輔助變量
    HeroNode temp = head.next;
    boolean flag = false;
    while(true){
        if (temp == null) {
            break; //已經遍歷完鏈表
        }
        //根據temp的值
        if(temp.no == newHeroNode.no) {
            //找到
            flag = true;
            break;
        }//沒有找到日後移
        temp = temp. next;
    }
    if(flag){
        //原節點進行修改信息
        temp.name=newHeroNode.name;
        temp.nickname=newHeroNode.nickname;
    }else{
        //沒有找到的時候仍然爲false
        System.out.printf("沒有找到編號 %d 的節點,不能修改\n",newHeroNode.no) ;
    }
}

如今咱們測試數據看看可否成功呢?好比將林沖修改成小白,豹子頭改成小白龍

HeroNode hero=new HeroNode(4,"小白","小白龍");
singleLinkedList.update(hero);
//顯示數據
singleLinkedList.list();

運行結果以下:
修改信息後的數據--------------
HeroNode [ no=1],name=[宋江],nickname=[及時雨]
HeroNode [ no=2],name=[盧俊義],nickname=[玉麒麟]
HeroNode [ no=3],name=[吳用],nickname=[智多星]
HeroNode [ no=4],name=[小白],nickname=[小白龍]

單鏈表的刪除

圖片.png

假設我如今有節點數據如上,若我想刪除數據4,該怎麼操做嘛?

思路分析:
1.仍然須要temp輔助節點,找到須要刪除節點的前一個節點temp
2.修改temp.next=temp.next.next

(好比說節點2的next節點是節點4 修改成節點2的next節點的next節點,便是節點4的next節點)

鏈表管理英雄節點添加新方法代碼編寫以下:

//思路
//1. head 不能動,所以咱們須要-個temp輔助節點找到待刪除節點的前一個節點
//2.說明咱們在比較時,是temp .next.no和須要刪除的節點的no比較
public void del(int no) {
    HeroNode temp = head;
    boolean flag = false; //標誌是否找到待刪除節點的
    while(true) {
        if(temp.next == null ) { //已經到鏈表 的最後
            break;
        }
        if(temp.next.no == no) {
            //找到的待刪除節點的前一個節點temp
            flag = true;
            break;
        }
        temp = temp.next; //temp後移遍歷
    }
    //判斷flag
    if(flag) { //找到 .
        //能夠刪除
        temp.next = temp. next. next;
    }else {
        System .out .printf("要刪除的%d節點不存在",no);
    }
}

如今咱們測試數據看看可否成功呢?好比將宋江刪除

//刪除一個節點
singleLinkedList.del(1);
System .out .println("刪除後的鏈表狀況--------------");
singleLinkedList.list();

運行結果以下:
HeroNode [ no=1],name=[宋江],nickname=[及時雨]
HeroNode [ no=2],name=[盧俊義],nickname=[玉麒麟]
HeroNode [ no=3],name=[吳用],nickname=[智多星]
HeroNode [ no=4],name=[小白],nickname=[小白龍]
刪除後的鏈表狀況--------------
HeroNode [ no=2],name=[盧俊義],nickname=[玉麒麟]
HeroNode [ no=3],name=[吳用],nickname=[智多星]
HeroNode [ no=4],name=[小白],nickname=[小白龍]

單鏈表面試題(新浪、百度、騰訊)

1)求單鏈表中節點的個數
2)查找單鏈表中的倒數第k個結點[ 新浪面試題]
3)單鏈表的反轉[騰訊面試題]
4)從尾到頭打印單鏈表[百度,要求方式1:反向遍歷。方式2: Stack棧]
5)合併兩個有序的單鏈表,合併以後的鏈表依然有序

題目一:

思路分析:
獲取到單鏈表的節點的個數(若是是帶頭結點的鏈表,需求不統計頭節點)

/**
 * @param  head 鏈表的頭節點
 * @return 返回的就是有效節點的個數
 */
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;
}

題目二:

思路分析:
1.編寫一個方法,接收head節點,同時接收一個index
2.index 表示是倒數第index個節點
3.先把鏈表從頭至尾遍歷,獲得鏈表的總的長度getLength
4.獲得size後,咱們從鏈表的第-個開始遍歷(size-index)個,就能夠獲得
5.若是找到了,則返回該節點,不然返回nu111

//查找單鏈表中的倒數第k個結點[新浪面試題]
public static HeroNode findLastIndexNode(HeroNode head, int index) {

    //判斷若是鏈表爲空,返回null
    if (head.next == null) {
        return null;//沒有找到
    }
    //第一個遍歷獲得鏈表的長度(節點個數)
    int size = getLength(head);

    //第二次遍歷size-index 位置,就是咱們倒數的第K個節點
    //先作index的校驗
    // 不能說一共才5個,你要求找倒數第六個數或者負數
    if (index <= 0 || index > size) {
        return null; //沒有找到
    }
    //定義給輔助變量,for循環定位到倒數的index
    HeroNode cur = head.next;
    //好比說有效數據爲三個 找倒數第一個
    // 3-1 = 2  二次就找到了
    for (int i = 0; i < size - index; i++) {
        cur = cur.next;
    } 
    return cur;
}

題目三:

圖片.png

思路分析:
1.先定義一個節點reverseHead =new HeroNode();
2.從頭至尾遍歷原來的鏈表,每遍歷一個節點,就將其取出,並放在新的鏈表reverseHead的最前端.
3.原來的鏈表的head.next =reverseHead.next

圖片.png

圖片.png

圖片.png

//將單鏈表反轉
public static void reversetList(HeroNode head) {

    //若是當前鏈表爲空,或者只有一個節點,無需反轉,直接返回
    //鏈表裏只有一個節點時,它的next節點也是空的
    if (head.next == null || head.next.next == null) {
        return;
    }
    //定義一個輔助的指針(變量),幫助咱們遍歷原來的鏈表
    HeroNode cur = head.next;
    HeroNode next = null;//指向當前節點[cur]的下一個節點.
    HeroNode reverseHead = new HeroNode(0,"","");
    //遍歷原來的鏈表,每遍歷一個節點,就將其取出,並放在新的鏈表reverseHead的最前端
    //動腦筋
    while(cur != null) {
        next = cur.next;//先暫時保存當前節點的下一個節點,由於後面須要使用
        cur.next = reverseHead.next;//將cur的下一個節點指向新的鏈表的最前端
        reverseHead.next = cur; //將cur 鏈接到新的鏈表上
        cur = next;//icur後移
    }
    //將head. next指向reverseHead.next, 實現單鏈表的反轉
    head.next = reverseHead. next;
}

題目四:

圖片.png

思路分析:

要求就是逆序打印單鏈表.
1.方式1:先將單鏈表進行反轉操做,而後再遍歷便可,這樣的作的問題是會破壞原來的單鏈表的結構,不建議
2.方式2:能夠利用這個數據結構,將各個節點壓入到棧中,而後利用棧的先進後出的特色,就實現了逆序打印的效果

圖片.png

//演示棧Stack的基本使用
public class TestStack {
    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());//pop就是將棧頂的數據取出
         }
    }   
}

運行結果以下:
smith
tom
jack

那麼咱們發現棧是符合這個要求的,可使用棧進行逆序打印鏈表

//方式2:鏈表結構順序沒有改變,使用逆序打印
//能夠利用棧這個數據結構,將各個節點壓入到棧中,而後利用棧的先進後出的特色,就實現了逆序打印的效果
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; //cur後移,這樣就能夠壓入下一個節點
    }
    //將棧中的節點進行打印,pop出棧
    while (stack.size() > 0) {
        System.out. println(stack.pop());
    }
}

3、經過應用示例熟悉雙鏈表

剛剛經過示例認識到單鏈表,可是單鏈表相比雙鏈表也是有些缺點的
1)單向鏈表:查找的方向只能是一個方向,而雙向鏈表能夠向前或者向後查找
2)單向鏈表不能自我刪除,須要靠輔助節點,而雙向鏈表,則能夠自我刪除,因此前面咱們單鏈表刪除時節點,老是須要找到temp,而temp是待刪除節點的的前一個節點

3)示意圖理解區別
圖片.png

分析雙向鏈表的遍歷,添加,修改,刪除的操做思路
1>:遍歷方法和單鏈表同樣,只是能夠向前,也能夠向後查找

2>:添加:(默認添加)到雙向鏈表的最後,須要先找到找到雙向鏈表的最後這個節點
2.1>:最後節點temp.next = 新節點 newHeroNode
2.2>:新節點 newHeroNode.pre = 最後節點 temp

3>:修改思路原理和單鏈表同樣

4>: 刪除:由於是雙向鏈表 能夠實現自我刪除某個節點,直接找到須要刪除的節點temp
4.1>:temp.pre.next=temp.next;
4.2>:temp.next.pre=temp.pre;

題目:使用帶head頭單向鏈表實現-水滸108位英雄排行榜管理

英雄代碼編寫以下:

//定義英雄節點,每個herNode對象就是一個節點
public class HeroNode2 {

    public int no;
    public String name;
    public String nickname;
    public HeroNode2 next; //指向下一個節點 默認爲null
    public HeroNode2 pre;  //指向上一個節   默認爲null
    //構造器
    public HeroNode2(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+"]";
    }
}

雙鏈表管理英雄節點代碼編寫以下:

package com.bdqn.it.test;

public class DouobleLikedList {

    //先初始化一一個頭節點,頭節點不要動,不存放具體的數據
    private HeroNode2 head = new HeroNode2(0, "", "");

    //顯示鏈表[遍歷]
    public void list() {
        //判斷鏈表是否爲空
        if (head.next == null) {
            System.out.println("鏈表爲空");
            return;
        }
        //分析思路提到頭節點不能動,須要使用臨時遍歷輔助
        HeroNode2 temp = head.next;
        while (true) {
            //若是臨時遍歷的下一節點指向爲空
            //則表明後面沒有值則結束循環
            if (temp == null) {
                break;
            }
            //不然則輸出信息,並指向下一個節點繼續
            //由於重寫了toString方法直接能夠輸出temp
            System.out.println(temp);
            //將temp指向下一個節點
            temp = temp.next;
        }
    }
    //添加節點
    public void add(HeroNode2 heroNode) {

        HeroNode2 temp = head;
        while (true) {
            //找到節點的next指針爲空時退出
            if (temp.next == null) {
                break;
            }
            //當前節點的next!=null 則日後移
            temp = temp.next;
        }
        //break結束循環則表明找到
        temp.next = heroNode;
        //新增節點的前一個節點爲當前最後一個節點
        heroNode.pre=temp;
    }

    //修改節點的信息,根據no編號來修改,即no編號不能改。
    //說明根據newHeroNode的no來修改便可
    public void update(HeroNode2 newHeroNode) {
        //判斷是否空
        //由於head是沒有數據的
        if (head.next == null) {
            System.out.println("鏈表爲空~");
            return;
        }
        //找到須要修改的節點,根據no編號
        //定義一個輔助變量
        HeroNode2 temp = head.next;
        boolean flag = false;
        while (true) {
            if (temp == null) {
                break; //已經遍歷完鏈表
            }
            //根據temp的值
            if (temp.no == newHeroNode.no) {
                //找到
                flag = true;
                break;
            }//沒有找到日後移
            temp = temp.next;
        }
        if (flag) {
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        } else {
            //沒有找到的時候仍然爲false
            System.out.printf("沒有找到編號 %d 的節點,不能修改\n", newHeroNode.no);
        }
    }

    //從雙向鏈表中刪除節點
    //思路
    //1.對於雙向鏈表,咱們能夠直接找到要刪除的這個節點
    //2.找到後,自我刪除便可
    public void del(int no) {


        if (head.next == null) { //空鏈表
            System.out.println("鏈表爲空,沒法刪除!");
        }
        boolean flag = false; //標誌是否找到待刪除節點的
        //直接指向刪除的節點
        HeroNode2 temp = head.next;
        while (true) {
            if (temp == null) { //空鏈表
               //已經到了鏈表最後的後一個
                break;
            }
            if (temp.no == no) {
                //找到的待刪除節點的前一個節點temp
                flag = true;
                break;
            }
            temp = temp.next; //temp後移遍歷
        }
        //判斷flag
        if (flag) { //找到 .
            //能夠刪除
            temp.pre.next = temp.next;
            if(temp.next!=null){
                //若刪除的節點是最後,則會出現空指針異常
                temp.next.pre=temp.pre;
            }
        } else {
            System.out.printf("要刪除的%d節點不存在", no);
        }
    }

}

如今咱們添加測試數據看看可否操做成功?

//建立節點作數據測試
HeroNode2 heroOne=new HeroNode2(1,"宋江","及時雨");
HeroNode2 heroTwo=new HeroNode2(2,"盧俊義","玉麒麟");
HeroNode2 heroThree=new HeroNode2(3,"吳用","智多星");
HeroNode2 heroFour=new HeroNode2(4,"林沖","豹子頭");


//建立鏈表
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();

//加入
doubleLinkedList.add(heroOne);
doubleLinkedList.add(heroTwo);
doubleLinkedList.add(heroThree);
doubleLinkedList.add(heroFour);

System.out.println("當前雙鏈表數據-------------");
doubleLinkedList.list();

System.out.println("修改英雄4,當前鏈表狀況--------------");
HeroNode2 hero=new HeroNode2(4,"小白","小白龍");
doubleLinkedList.update(hero);
doubleLinkedList.list();


//刪除一個節點
doubleLinkedList.del(1);
System .out .println("刪除英雄1,當前鏈表狀況--------------");
doubleLinkedList.list();

運行結果以下:

當前雙鏈表數據-------------
HeroNode [ no=1],name=[宋江],nickname=[及時雨]
HeroNode [ no=2],name=[盧俊義],nickname=[玉麒麟]
HeroNode [ no=3],name=[吳用],nickname=[智多星]
HeroNode [ no=4],name=[林沖],nickname=[豹子頭]
修改英雄4,當前鏈表狀況--------------

HeroNode [ no=1],name=[宋江],nickname=[及時雨]
HeroNode [ no=2],name=[盧俊義],nickname=[玉麒麟]
HeroNode [ no=3],name=[吳用],nickname=[智多星]
HeroNode [ no=4],name=[小白],nickname=[小白龍]
刪除英雄1,當前鏈表狀況--------------

HeroNode [ no=2],name=[盧俊義],nickname=[玉麒麟]
HeroNode [ no=3],name=[吳用],nickname=[智多星]
HeroNode [ no=4],name=[小白],nickname=[小白龍]

4、經過應用示例熟悉單向環形鏈表

單向環形鏈表介紹

圖片.png

Josephu約瑟夫環問題

圖片.png

Josephu問題爲:
設編號爲1,2 ... n的n我的圍坐一圈,約定編號爲k (1<=k<=n)的人從1開始報數數到m的那我的出列,它的下一位又從1開始報數,數到m的那我的又出列,依次類推,直到全部人出列爲止

示意圖分析思路:

圖片.png

圖片.png

由此產生一個出隊編號的序列。
提示:用一個不帶頭節點循環鏈表來處理Josephu問題

1.先構成一個有n個節點的單循環鏈表,而後由k結點從1開始計數計到m時對應節點從鏈表中刪除
2.再從被刪除節點的下一個節點又從1開始計數,直到最後一個節點從鏈表中刪除結束。

構建一個單向的環形鏈表思路
1.先建立第一個節點,讓first指向該節點,並造成環形.
2.後面當咱們每建立一個新的節點,就把該節點,加入到已有的環形鏈表中便可.

圖片.png

圖片.png

遍歷環形鏈表
1.先讓一個輔助指針(變量)curBoy,指向first節點
2.而後經過一個while循環遍歷該環形鏈表便可curBoy.next

圖片.png

丟手絹小孩代碼編寫以下:

//建立- -個Boy類,表示一個節點
public class Boy {
    private int no;//編號
    private Boy next; //指向下一個 節點,默認null

    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;
    }
}

單向環形鏈表管理節點代碼編寫以下:

//建立管理環形的單向鏈表
class CircleSingleLinkedList {

    //建立一個first節點,當前沒有編號
    private Boy first = null ;

    //添加小孩節點,構建成一個環形的鏈表
    public void createLinkedList(int nums){
        //num 作一個數據校驗
        if(nums < -1){
            System.out.println("num的值不正確");
            return;
        }
        Boy curBoy = null; //輔助指針,幫助構建環形鏈表
        //使用for來建立咱們的環形鏈表
        for(int i = 1; i <= nums; i++) {
            //根據編號,建立小孩節點
            Boy boy = new Boy(i);
            //若是是第一個小孩
            if (i == 1) {
                first = boy;
                first.setNext(first); //構成環
                curBoy = first; //讓curBoy指向第一個小孩
            } else {
                curBoy.setNext(boy);//當前boy指向新的小孩
                boy.setNext(first);//新小孩指向第一個小孩
                curBoy = boy;//當前boy=新小孩
            }
        }
    }
    //遍歷當前的環形鏈表
    public void showBoy() {
        //判斷鏈表是否爲空
        if(first == null) {
            System. out. println("沒有任何小孩~");
            return;
        }
         //由於first不能動,所以咱們仍然使用一個輔助指針完成遍歷
        Boy curBoy = first;
        while(true) {
            System.out. printf("小孩的編號%d \n", curBoy. getNo());
            //說明已經遍歷完
            if(curBoy. getNext() == first) {畢
                break;
            }
            //curBoy後移
            curBoy = curBoy. getNext();
        }
    }
}

先讓咱們完成第一步,看看有多少個小孩參加玩丟手絹的遊戲?

//測試-把看看構建環形鏈表,和遍歷是否ok
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.createLinkedList(5);//加入5個小孩玩遊戲
circleSingleLinkedList.showBoy();

運行結果以下:

小孩的編號1 
小孩的編號2 
小孩的編號3 
小孩的編號4 
小孩的編號5

那麼如何按照報數出序列呢?

圖片.png

圖片.png

圖片.png

代碼實現根據用戶輸入計算出圈順序

//根據用戶的輸入,計算出小孩出圈的順序
/**
 * @param startNo 表示從第幾個小孩開始數數
 * @param countNum 表示數幾下
 * @param nums 表示最初有多少小孩在圈中
 */
public void countBoy(int startNo, int countNum, int nums) {
    //先對數據進行校驗
    //校驗環形鏈表是否有數據,空鏈表沒法出
    //校驗從第幾個開始小於1,從-1個小孩開始沒法出
    //校驗從第幾個開始大於環形鏈表裏的值,從5個小孩裏第6個沒法出
    if (first == null || startNo < 1 || startNo > nums) {
        System.out.println("參數輸入有誤,請從新輸入");
        return;
    }
    //建立要給輔助指針,幫助完成小孩出圈
    Boy helper = first;
    //需求建立-個輔助指針(變量) helper ,事先應該指向環形鏈表的最後這個節點
    while (true) {
        //說明he1per指向最後小孩節點
        if (helper.getNext() == first) {
            break;
        }
        //後移不斷找到最後節點
        helper = helper.getNext();
    }
    //報數前先讓first和helper 移動 startNo - 1 次
    //若從二個小孩開始此時first 指向第一個小孩
    //則須要移動 2 - 1 次 將first指向要報數的小孩
    for (int j = 0; j < startNo - 1; j++) {
        first = first.getNext();
        helper = helper.getNext();
    }
    //此時first 指向報數的小孩
    while(true) {
        //說明圈中只留下最後一個小孩
        if(helper == first) {
            break;
        }
        //好比說每一個三 就出圈 此時first 指向的是報數的小孩 則表明=1
        //只須要 3 - 1 則找到要出圈的小孩 而後出圈直到只有一個節點
        //讓 first 和 helper 指針同時的移動 countNum - 1
        for(int j=0;j<countNum-1;j++){
            first = first.getNext();
            helper = helper .getNext();
        }
        //這時first指向的節點,就是要出圈的小孩節點
        System.out.printf("小孩%d出圈 \n",first.getNo());
        //將小孩出圈
        first=first.getNext();
        helper.setNext(first);
    }
    System.out.printf("最後留在圈中的小孩 %d \n",first.getNo());
}

來看看剛剛添加五個小孩的出圈順序吧

//測試一把小孩出圈是否正確
//一共有五個小孩
//出圈規則:從第一個小孩開始數二下出圈
circleSingleLinkedList.countBoy(1, 2, 5);


運行結果以下:
小孩2出圈 
小孩4出圈 
小孩1出圈 
小孩5出圈 
最後留在圈中的小孩 3

圖片.png

相關文章
相關標籤/搜索