美文网首页
LinkedList双向链表解析

LinkedList双向链表解析

作者: icecrea | 来源:发表于2017-11-27 22:45 被阅读1294次

    Queue是队列



    add 增加一个元素
    offer 添加一个元素并返回true 如果队列已满,则返回false
    remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
    poll 移除并返问队列头部的元素 如果队列为空,则返回null
    element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
    peek 返回队列头部的元素 如果队列为空,则返回null

    小测试Demo

      public static void main(String [] args) {
            Queue<String> queue = new LinkedList<String>();
            //追加元素
            queue.offer("one");
            queue.offer("two");
            queue.offer("three");
            queue.offer("four");
            System.out.println(queue);
            //从队首取出元素并删除
            String poll = queue.poll();
            System.out.println(poll);
            System.out.println(queue);
            //从队首取出元素但是不删除
            String peek = queue.peek();
            System.out.println(peek);
            System.out.println(queue);
            //遍历队列,这里要注意,每次取完元素后都会删除,整个
            //队列会变短,所以只需要判断队列的大小即可
            while(queue.size() > 0) {
                System.out.println(queue.poll());
            }
        }
    

    双向队列(Deque),是Queue的一个子接口,双向队列是指该队列两端的元素既能入队(offer)也能出队(poll),如果将Deque限制为只能从一端入队和出队,则可实现栈的数据结构。对于栈而言,有入栈(push)和出栈(pop),遵循先进后出原则

      public static void main(String [] args) {
            Deque<String> deque = new LinkedList<String>();
            deque.push("a");
            deque.push("b");
            deque.push("c");
            System.out.println(deque);
            //获取栈首元素后,元素不会出栈
            String str = deque.peek();
            System.out.println(str);
            System.out.println(deque);
            while (deque.size() > 0) {
                //获取栈首元素后,元素将会出栈
                System.out.println(deque.pop());
            }
            System.out.println(deque);
        }
    

    linkedlist和collection关系


    LinkedList的本质是双向链表。
    (01) LinkedList继承于AbstractSequentialList,并且实现了Dequeue接口。
    (02) LinkedList包含两个重要的成员:header 和 size。
      header是双向链表的表头,它是双向链表节点所对应的类Entry的实例。Entry中包含成员变量: previous, next, element。其中,previous是该节点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值。
      size是双向链表中节点的个数。

    LinkedList实际上是通过双向链表去实现的。既然是双向链表,那么它的顺序访问会非常高效,而随机访问效率比较低。既然LinkedList是通过双向链表的,但是它也实现了List接口{也就是说,它实现了get(int location)、remove(int location)等“根据索引值来获取、删除节点的函数”}。LinkedList是如何实现List的这些接口的,如何将“双向链表和索引值联系起来的”?
    实际原理非常简单,它就是通过一个计数索引值来实现的。例如,当我们调用get(int location)时,首先会比较“location”和“双向链表长度的1/2”;若前者大,则从链表头开始往后查找,直到location位置;否则,从链表末尾开始先前查找,直到location位置。
    这就是“双线链表和索引值联系起来”的方法。

    183     // 获取双向链表中指定位置的节点
    184     private Entry<E> entry(int index) {
    185         if (index < 0 || index >= size)
    186             throw new IndexOutOfBoundsException("Index: "+index+
    187                                                 ", Size: "+size);
    188         Entry<E> e = header;
    189         // 获取index处的节点。
    190         // 若index < 双向链表长度的1/2,则从前先后查找;
    191         // 否则,从后向前查找。
    192         if (index < (size >> 1)) {
    193             for (int i = 0; i <= index; i++)
    194                 e = e.next;
    195         } else {
    196             for (int i = size; i > index; i--)
    197                 e = e.previous;
    198         }
    199         return e;
    200     }
    
    202     // 从前向后查找,返回“值为对象(o)的节点对应的索引”
    203     // 不存在就返回-1
    204     public int indexOf(Object o) {
    205         int index = 0;
    206         if (o==null) {
    207             for (Entry e = header.next; e != header; e = e.next) {
    208                 if (e.element==null)
    209                     return index;
    210                 index++;
    211             }
    212         } else {
    213             for (Entry e = header.next; e != header; e = e.next) {
    214                 if (o.equals(e.element))
    215                     return index;
    216                 index++;
    217             }
    218         }
    219         return -1;
    220     }
    

    从上面代码我们可以很明显的看出,遍历Linkedlist的时候不要用get方法,他实际上也是调用了entry(index)方法,每一次都会从头或者从尾进行遍历,时间复杂度非常高。

            第一个元素(头部)                 最后一个元素(尾部)
            抛出异常        特殊值            抛出异常        特殊值
    插入    addFirst(e)    offerFirst(e)    addLast(e)        offerLast(e)
    移除    removeFirst()  pollFirst()      removeLast()    pollLast()
    检查    getFirst()     peekFirst()      getLast()        peekLast()
    

    LinkedList可以作为FIFO(先进先出)的队列,作为FIFO的队列时,下表的方法等价:

    队列方法       等效方法
    add(e)        addLast(e)
    offer(e)      offerLast(e)
    remove()      removeFirst()
    poll()        pollFirst()
    element()     getFirst()
    peek()        peekFirst()
    

    LinkedList可以作为LIFO(后进先出)的栈,作为LIFO的栈时,下表的方法等价:

    栈方法        等效方法
    push(e)      addFirst(e)
    pop()        removeFirst()
    peek()       peekFirst()
    

    因为是双向链表,header代表头指针,是没有数据的。在头部添加,实质上就是在header和header.next之间添加。在尾部添加,实质上是在header和header.previous之间插入。

     24     // 获取LinkedList的第一个元素
     25     public E getFirst() {
     26         if (size==0)
     27             throw new NoSuchElementException();
     28 
     29         // 链表的表头header中不包含数据。
     30         // 这里返回header所指下一个节点所包含的数据。
     31         return header.next.element;
     32     }
     33 
     34     // 获取LinkedList的最后一个元素
     35     public E getLast()  {
     36         if (size==0)
     37             throw new NoSuchElementException();
     38 
     39         // 由于LinkedList是双向链表;而表头header不包含数据。
     40         // 因而,这里返回表头header的前一个节点所包含的数据。
     41         return header.previous.element;
     42     }
    
     54     // 将元素添加到LinkedList的起始位置
     55     public void addFirst(E e) {
     56         addBefore(e, header.next);
     57     }
     58 
     59     // 将元素添加到LinkedList的结束位置
     60     public void addLast(E e) {
     61         addBefore(e, header);
     62     }
    
        /**
         * 将LinkedList当作 LIFO(后进先出)的堆栈
         */
        private static void useLinkedListAsLIFO() {
            System.out.println("\nuseLinkedListAsLIFO");
            // 新建一个LinkedList
            LinkedList stack = new LinkedList();
    
            // 将1,2,3,4添加到堆栈中
            stack.push("1");
            stack.push("2");
            stack.push("3");
            stack.push("4");
            // 打印“栈”
            System.out.println("stack:"+stack);
    
            // 删除“栈顶元素”
            System.out.println("stack.pop():"+stack.pop());
    
            // 取出“栈顶元素”
            System.out.println("stack.peek():"+stack.peek());
    
            // 打印“栈”
            System.out.println("stack:"+stack);
        }
    
        /**
         * 将LinkedList当作 FIFO(先进先出)的队列
         */
        private static void useLinkedListAsFIFO() {
            System.out.println("\nuseLinkedListAsFIFO");
            // 新建一个LinkedList
            LinkedList queue = new LinkedList();
    
            // 将10,20,30,40添加到队列。每次都是插入到末尾
            queue.offer("10");
            queue.offer("20");
            queue.offer("30");
            queue.offer("40");
            // 打印“队列”
            System.out.println("queue:"+queue);
    
            // 删除(队列的第一个元素)
            System.out.println("queue.poll():"+queue.poll());
    
            // 读取(队列的第一个元素)
            System.out.println("queue.peek():"+queue.peek());
    
            // 打印“队列”
            System.out.println("queue:"+queue);
        }
    

    本文大量代码参考自:https://www.cnblogs.com/skywang12345/p/3308807.html


    2017.11.28 更新:
    之前电脑是1.8源码,所以我就看了别人的解读做了一部分总结,但刚刚我查看了1.7的源码,发现还是有一些不一样的
    下面代码来自jdk1.7.0_40
    内部类node相同,都是前后指针和本身

       private static class Node<E> {
            E item;
            Node<E> next;
            Node<E> prev;
    
            Node(Node<E> prev, E element, Node<E> next) {
                this.item = element;
                this.next = next;
                this.prev = prev;
            }
        }
    

    但是全局变量并没有使用头指针header的形式,而是用了first,last两个指针,size表示大小

        transient int size = 0;
    
        /**
         * Pointer to first node.
         * Invariant: (first == null && last == null) ||
         *            (first.prev == null && first.item != null)
         */
        transient Node<E> first;
    
        /**
         * Pointer to last node.
         * Invariant: (first == null && last == null) ||
         *            (last.next == null && last.item != null)
         */
        transient Node<E> last;
    

    去掉了头指针,在访问首尾节点更加得方便。
    同时,在按下标访问时,也是比较size的一半,然后从头或者从尾遍历。

        Node<E> node(int index) {
            // assert isElementIndex(index);
    
            if (index < (size >> 1)) {
                Node<E> x = first;
                for (int i = 0; i < index; i++)
                    x = x.next;
                return x;
            } else {
                Node<E> x = last;
                for (int i = size - 1; i > index; i--)
                    x = x.prev;
                return x;
            }
        }
    

    相关文章

      网友评论

          本文标题:LinkedList双向链表解析

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