數據結構與算法—鏈表(單向鏈表)

基本介紹

鏈表是有序的列表,只包含一個指針域、由n個結點鏈接形成的鏈表,就稱爲線型鏈表或者單向鏈表。如圖:

通過上圖,可以總結出鏈表的特點:
①. 鏈表是以結點的方式來存儲的,是鏈式存儲的。
②. 每個結點包含data域,next域:指向下一個結點。
③. 鏈表的各個結點不一定是連續的。
④. 鏈表有帶頭結點和不帶頭結點的。

那麼帶頭結點和不帶頭節點的區別是什麼?

  1. 帶頭結點 插入和刪除,不需要修改 head 值。
  2. 更好地處理鏈表爲空和非空。

鏈表結構示意圖

代碼實現

博主作爲一個英雄聯盟的老粉,從S4開始就征戰峽谷之巔了,每每想起曾經和自己一起玩遊戲的夥伴,現在都分散在各地,心裏便不是滋味。所以下面,通過對英雄聯盟中的英雄進行管理,增加,刪除,修改,以及排序等。來實現對鏈表的操作。

首先創建一個英雄對象

public class HeroNode {

    /**
     * 編號
     */
    public final int number;

    /**
     * 英雄名稱
     */
    public String name;

    /**
     * 定位
     */
    public String position;

    /**
     * 價錢
     */
    public final double price;

    /**
     * 下一個結點
     */
    public HeroNode next;

    public HeroNode(int number, String name, String position, double price) {
        this.number = number;
        this.name = name;
        this.position = position;
        this.price = price;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "number=" + number +
                ", name='" + name + '\'' +
                ", position='" + position + '\'' +
                ", price=" + price +
                '}';
    }
}

然後創建一個操作英雄的商城(鏈表對象)。

public class SaleLinkList {

    /**
     * 鏈表的頭
     */
    public final HeroNode headNode = new HeroNode(0, "", "", 0);

    /**
     * TODO 添加數據(不考慮英雄價錢和重複的情況)
     */
    public void add(HeroNode node) {
        // 將頭結點存入臨時變量
        HeroNode temp = headNode;
        // 遍歷鏈表,找到鏈表最後一個結點
        while (temp.next != null) {
            // 如果沒有找到,就將當前結點後移
            temp = temp.next;
        }
        // 在鏈表最後添加新的結點
        temp.next = node;
    }

    /**
     *TODO 顯示鏈表
     */
    public void list() {
        if (headNode.next == null) {
            System.out.println("鏈表爲空");
            return;
        }
        HeroNode temp = headNode.next;
        // 判斷鏈表是否到最後
        while (temp != null) {
            System.out.println(temp);
            // 將temp後移
            temp = temp.next;
        }
    }
}

基本上,添加鏈表的操作就完成了,下面編寫測試代碼

    public static void main(String[] args) {
        HeroNode node1 = new HeroNode(1, "薇恩", "ADC", 6300);
        HeroNode node2 = new HeroNode(2, "冰鳳凰", "法師", 4800);
        HeroNode node3 = new HeroNode(3, "阿卡麗", "刺客", 3150);
        HeroNode node4 = new HeroNode(4, "努努", "打野", 450);
        HeroNode node5 = new HeroNode(5, "小炮", "ADC", 1350);
        HeroNode node6 = new HeroNode(1, "薇恩", "ADC", 6300);

        // 創建鏈表(商城)
        SaleLinkList saleLinkList = new SaleLinkList();
        // 添加結點(添加英雄到商店)
        saleLinkList.add(node1);
        saleLinkList.add(node2);
        saleLinkList.add(node3);
        saleLinkList.add(node4);
        saleLinkList.add(node5);
        saleLinkList.add(node6);

        // 顯示鏈表(顯示所有英雄)
        saleLinkList.list();
    }

打印結果是:

HeroNode{number=1, name='薇恩', position='ADC', price=6300.0}
HeroNode{number=2, name='冰鳳凰', position='法師', price=4800.0}
HeroNode{number=3, name='阿卡麗', position='刺客', price=3150.0}
HeroNode{number=4, name='努努', position='打野', price=450.0}
HeroNode{number=5, name='小炮', position='ADC', price=1350.0}
HeroNode{number=1, name='薇恩', position='ADC', price=6300.0}

可以看到輸出結果是正常的。證明添加和顯示操作正確,但是這裏出現了重複數據,在英雄聯盟商城中,可不會出現重複英雄的。而且還有一點:英雄聯盟中默認按照英雄的價錢降序排列,所以這裏需要進行優化。

針對上面代碼進行改進。
添加操作(思路分析):

1. 首先找到新添加結點的位置,通過變量 temp(指針)
2. 新的結點的 next(newNode.next = temp.next)
3. 將 temp.next 指向新的結點(temp.next = newNode)

因爲鏈表還涉及修改、刪除操作,所以這裏先分析,後面再用代碼依次實現
修改操作(思路分析):

1. 這裏不能對 編號 以及 價錢 修改,不然就相當於添加新的數據
2. 找到需要修改的這個結點,根據變量 temp 進行查找
3. 滿足條件: temp.number = newNode.number
4. 將新的值賦值給以前的值(temp.name = newNode.name;temp.position = newNode.position)

刪除操作(思路分析):

1. 首先找到需要刪除的這個結點的前一個結點 temp
2. 直接將前一個結點的 next 指向下下個結點(temp.next = temp.next.next)
3. 被刪除的結點,沒有其他引用,就會被垃圾回收機制回收

思路分析完,下面就完成代碼的編寫。

public class SaleLinkList {

    /**
     * 鏈表的頭
     */
    public final HeroNode headNode = new HeroNode(0, "", "", 0);

    /**
     * TODO 添加數據(不考慮英雄價錢和重複的情況)
     */
    public void add(HeroNode node) {
        // 將頭結點存入臨時變量
        HeroNode temp = headNode;
        // 遍歷鏈表,找到鏈表最後一個節點
        while (temp.next != null) {
            // 如果沒有找到,就將當前節點後移
            temp = temp.next;
        }
        // 在鏈表最後添加新的節點
        temp.next = node;
    }

    /**
     * TODO 添加數據(根據英雄的價格進行排序,並對編號進行去重)
     */
    public void addByNumber(HeroNode node) {
        // 將 頭結點 賦給 中間值
        HeroNode temp = headNode;
        // 判斷 當前英雄的編號是否存在
        boolean flag = false;
        while (true) {
            // 說明 temp 已經是 鏈表的最後
            if (temp.next == null) {
                break;
            }
            // 找到滿足條件(按照價格插入,並且編號不能相等),在 temp 之後插入
            if (temp.next.price < node.price && temp.next.number != node.number) {
                break;
                // 編號已經存在
            } else if (temp.next.number == node.number) {
                flag = true;
                break;
            }
            // 後移,即遍歷鏈表
            temp = temp.next;
        }
        if (flag) {
            System.out.printf("添加失敗,插入當前的英雄的編號 %d 已經存在。\n", node.number);
        } else {
            // 將之前 temp 中的 下一個指針 賦給 當前的 node的 next
            node.next = temp.next;
            // 新的結點 指向當前 temp 的 next
            temp.next = node;
        }
    }

    /**
     * TODO 修改結點信息,根據 number 來修改(編號和價錢不能修改)
     */
    public void update(HeroNode node) {
        if (headNode.next == null) {
            System.out.println("鏈表爲空");
            return;
        }
        HeroNode temp = headNode.next;
        // 是否找到這個結點
        boolean flag = false;
        while (true) {
            if (temp == null) {
                break;
            }
            if (temp.number == node.number) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        // 如果找到 對應的 編號,進行修改
        if (flag) {
            temp.name = node.name;
            temp.position = node.position;
        } else {
            System.out.printf("修改失敗,沒有找到 編號 爲 %d 的節點 \n", node.number);
        }
    }

    /**
     * TODO 需要找到待刪除結點之前的結點, 比較temp.next.number = number(需要刪除的結點)
     */
    public void delete(int number) {
        HeroNode temp = headNode;
        // 是否找到刪除結點
        boolean flag = false;
        while (true) {
            // 判斷是否爲鏈表的尾部
            if (temp.next == null) {
                break;
            }
            if (temp.next.number == number) {
                flag = true;
                break;
            }
            // 鏈表後移
            temp = temp.next;
        }
        if (flag) {
            // 刪除,改變 next 的指向
            temp.next = temp.next.next;
        }else {
            System.out.printf("刪除失敗,沒有找到 編號 爲 %d 的節點 \n", number);
        }
    }

    /**
     * 顯示鏈表
     */
    public void list() {
        if (headNode.next == null) {
            System.out.println("鏈表爲空");
            return;
        }
        HeroNode temp = headNode.next;
        // 判斷鏈表是否到最後
        while (temp != null) {
            System.out.println(temp);
            // 將temp後移
            temp = temp.next;
        }
    }
}

編寫測試類

public class Test {

    public static void main(String[] args) {
        HeroNode node1 = new HeroNode(1, "薇恩", "ADC", 6300);
        HeroNode node2 = new HeroNode(2, "冰鳳凰", "法師", 4800);
        HeroNode node3 = new HeroNode(3, "阿卡麗", "刺客", 3150);
        HeroNode node4 = new HeroNode(4, "努努", "打野", 450);
        HeroNode node5 = new HeroNode(5, "小炮", "ADC", 1350);
        HeroNode node6 = new HeroNode(1, "薇恩", "ADC", 6300);


        SaleLinkList saleLinkList2 = new SaleLinkList();

        // 添加節點(添加英雄到商店:價格排序,並進行重複校驗)
        saleLinkList2.addByNumber(node1);
        saleLinkList2.addByNumber(node2);
        saleLinkList2.addByNumber(node3);
        saleLinkList2.addByNumber(node4);
        saleLinkList2.addByNumber(node5);
        saleLinkList2.addByNumber(node6);

        System.out.println("修改之前:");
        saleLinkList2.list();
        saleLinkList2.update(new HeroNode(1, "大嘴", "ADC", 6300));
        System.out.println("修改之後:");
        saleLinkList2.list();

        // 刪除
        saleLinkList2.delete(6);
        saleLinkList2.delete(1);
        System.out.println("刪除後:");
        saleLinkList2.list();
    }
}

輸出結果:

添加失敗,插入當前的英雄的編號 1 已經存在。
修改之前:
HeroNode{number=1, name='薇恩', position='ADC', price=6300.0}
HeroNode{number=2, name='冰鳳凰', position='法師', price=4800.0}
HeroNode{number=3, name='阿卡麗', position='刺客', price=3150.0}
HeroNode{number=5, name='小炮', position='ADC', price=1350.0}
HeroNode{number=4, name='努努', position='打野', price=450.0}
修改之後:
HeroNode{number=1, name='大嘴', position='ADC', price=6300.0}
HeroNode{number=2, name='冰鳳凰', position='法師', price=4800.0}
HeroNode{number=3, name='阿卡麗', position='刺客', price=3150.0}
HeroNode{number=5, name='小炮', position='ADC', price=1350.0}
HeroNode{number=4, name='努努', position='打野', price=450.0}
刪除失敗,沒有找到 編號 爲 6 的節點 
刪除後:
HeroNode{number=2, name='冰鳳凰', position='法師', price=4800.0}
HeroNode{number=3, name='阿卡麗', position='刺客', price=3150.0}
HeroNode{number=5, name='小炮', position='ADC', price=1350.0}
HeroNode{number=4, name='努努', position='打野', price=450.0}

Process finished with exit code 0

基本上單向鏈表(帶頭結點)的實現就完成了,數據結構學起來還是挺有意思的,就是有的時候自己理解的慢,就容易產生煩躁的情緒。因此,需要多多的學習,多多的練習,才能克服這種牴觸情緒。

參考資料:
作者:韓順平
課程:《Java數據結構與算法》

天氣因你逆轉,世界因你天晴。

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