美文网首页
数据结构学习笔记:链表基础(一)

数据结构学习笔记:链表基础(一)

作者: 陈兴强 | 来源:发表于2021-03-13 18:30 被阅读0次

    一.学习链表的意义

    链表是一种最重要的动态数据结构
    更深入的理解引用(或者指针)
    更深入的理解递归
    组织更加复杂的数据结构

    二.什么是链表(Linked List)

    数据存储在"节点"(Node)中
    优点:真正的动态,不需要处理固定容量的问题
    缺点:失去了随机访问的能力

    数组和链表的对比:
    数组最好用与索引有语意的情况.scores[2]
    最大的优点:支持快速查询

    链表不适合用于索引有语意的情况
    最大的优点:动态,插入和删除可以达到o(1)的复杂度

    三.数据结构

    1.单向链表

    单向链表组成:
    数据字段+指针字段
    物理结构:
    分散的,不必分配连续的内存
    逻辑上的结构:
    逻辑结构:
    1.单向链表所有的节点都知道它下一个节点在哪里,却无法知道前一个节点在哪里,所以它的每一个节点需要维护一个链表头指针。
    2.单向链表有第一个节点和最后一个节点的,最后一个节点指向的是null。

    2.环形链表

    环型链表组成:
    数据字段+指针字段
    物理结构:
    分散的,不必分配连续的内存
    逻辑结构:
    1.单向链表最后一个节点指向的是null,为链弥补单向链表的不足,环形链表最后一个节点指向的是第一个节点。它的数据结构是没有第一个节点也没有最后一个节点的,每一个节点都是第一个节点。其它与单向数据结构一致。

    3.双向链表

    左指针字段+数据字段+右指针字段
    物理结构:
    分散的,不必分配连续的内存
    逻辑结构:
    1.所有的节点知道下一个节点在哪里也知道上一个节点在哪里。
    2.通常会加上一个链表头,链表头不存放数据,左指针指向最后一个节点,右指针指向下一个节点。

    四:LRUCache

    数据结构:
    创建:创建时必须指定最大长度

        /**
         * @param maxSize for caches that do not override {@link #sizeOf}, this is
         *     the maximum number of entries in the cache. For all other caches,
         *     this is the maximum sum of the sizes of the entries in this cache.
         */
        public LruCache(int maxSize) {
            if (maxSize <= 0) {
                throw new IllegalArgumentException("maxSize <= 0");
            }
            this.maxSize = maxSize;
            this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
        }
    

    插入:使用双向链表来存储数据,插入操作时间复杂度为O(1),当LRUCache未满时,会在表头插入一条数据,当LRUCache已满时,会先在表头插入一条数据,再从表尾删除一条数据

        /**
         * Caches {@code value} for {@code key}. The value is moved to the head of
         * the queue.
         *
         * @return the previous value mapped by {@code key}.
         */
        @Nullable
        public final V put(@NonNull K key, @NonNull V value) {
            if (key == null || value == null) {
                throw new NullPointerException("key == null || value == null");
            }
    
            V previous;
            synchronized (this) {
                putCount++;
                size += safeSizeOf(key, value);
                previous = map.put(key, value);
                if (previous != null) {
                    size -= safeSizeOf(key, previous);
                }
            }
    
            if (previous != null) {
                entryRemoved(false, key, previous, value);
            }
    
            trimToSize(maxSize);
            return previous;
        }
    
       /**
         * Remove the eldest entries until the total of remaining entries is at or
         * below the requested size.
         *
         * @param maxSize the maximum size of the cache before returning. May be -1
         *            to evict even 0-sized elements.
         */
        public void trimToSize(int maxSize) {
            while (true) {
                K key;
                V value;
                synchronized (this) {
                    if (size < 0 || (map.isEmpty() && size != 0)) {
                        throw new IllegalStateException(getClass().getName()
                                + ".sizeOf() is reporting inconsistent results!");
                    }
    
                    if (size <= maxSize || map.isEmpty()) {
                        break;
                    }
    
                    Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                    key = toEvict.getKey();
                    value = toEvict.getValue();
                    map.remove(key);
                    size -= safeSizeOf(key, value);
                    evictionCount++;
                }
    
                entryRemoved(true, key, value, null);
            }
        }
    

    查询:使用HashMap来保存key与Node节点的对应关系,从而实现查询O(1)的复杂度。查询命中的把查到的数据移动到表头。

        /**
         * Returns the value for {@code key} if it exists in the cache or can be
         * created by {@code #create}. If a value was returned, it is moved to the
         * head of the queue. This returns null if a value is not cached and cannot
         * be created.
         */
        @Nullable
        public final V get(@NonNull K key) {
            if (key == null) {
                throw new NullPointerException("key == null");
            }
    
            V mapValue;
            synchronized (this) {
                mapValue = map.get(key);
                if (mapValue != null) {
                    hitCount++;
                    return mapValue;
                }
                missCount++;
            }
    
            /*
             * Attempt to create a value. This may take a long time, and the map
             * may be different when create() returns. If a conflicting value was
             * added to the map while create() was working, we leave that value in
             * the map and release the created value.
             */
    
            V createdValue = create(key);
            if (createdValue == null) {
                return null;
            }
    
            synchronized (this) {
                createCount++;
                mapValue = map.put(key, createdValue);
    
                if (mapValue != null) {
                    // There was a conflict so undo that last put
                    map.put(key, mapValue);
                } else {
                    size += safeSizeOf(key, createdValue);
                }
            }
    
            if (mapValue != null) {
                entryRemoved(false, key, createdValue, mapValue);
                return mapValue;
            } else {
                trimToSize(maxSize);
                return createdValue;
            }
        }
    
    

    五:LinkedHashMap

    LinkedHashMap是HashMap的子类,在HashMap基础上维护了一个双向链表,它可以保持插入顺序的LinkedHashMap,也可以保持访问顺序的LinkedHashMap。
    数据结构:
    每put一个数据先将其保存到哈希表中对应的位置上,再将其将其插入到双向链表的尾部。
    实现的是HashMap的put方法

        public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
    

    问题:它重写链HashMap的put方法怎么实现这个过年,LinkedHashMap重写了newNode方法

        Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
            LinkedHashMapEntry<K,V> p =
                new LinkedHashMapEntry<K,V>(hash, key, value, e);
            linkNodeLast(p);
            return p;
        }
    
       /**
         * The head (eldest) of the doubly linked list.
         */
        transient LinkedHashMapEntry<K,V> head;
    
        /**
         * The tail (youngest) of the doubly linked list.
         */
        transient LinkedHashMapEntry<K,V> tail;
    
        /**
         * The iteration ordering method for this linked hash map: <tt>true</tt>
         * for access-order, <tt>false</tt> for insertion-order.
         *
         * @serial
         */
        final boolean accessOrder;
    

    head:双向链表头节点的header
    tail:双向链表头节点的尾部
    accessOrder:
    值为true时,按照访问顺序存储,值为false时,表示按照插入顺序存储
    get方法:

        public V get(Object key) {
            Node<K,V> e;
            if ((e = getNode(hash(key), key)) == null)
                return null;
            if (accessOrder)
                afterNodeAccess(e);
            return e.value;
        }
    
    
        void afterNodeAccess(Node<K,V> e) { // move node to last
            LinkedHashMapEntry<K,V> last;
            if (accessOrder && (last = tail) != e) {
                LinkedHashMapEntry<K,V> p =
                    (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
                p.after = null;
                if (b == null)
                    head = a;
                else
                    b.after = a;
                if (a != null)
                    a.before = b;
                else
                    last = b;
                if (last == null)
                    head = p;
                else {
                    p.before = last;
                    last.after = p;
                }
                tail = p;
                ++modCount;
            }
        }
    
    
    

    注意这里afterNodeAccess方法,如果链表中元素的排序规则是按照插入的先后顺序排序的话,该方法什么也不做;
    如果是按照访问的先后顺序排序的话,则将e移到链表的尾处

    链表相关资料连接:
    算法与数据结构体系课
    https://class.imooc.com/sc/105/learn
    通俗易懂讲解 链表
    https://zhuanlan.zhihu.com/p/29627391
    链表实战(带图分析)
    https://www.jianshu.com/p/9a4561d42e9a
    LruCache
    https://blog.csdn.net/u014106644/article/details/91797484

    相关文章

      网友评论

          本文标题:数据结构学习笔记:链表基础(一)

          本文链接:https://www.haomeiwen.com/subject/ejsofltx.html