拜託,別再問我什麼是鏈表了!!!

鏈表(Linked List)

上一篇文章分析了List源碼,這一篇文章本來要分析Set的源碼,發現Set的底層是使用HashMap實現的,於是準備先分析Map,但是發現map的實現類的底層數據結構是數組,鏈表,紅黑樹,撤了撤了,先講一波數據結構,數組大家都比較熟悉,所以我們主要講鏈表和樹結構,這一篇文章先學習鏈表。

鏈表的定義

鏈表是以節點(node)存儲的鏈式存儲結構,一個node包含一個data域(存放數據)和一個next域(存放下一個node的指針),鏈表的各個節點不一定是連續的,它可以分爲帶頭結點和不帶頭結點。頭結點僅包含next域。

下面我們主要講解單向鏈表雙向鏈表單向循環鏈表(約瑟夫問題)

1、單向鏈表

單向鏈表的特點是鏈接方向是單向的,對鏈表的訪問要從頭部開始進行順序讀取,鏈表中的每個結點僅有一個指向下一個結點的指針。

1.1構造鏈表

  • 創建節點類,包含了data和next,next指向下一個節點對象

class PersonNode {
public int number;//每個結點的序號,方便我們進行排序和查找
public String name;//結點的名稱
public String nickName;//結點的別名
public PersonNode next;//指向下一結點的指針

public PersonNode(int number, String name, String nickName) {
this.number = number;
this.name = name;
this.nickName = nickName;
}

@Override
public String toString() {
return "PersonNode{" +
      "number=" + number +
      ", name='" + name + '\'' +
      ", nickName='" + nickName + '\'' +
      '}';
}
}
  • 創建單向鏈表類,裏面包含了增刪改查的方法

class SingleLinkedList {
    //創建頭結點
    private PersonNode headNode = new PersonNode(0, "", "");

    public PersonNode getHeadNode() {
        return headNode;
    }
    
//添加節點的單向鏈表

    /**
     * 在鏈表尾部插入
     */

    public void addNode(PersonNode personNode) {
      ......
    }

    /**
     * 按照number順序插入
     */
    public void addByOrder(PersonNode personNode) {
       ......
    }

    /**
     * 修改節點信息
     */

    public void update(PersonNode personNode) {
      ......
    }

    /**
     * 刪除某一個節點
     * 先根據number查找,在刪除
     */
    public void delete(PersonNode personNode) {
        ......
    }

    /**
     * 遍歷節點
     */
    //顯示鏈表中的有效節點
    public void show(PersonNode personNode) {
       ......
    }

1.2單向鏈表增刪改查的實現

我們將會以圖解的方式講解,

  • 在鏈表中增加一個節點


確定要插入的節點位置,循環遍歷到該位置節點的上一個節點。

S->next=P->next
P->next=S

代碼實現

  /**
     * 按照number順序插入
     */
    public void addByOrder(PersonNode personNode) {
        //定義插入標識符
        boolean flag = false;
        //定義替代變量
        PersonNode temp = headNode;
        while (true) {
            if (temp.next == null) {
                break;
            }
            //滿足條件
            if (temp.next.number > personNode.number) {
                break;
            } else if (temp.next.number == personNode.number) {
                flag = true;
                break;
            }
            //後移
            temp = temp.next;
        }
        //插入節點
        if (flag) {
            System.out.println("節點已經存在~~~~");
        } else {
            personNode.next = temp.next;
            temp.next = personNode;
        }
    }
  • 在鏈表中刪除一個節點

確定刪除的位置,循環遍歷到該位置節點的上一個節點

P->next=p->next->next

代碼實現

  /**
     * 刪除某一個節點
     * 先根據number查找,在刪除
     */
    public void delete(PersonNode personNode) {
        boolean flag = false;
        PersonNode temp = headNode;
        while (true) {
            if (temp.next == null) {
                System.out.println("鏈表爲空");
                break;
            }
            if (temp.next.number == personNode.number) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            temp.next = temp.next.next;
        } else {
            System.out.println("找不到該節點的信息");
        }

    }
  • 修改鏈表中的一個節點信息

確定要修改節點的位置,先遍歷找到該節點,修改該節點的信息。

  /**
     * 修改節點信息
     */

    public void update(PersonNode personNode) {
        boolean flag = false;
        PersonNode temp = headNode;
        //根據編號找到要修改的節點
        while (true) {
            if (temp.next == null) {
                System.out.println("鏈表爲空");
                break;
            }
            if (temp.next.number == personNode.number) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        //修改節點信息
        if (flag) {
            temp.next.nickName = personNode.nickName;
            temp.next.name = personNode.name;
        } else {
            System.out.println("找不到該節點的信息");
        }

    }
  • 遍歷鏈表

/**
  * 遍歷節點
  */
    //顯示鏈表中的有效節點
    public void show(PersonNode personNode) {
        if (personNode.next == null) {
            System.out.println("該鏈表爲空~~~");
        }
        PersonNode temp = personNode;
        while (true) {
            if (temp.next == null) {
                break;
            }
            temp = temp.next;
            System.out.println(temp);
        }
    }

2、雙向鏈表

雙向鏈表的定義

雙向鏈表中的數據結點有兩個指針,分別指向直接後繼和直接前驅,所以從雙向鏈表中的任一結點開始,都可以很方便的訪問它的前驅結點和後繼結點。

2.1構造雙向鏈表

它和單向鏈表很相似,僅僅是增加了一個直接前驅結點

public int number;
public String name;
public String nickName;
public PersonNode next;
public PersonNode pre;

2.2雙向鏈表增刪改查的實現

  • 雙向鏈表的遍歷

雙向鏈表的遍歷與單向鏈表相同,可以按照兩個方向遍歷,在這裏就不在代碼實現了。

  • 雙向鏈表的添加

1、在末尾添加

在雙向鏈表的末尾添加C節點(定位到節點B)

B->next=C
C->pre=B

代碼實現

 /**
   * 1、在末尾添加節點
   */
    public void  add(PersonNode2 personNode2){
        PersonNode2 temp=headNode;

        while (true){
            if(temp.next==null){
                break;
            }
            temp=temp.next;
        }
        //構成雙向鏈表
        temp.next=personNode2;
        personNode2.pre=temp;
    }

2、在兩節點間添加

在節點B和節點C間添加D(程序定位到節點D),

順序:先搞定D的前驅和後繼節點,再搞定C的節點的前驅和B的後繼節點。

D->pre=B
D->next=C
C->pre=D
B->next=D

代碼實現

/**
*在兩節間插入新的結點
*/
public void addByOrder(PersonNode2 personNode2){
        PersonNode2 temp=headNode;
        boolean flag=false;
        boolean flag2=false;
        boolean flag3=false;
        while (true){
            if (temp.next==null){
                flag2=true;
                break;
            }
            //加入兩個節點間的條件
            if(temp.next.number>personNode2.number){
                    flag3=true;
                    break;
            }
            if(temp.next.number==personNode2.number){
                flag=true;
                break;
            }

            temp=temp.next;
        }
        if(flag){
            System.out.println("相同的節點已存在");
        }
        if(flag2){
            //在末尾添加構成雙向鏈表
            temp.next=personNode2;
            personNode2.pre=temp;
        }
        if(flag3){
            //在兩個有效節點間添加
            personNode2.pre= temp;
            personNode2.next=temp.next;
            temp.next.pre=personNode2;
            temp.next=personNode2;
        }
    }
  • 雙向鏈表的修改

同單鏈表修改相同

  • 雙向鏈表的刪除

在兩節點間刪除(定位到節點S(D))

S->pre->next=S->next
S->next->pre=S->pre

代碼實現

 /**
  * 刪除節點
  */
    public  void delete(PersonNode2 personNode2){
        PersonNode2 temp=headNode;
        boolean flag=false;
        while (true){
            if(temp.next==null){
                break;
            }
            temp=temp.next;
            if(temp.number==personNode2.number){
                flag=true;
                break;
            }
        }
        if(flag){
            temp.pre.next=temp.next;
            if(temp.next!=null){
                temp.next.pre=temp.pre;
            }

        }else {
            System.out.println("找不到刪除節點的信息~~");
        }

    }

3、單向循環鏈表(無頭結點)

定義

單向循環鏈表是單鏈表的另一種形式,其結構特點是最後一個節點不再是結束標記,而是指向整個鏈表的第一個結點,從而形成一個環。

約瑟夫問題是單向循環鏈表的一個典型的應用,所以下面通過學習約瑟夫問題來學習單向循環鏈表。

  • 約瑟夫問題描述

約瑟夫問題是一個非常著名的趣題,即由n個小男孩坐成一圈,按順時針由1開始給他們編號。然後由第一個人開始報數,數到m的人出局。現在需要求的是最後一個出局的人的編號。給定兩個參數 n和m,分別代表遊戲的人數和報數大小。請返回最後一個出局的人的編號.

問題分析:

約瑟夫問題可以簡單分爲兩個步驟

構造Boy節點類

 private int number;
 private Boy next;
  • 構建一個單向循環鏈表

定義兩個指針變量First和cur,first表示頭結點,cur指向當前節點

/**
*構造第一個節點first
*/
first = boy;//第一個節點爲first
first.setNext(first);//僅有一個節點時,first指向first,構成閉環
cur = first;//輔助變量指向first
/**
*構造其它節點
*/
cur.setNext(boy);//將新添加的節點設置爲當前節點的後繼節點
boy.setNext(first);//新添加的節點與first構成閉環
cur=boy;//將當前節點設置爲新添加的節點
  • 遍歷鏈表,根據約定的規則退出循環鏈表,直至僅有一個節點

定義兩個輔助節點變量first和helper,first指向每一輪要開始的節點,helper指向最後一個節點,當first與helper指向同一個節點時,該循環鏈表僅剩一個節點

構造函數
moveCycle(int no,int number,int sum)
1、確定從第幾個節點開始
for(int i=0;i<no-1;i++){
  first=first.getNext();
  helper=helper.getNext();
}
2、循環,找出節點,直至僅剩一個節點
while (true){
  if(first==helper){
      System.out.printf("最後一個節點時%d\n",first.getNumber());
      break;
  }
  //開始,每number下,出圈一次
  for(int j=0;j<number-1;j++){
      first=first.getNext();
      helper=helper.getNext();
  }
  System.out.printf("%d出圈\n",first.getNumber());
  first=first.getNext();
  helper.setNext(first);
}

往期推薦

【吊打面試官】Mysql大廠高頻面試題!!!

Redis高頻面試題及答案

大白話布隆過濾器,又能和面試官扯皮了~

天天用Redis,持久化方案有哪些你知道嗎?

面試官:你知道哪幾種事務失效的場景?

天天寫 order by,你知道Mysql底層執行流程嗎?萬字長文帶你入門Zookeeper!!!求你了,別再問我Zookeeper如何實現分佈式鎖了!!!

Mysql性能優化:爲什麼count(*)這麼慢?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章