Java數據結構之鏈表(Linked List)


1.鏈表(Linked List)介紹

鏈表是有序的列表,但是它在內存存儲結構如下:

2.特點:

  • 鏈表是以節點的方式來存儲,是鏈式存儲
  • 每個節點包含 data 域, next 域:指向下一個節點.
  • 鏈表的各個節點不一定是連續存儲.
  • 鏈表分帶頭節點的鏈表和沒有頭節點的鏈表,根據實際的需求來確定

3.單鏈表介紹

單鏈表(帶頭結點) 邏輯結構示意圖如下:

4.應用示例:

使用帶head頭的單向鏈表實現 –水滸英雄排行榜管理,完成對英雄人物的增刪改查操作

第一種方法在添加英雄時,直接添加到鏈表的尾部

思路分析

添加(創建)

  • 先創建一個head頭節點,表示單鏈表的頭
  • 在添加英雄時,根據排名將英雄插入到指定位置(如果有這個排名,則添加失敗,並給出提示)

遍歷

  • 通過一個輔助變量遍歷,幫助遍歷整個鏈表。

刪除節點

 

鏈表數據結構實現:

//定義HeroNode , 每個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;
    }
    //為了顯示方法,我們重新toString
    @Override
    public String toString() {
        return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
    }
}

輔助管理鏈表類:

需要注意,鏈表維護的表頭節點head 不能動,因此處理時需要一個temp輔助節點找到待刪除節點的前一個節點

//定義SingleLinkedList 管理我們的英雄
class SingleLinkedList {
    //先初始化一個頭節點, 頭節點不要動, 不存放具體的數據
    private HeroNode head = new HeroNode(0, "", "");
    
    
    //返回頭節點
    public HeroNode getHead() {
        return head;
    }

    //添加節點到單向鏈表
    //思路,當不考慮編號順序時
    //1. 找到當前鏈表的最后節點
    //2. 將最后這個節點的next 指向 新的節點
    public void add(HeroNode heroNode) {
        
        //因為head節點不能動,因此我們需要一個輔助遍歷 temp
        HeroNode temp = head;
        //遍歷鏈表,找到最后
        while(true) {
            //找到鏈表的最后
            if(temp.next == null) {//
                break;
            }
            //如果沒有找到最后, 將將temp后移
            temp = temp.next;
        }
        //當退出while循環時,temp就指向了鏈表的最后
        //將最后這個節點的next 指向 新的節點
        temp.next = heroNode;
    }
    
    //第二種方式在添加英雄時,根據排名將英雄插入到指定位置
    //(如果有這個排名,則添加失敗,並給出提示)
    public void addByOrder(HeroNode heroNode) {
        //因為頭節點不能動,因此我們仍然通過一個輔助指針(變量)來幫助找到添加的位置
        //因為單鏈表,因為我們找的temp 是位於 添加位置的前一個節點,否則插入不了
        HeroNode temp = head;
        boolean flag = false; // flag標志添加的編號是否存在,默認為false
        while(true) {
            if(temp.next == null) {//說明temp已經在鏈表的最后
                break; //
            } 
            if(temp.next.no > heroNode.no) { //位置找到,就在temp的后面插入
                break;
            } else if (temp.next.no == heroNode.no) {//說明希望添加的heroNode的編號已然存在
                
                flag = true; //說明編號存在
                break;
            }
            temp = temp.next; //后移,遍歷當前鏈表
        }
        //判斷flag 的值
        if(flag) { //不能添加,說明編號存在
            System.out.printf("准備插入的英雄的編號 %d 已經存在了, 不能加入\n", heroNode.no);
        } else {
            //插入到鏈表中, temp的后面
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }

    //修改節點的信息, 根據no編號來修改,即no編號不能改.
    //說明
    //1. 根據 newHeroNode 的 no 來修改即可
    public void update(HeroNode newHeroNode) {
        //判斷是否空
        if(head.next == null) {
            System.out.println("鏈表為空~");
            return;
        }
        //找到需要修改的節點, 根據no編號
        //定義一個輔助變量
        HeroNode temp = head.next;
        boolean flag = false; //表示是否找到該節點
        while(true) {
            if (temp == null) {
                break; //已經遍歷完鏈表
            }
            if(temp.no == newHeroNode.no) {
                //找到
                flag = true;
                break;
            }
            temp = temp.next;
        }
        //根據flag 判斷是否找到要修改的節點
        if(flag) {
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        } else { //沒有找到
            System.out.printf("沒有找到 編號 %d 的節點,不能修改\n", newHeroNode.no);
        }
    }
    
    //刪除節點
    //思路
    //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 節點不存在\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 = temp.next;
        }
    }
}
View Code

 【騰訊面試題】單鏈表的反轉

思路:

先定義一個節點reverseHead = new HeroNode();

從頭到尾遍歷原來的鏈表,每遍歷一個節點,就將其取出,並放在新的鏈表reverseHead的最前端

原來的鏈表的head.next = reverseHead.net

代碼實現

// 將單鏈表反轉
public static void reversetList(HeroNode head){
    // 如果當前鏈表為空,或者只有一個節點,無需反轉,直接返回
    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;            // 將cur后移
    }
    // 將head.next指向reverseHead.next,實現單鏈表的反轉
    head.next = reverseHead.next;
}
View Code

 【百度面試題】從尾到頭打印單鏈表(要求方式1:反向遍歷,方式2:Stack棧)

思路:

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

方式2代碼實現

 

// 單鏈表逆序打印
public static void reversePrint(HeroNode head){
    if(head.next == null){
        return;
    }
    // 創建一個棧
    Stack<HeroNode> stack = new Stach<HeroNode>();
    HeroNode cur = head.next;
    // 遍歷鏈表將所有的節點壓入棧
    while(cur != null){
        stack.push(cur);
        cur = cur.next;
    }
    
    // 將棧中的節點進行打印,pop出棧
    while(stack.size() > 0){
        System.out.println(stack.pop());    // 利用stack先入后出的特點
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM