手寫真正的動態數據結構-鏈表

鏈表

線型數據結構

爲什麼鏈表很重要

  • 鏈表是真正的動態數據結構

  • 鏈表是最簡單的動態數據結構

  • 可以更深入的理解引用(指針)

  • 可以更深入的理解遞歸

  • 可以輔助組成其他的數據結構

什麼是鏈表(LinkedList)

  • 數據存儲在"節點"(Node)中

class Node {
    
    /**
     * 所存儲的真正的數據
     */
    E e;
    
    /**
     * 指向當前節點的下一個節點
     */
    Node next;
}

  • 優點:真正的動態數據結構,不需要處理固定容量的問題

  • 缺點:喪失了隨機訪問的能力

數組和鏈表的對比

  • 數組最好用於索引有語意的情況,如:scores[2]

  • 數組最大的優點就是:支持快速查詢

  • 鏈表不適合於索引有語意的情況

  • 鏈表最大的優點:動態

鏈表的基本實現

package com.ldc.datastructures.linkedlist;

/**
 * @author lengdongcheng
 */
public class LinkedList<E> {

    /**
     * 內部類
     */
    private class Node{
        /**
         * 存放的真實的數據
         */
        public E e;

        /**
         * 指向該節點的下一個節點
         */
        public Node next;

        /**
         * 構造函數
         * @param e
         * @param next
         */
        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }

        /**
         * 只傳一個e的構造函數
         * @param e
         */
        public Node(E e) {
            this(e, null);
        }

        /**
         * 默認構造函數
         */
        public Node() {
            this(null, null);
        }

        /**
         * 重寫toString方法
         * @return
         */
        @Override
        public String toString() {
            return e.toString();
        }
    }
}

鏈表頭添加元素

圖示


  • 將新增加的節點掛接到原來的節點上,讓新增的Node節點的next指向將要掛在的節點的頭部

  • 維護head,將head頭部指向新增的節點

  • 最終效果

代碼演示

package com.ldc.datastructures.linkedlist;

/**
 * @author lengdongcheng
 */
public class LinkedList<E> {

    /**
     * 內部類
     */
    private class Node {
        /**
         * 存放的真實的數據
         */
        public E e;

        /**
         * 指向該節點的下一個節點
         */
        public Node next;

        /**
         * 構造函數
         *
         * @param e
         * @param next
         */
        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }

        /**
         * 只傳一個e的構造函數
         *
         * @param e
         */
        public Node(E e) {
            this(e, null);
        }

        /**
         * 默認構造函數
         */
        public Node() {
            this(null, null);
        }

        /**
         * 重寫toString方法
         *
         * @return
         */
        @Override
        public String toString() {
            return e.toString();
        }
    }

    /**
     * 頭部元素
     */
    private Node head;

    /**
     * 鏈表中元素的個數
     */
    private int size;

    /**
     * 默認的構造函數
     */
    public LinkedList() {
        //頭部元素爲空
        head = null;
        //元素的個數爲0
        size = 0;
    }

    /**
     * 獲取鏈表中元素的個數
     *
     * @return
     */
    public int getSize() {
        return size;
    }

    /**
     * 判斷鏈表是否爲空
     *
     * @return
     */
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 在鏈表的頭部添加元素
     */
    public void addFirst(E e) {
/*        //將元素e包裝成一個Node對象
        Node node = new Node(e);
        //將Node的next指向將要掛載的Node對象
        node.next = head;
        //維護一下head的指向,指向新添加的Node節點
        head = node;*/

        //上面三行代碼可以用下面一行代碼來表示
        head = new Node(e, head);

        //維護一下size
        size++;
    }
}

在鏈表中間添加元素

圖示

  • 需求

  • 將node節點的next指向prev的下一個節點:node.next = prev.next

  • 將prev節點的next指向node節點:prev.next = node

  • 最後效果

  • 需要注意下順序

代碼實現

/**
     * 在鏈表的index(從0開始)位置添加元素
     * 在鏈表的中間添加元素不是一個常用的操作,這裏只是練習用
     * @param index
     * @param e
     */
    public void add(int index, E e) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Add failed,index < 0 || index > size.");
        }
        //新添加的元素添在鏈表的頭部,則調用addFirst就好了(添加到0的位置需要特殊處理)
        if (index == 0) {
            addFirst(e);
        } else {
            //剛開始將prev初始化爲head的位置
            Node prev = head;
            //一直遍歷,剛開始指向的是head的這個位置,然後一直將head.next賦值給prev
            for (int i = 0; i < index - 1; i++) {
                prev = prev.next;
            }
            //將新添加的元素包裝成Node對象
/*            Node node = new Node(e);
            //將node的下一個節點指向prev的下一個節點
            node.next = prev.next;
            //將prev的下一個節點指向node
            prev.next = node;*/

            //同樣,可以將上面三行代碼合成一行代碼
            //new Node(e, prev.next); ----->將node這個節點於後面的節點掛接在一起
            prev.next = new Node(e, prev.next);

            size++;
        }
    }
    /**
     * 在鏈表的末尾位置添加元素
     * @param e
     */
    public void addLast(E e) {
        add(size, e);
    }

爲鏈表設置虛擬頭節點

  • 代碼實現

    /**
     * 在鏈表的index(從0開始)位置添加元素
     * 在鏈表的中間添加元素不是一個常用的操作,這裏只是練習用
     * @param index
     * @param e
     */
    public void add(int index, E e) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Add failed,index < 0 || index > size.");
        }
        Node prev = dummyHead;
        for (int i = 0; i < index; i++) {
            prev = prev.next;
        }
        //將虛擬頭節點的next指向新節點與虛擬頭節點後面節點掛載後的節點
        prev.next = new Node(e, prev.next);
        size++;
    }

    /**
     * 在鏈表的頭部添加元素
     */
    public void addFirst(E e) {
        add(0, e);
    }

    /**
     * 在鏈表的末尾位置添加元素
     * @param e
     */
    public void addLast(E e) {
        add(size, e);
    }

查詢以及更新鏈表中的元素

    /**
     * 根據索引獲取鏈表中的元素(根據索引獲取元素不常用,只是練習用)
     * @param index
     * @return
     */
    public E get(int index) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("get failed,index < 0 || index > size.");
        }

        //虛擬節點後面的節點
        Node cur = dummyHead.next;
        for (int i = 0; i < index; i++) {
            //獲取index位置的Node節點
            cur = cur.next;
        }
        //獲取index位置的Node節點的元素
        return cur.e;
    }

    /**
     * 獲取第一個節點的元素
     * @return
     */
    public E getFirst(){
        return get(0);
    }

    /**
     * 獲取最後一個節點的元素
     * @return
     */
    public E getLast(){
        return get(size-1);
    }

    /**
     * 修改鏈表中的第index位置的元素
     * @param index
     * @param e
     */
    public void set(int index, E e) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("set failed,index < 0 || index > size.");
        }

        Node cur = dummyHead.next;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        cur.e = e;
    }

    /**
     * 查看鏈表是否包含元素e
     * @param e
     * @return
     */
    public boolean contains(E e) {
        Node cur = dummyHead.next;
        //如果cur爲空的話,那麼則表示整的鏈表都已經遍歷完了一遍
        while (cur != null) {
            if (cur.e.equals(e)) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        Node cur = dummyHead.next;
        while (cur != null) {
            sb.append(cur + "->");
            cur = cur.next;
        }
        sb.append("NULL");
        return sb.toString();
    }

測試案例

package com.ldc.datastructures.linkedlist;

/**
 * @author lengdongcheng
 */
public class Main {
    public static void main(String[] args) {
        LinkedList<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < 5; i++) {
            linkedList.addFirst(i);
            System.out.println(linkedList);
        }

        linkedList.add(2, 666);
        System.out.println(linkedList);
    }
}

測試結果

0->NULL
1->0->NULL
2->1->0->NULL
3->2->1->0->NULL
4->3->2->1->0->NULL
4->3->666->2->1->0->NULL

從鏈表中刪除元素

圖示

  • 需求

  • 實現

代碼實現

    /**
     * 刪除指定索引位置的元素
     * @param index
     * @return
     */
    public E remove(int index) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("remove failed,index < 0 || index > size.");
        }
        Node prev = dummyHead;
        for (int i = 0; i < index; i++) {
            //找到待刪除節點之前的節點
            prev = prev.next;
        }
        Node retNode = prev.next;
        prev.next = retNode.next;
        retNode = null;
        size--;
        return retNode.e;
    }

    /**
     * 刪除鏈表中的第一個元素
     * @return
     */
    public E removeFirst() {
        return remove(0);
    }

    /**
     * 刪除鏈表中的最後一個元素
     * @return
     */
    public E removeLast() {
        return remove(0);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章