數據結構-----2.鏈表:

1.概念:鏈表不需要在內存中有連續的內存空間,他是將零散的內存塊通過 “指針” 的形式串聯在一起,每個節點中需要保存下一個節點所在的地址。

鏈表與數組的區別是:數組在內存中需要一塊連續的空間來存儲數據,如果數組需要100MB的存儲空間,雖然內存中剩餘的存儲空間大於100MB,但是連續的存儲空間小於100MB,那麼該數組同樣也會創建失敗。

2.常見的三種鏈表結構:單向鏈表,雙向鏈表,循環鏈表

(1)單向鏈表:

         在單向鏈表中,通常把第一個節點稱之爲 “頭節點”,頭節點中不存放數據,頭節點用來記錄整條鏈表的基地址,把最後一個節點稱之爲 “尾節點”。

根據頭節點就可以便利整條鏈表。尾節點的指針,指向一個NULL,代表這是鏈表上的最後一個節點。

         在鏈表中的某一個位置,刪除一個節點數據時,只需要改變相鄰節點的指針即可,所以它的時間複雜度爲O(1),但是如果在鏈表中隨機訪問某一個元素,因爲他在內存中不是連續存儲的,所以無法像數組那樣,可以根據首地址和下標索引,根據尋址公式計算出當前元素的地址,只能通過便利鏈表的形式來查找,所以它的時間複雜度爲O(n)。

(2)雙向鏈表:

         雙向鏈表在實際開發中也是最爲常用的,它存在兩個指針,(1)前驅指針(2)後繼指針,所以在某些特殊的情況下,它的插入,刪除的效率要比單向鏈表高。在存儲相同的數據時,雙向鏈表比單向鏈表更佔內存空間。

        實際開發中,從鏈表中刪除一個數據時,最常見的兩種情況:(1)根據指定的值去刪除(2)根據指定的指針去刪除

其實說單向鏈表中,插入和刪除的時間複雜度是O(1),指的是單純的插入和刪除操作,沒有算上之前的遍歷運算。

在第一種情況下,無論是單向鏈表還是雙向鏈表,都需要遍歷鏈表,然後找到對應的值去刪除,時間複雜度都爲O(n),

第二種情況下,因爲單向鏈表沒有前驅指針,即使知道要刪除哪一個節點,還是需要找到它的前驅節點,所以還需要遍歷鏈表,根據時間複雜度的加法法則,複雜度還是O(n)。但是雙向鏈表就會不一樣,它存在前驅指針,所以在知道確定刪除哪一個節點之後,就會找到它的前驅和後繼,進行指針的變換,所以複雜度爲O(1).

同理:在某一個節點的前面插入節點時,也會出現該現象。

在實際開發中,即使雙向鏈表消耗更多的內存,也使用的比較多,是因爲裏面有 “用空間換時間的思想”,對於執行較慢的程序,可以採用,用空間換時間。對於內存比較緊張的情況下,可以採用,以時間換空間的思想。LinkedHashMap。

(3)循環鏈表:

          循環鏈表與單向鏈表的區別在於,單向鏈表的末尾指針,指向NULL,但是循環鏈表的末尾指針,指向當前鏈表的頭節點,如同一個環形結構,比如著名的 “約瑟夫問題” 就可以使用循環鏈表來解決。

   

 3.基於鏈表實現緩存淘汰算法:

我的思路是這樣的:我們維護一個有序單鏈表,越靠近鏈表尾部的結點是越早之前訪問的。當有一個新的數據被訪問時,我們從鏈表頭開始順序遍歷鏈表。

1.如果此數據之前已經被緩存在鏈表中了,我們遍歷得到這個數據對應的結點,並將其從原來的位置刪除,然後再插入到鏈表的頭部。

2.如果此數據沒有在緩存鏈表中,又可以分爲兩種情況:

  • 如果此時緩存未滿,則將此結點直接插入到鏈表的頭部;

  • 如果此時緩存已滿,則鏈表尾結點刪除,將新的數據結點插入鏈表的頭部。

這樣我們就用鏈表實現了一個LRU緩存,

public class LRUCacheLinkedList<T> {

    /*
    * 緩存鏈表的默認容量*/
    private int DEFAULT_CAPACITY = 10;

    /*
    * 緩存鏈表頭節點*/
    private CacheNode headNode;

    /*
    * 緩存鏈表長度*/
    private int length;

    /*
    * 緩存鏈表容量*/
    private int capacity;

    public LRUCacheLinkedList(int capacity) {
        this.headNode = new CacheNode();
        this.capacity = capacity;
        this.length = 0;
    }

    public LRUCacheLinkedList() {
        this.length = 0;
        this.capacity = DEFAULT_CAPACITY;
        this.headNode = new CacheNode();
    }

    /*
    * 遍歷緩存鏈表,判斷在鏈表中是否能查找到指定的數據,如果能查到,那麼返回當前節點的前驅節點,
    * 如果查不到,那麼返回null
    */
    public CacheNode findPreNode(T data) {
        CacheNode node = headNode;
        while(node.nextNode != null) {
            if(node.nextNode.getValue().equals(data)) {
                return node;
            }
            node = node.nextNode;
        }
        return null;
    }

    /*
    * 增加節點*/
    public void lruAdd(T data) {
        //增加節點時,需要先判斷該數據在緩存鏈表中,是否存在,如果存在,需要先刪除,然後加到頭節點
        CacheNode node = findPreNode(data);
        if(node != null) {
           lruRemove(node);
           insertByHead(data);
        }else {
            if(length >= this.capacity) {
                removeEndNode();
            }
            insertByHead(data);
        }
    }

    /*
    * 將數據添加到頭節點*/
    public void insertByHead(T data) {
        CacheNode node = headNode.getNextNode();
        headNode.setNextNode(new CacheNode(data,node));
        length++;
    }

    /*
    * 刪除節點*/
    public void lruRemove(CacheNode preNode) {
        CacheNode node = preNode.getNextNode();
        preNode.setNextNode(node.getNextNode());
        node = null;
        //因爲刪除節點了,所以長度需要減一
        length--;
    }

    /*
    * 刪除緩存鏈表中尾部節點*/
    public void removeEndNode() {
        CacheNode node = headNode;
        //如果是空鏈表,那麼直接返回
        if(node.getNextNode() == null) {
            return;
        }
        //獲得緩存鏈表的倒數第二個節點
        while ( node.getNextNode().getNextNode()!= null) {
            node = node.getNextNode();
        }
        CacheNode temp = node.getNextNode();
        node.setNextNode(null);
        temp = null;
        length--;
    }

    /*
    * 打印緩存鏈表中的元素*/
    public void printAll() {
        CacheNode node = headNode.getNextNode();
        while (node != null) {
            System.out.print(node.getValue()+", ");
            node = node.getNextNode();
        }
        System.out.println();
        System.out.println("打印完畢!");
    }

    /*
    * 內部類,緩存鏈表中的節點元素*/
    private class CacheNode<T> {
        private T value;
        private CacheNode nextNode;
        /*
        * 該構造方法在鏈表頭部添加節點時,需要用到*/
        public CacheNode(T value,CacheNode nextNode) {
            this.value = value;
            this.nextNode = nextNode;
        }

        public CacheNode(T value) {
            this.value = value;
        }

        public CacheNode() {
            this.nextNode = null;
        }

        public T getValue() {
            return value;
        }

        public void setValue(T value) {
            this.value = value;
        }

        public CacheNode getNextNode() {
            return nextNode;
        }

        public void setNextNode(CacheNode nextNode) {
            this.nextNode = nextNode;
        }
    }

    public static void main(String[] args) {
        LRUCacheLinkedList lru = new LRUCacheLinkedList();
        Scanner scanner = new Scanner(System.in);
        while(true) {
            lru.lruAdd(scanner.nextInt());
            lru.printAll();
        }
    }

}

 

發佈了42 篇原創文章 · 獲贊 10 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章