数据结构(链表)的应用——单向链表和双向链表(LinkedList)

链表是线性表的其中之一,线性表又是我们要学的数据结构的一部分,所以非常有学习价值,我们今天专门分析单链表和双链表。

一、单链表

  1. 存储结构
    上图就是单链表的存储结构原理图,由图中我们可以看出每个节点都由两部分构成,一个是data数据域、另一个是指向下一个节点的next指针域,指针域不存放任何数据。然后一直通过这样的方式一直指下去,最后就形成了一条类似铁链的结构,所以称为链表。我们看到最后的next指针为null,说明到了最后一个节点,最后一个节点的指针域不指向任何节点,所以next=null。

  2. 代码实现

/**
 * Created by bryanrady on 2017/12/7.
 *  单链表的实现
 */

public class SinglyLinkedList<E>{

private Node<E> first;      //单链表的头结点
private Node<E> last;       //单链表的最后一个节点  
private int size;

public SinglyLinkedList(){

}

public SinglyLinkedList(SinglyLinkedList c){
    this();
    addAll(c);
}

static class Node<E>{
    E data;     //数据域
    Node<E> next;   //指针域

    public Node(E data){
        this.data = data;
    }
}

/**
 * 返回单链表的长度
 * @return
 */
public int size(){
    return size;
}

/**
 * 在单链表尾部添加一个元素节点
 * @param element
 */
public void add(E element){
    Node<E> node = new Node<E>(element);
    Node<E> l = last;
    if(l == null){  //表明之前没有节点
        first = node;
    }else{
        l.next = node;
    }
    last = node;
    size++;
}

/**
 * 删除节点
 */
public E remove(){
    return removeFirst();
}

/**
 * 删除第一个节点
 * @return
 */
private E removeFirst(){
    Node<E> f = first;
    if(f == null){
        throw new NoSuchElementException();
    }
    return unlinkFirst(f);
}

/**
 * 删除第一个节点
 * @param node
 * @return
 */
private E unlinkFirst(Node<E> node) {
    E element = node.data;
    Node<E> next = node.next;
    node.data = null;
    node.next = null;
    first = next;
    if(next == null){
        last = null;
    }
    size--;
    return element;
}

/**
 * 在链表尾部添加一个集合
 * @param list
 * @return
 */
public boolean addAll(SinglyLinkedList<E> list){
    return  addAll(size,list);
}

/**
 * 返回单链表的Object[]数组
 * @return
 */
public Object[] toArray() {
    Object[] result = new Object[size];
    int i = 0;
    Node<E> e = first;
    while(e!=null){
        result[i++] = e.data;
        e = e.next;
    }
    return result;
}

/**
 * 在指定位置添加集合
 * @param index
 * @param list
 * @return
 */
private boolean addAll(int index,SinglyLinkedList<E> list){
    if(index<0 || index > size){
        throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size);
    }
    Object[] a = list.toArray();
    int numNew = a.length;
    if(numNew == 0){
        return false;
    }
    for(Object o : a){
        E e = (E)o;
        Node<E> newNode = new Node<>(e);
        if(last==null){
            first = newNode;
        }else{
            last.next = newNode;
        }
        last = newNode;
    }
    size += numNew;
    return true;
}

/**
 * 根据传入的Index查找节点
 * @param index
 * @return
 */
private Node<E> node(int index){
    Node<E> x = first;
    for(int i=0;i<index;i++){
       x = x.next;
    }
    return x;
}

public E get(int index){
    if(index<0||index>=size){
        throw new IndexOutOfBoundsException("Index:"+index);
    }
    return node(index).data;
}

}

我这里没有像其他实现者一样定义头结点是空的数据域,但是实现思路和方法基本都是一模一样的。我是采用的LinkedList代码的思想来进行实现,后面有时间再添加其他有关于集合的方法。

二、双向链表

双向链表与单链表比较就是多了一个指向前驱节点的指针,这样来构成有序性。我们常用的LinkedList就是在双向链表的数据结构上进行实现的,现在我们直接来分析源代码。

LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当作链表来操作外,它还可以当作栈,队列和双端队列来使用。LinkedList同样是非线程安全的,只在单线程下适合使用。

  1. 继承关系与实现的接口

     public class LinkedList<E>
             extends AbstractSequentialList<E>
             implements List<E>, Deque<E>, Cloneable, java.io.Serializable
    

LinkedList继承了AbstractSequentialList,而AbstractSequentialList又是AbstractList<E>的一个子类,这就是集合框架架构图,这里不详细介绍,后面再总结。

LinkedList实现了Deque(双端队列)接口,即LinkedList可以作为一个双端队列来使用。

LinkedList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了Cloneable接口,能被克隆。

  1. 数据结构,就是一个双向链表

    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;  //指向上一个节点
     }
    

    }

3.属性字段

  transient int size = 0;    //链表中的节点个数

  transient Node<E> first;    //定义链表头结点

  transient Node<E> last;    //定义最后一个节点

4.构造函数

public LinkedList() {
}

public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}
  1. 增删改查操作
    (1)添加操作
   /**
    * 添加元素
    * @param e
    * @return
    */
   public boolean add(E e){
       linkLast(e);
       return true;
   }

   /**
    * 在指定的位置往集合中添加元素e
    * @param index
    * @param e
    */
   public void add(int index,E e){
       checkPositionIndex(index);
       if(index == size){
           linkLast(e);
       }else{
           linkBefore(e,node(index));
       }
   }

   /**
    * 添加元素作为第一个节点
    * @param e
    */
   public void addFirst(E e){
       linkFirst(e);
   }

   /**
    * 添加元素作为最后一个节点
    * @param e
    */
   public void addLast(E e){
       linkLast(e);
   }

   /**
    * 在链表头部添加元素e,并将元素e作为第一个元素
    * @param e
    */
   private void linkFirst(E e){
       Node<E> f = first;
       Node<E> newNode = new Node<>(null,e,f);
       first = newNode;
       if(f==null){
           last = newNode;
       }else{
           f.prev = newNode;
       }
       size++;
   }

   /**
    * 在链表尾部添加元素e,并将e作为最后一个元素
    * @param e
    */
   private void linkLast(E e){
       Node<E> l = last;
       Node newNode = new Node(l,e,null);
       if(l == null){
           first = newNode;
       }else{
           l.next = newNode;
           newNode.prev = l;
       }
       last = newNode;
       size++;
   }

   /**
    * 在非空节点succ之前插入元素e
    * @param e
    * @param succ
    */
   private void linkBefore(E e,Node<E> succ){
       Node<E> pred = succ.prev;
       Node<E> newNode = new Node<>(pred,e,succ);
       if(pred == null){
           first = newNode;
       }else{
           pred.next = newNode;
       }
       succ.prev = newNode;
       size++;
   }


/**
    * 将集合c追加到LinkedList中
    * @param c
    * @return
    */
   public boolean addAll(Collection<? extends E> c){
       return addAll(size,c);
   }

   /**
    * 将集合c添加到LinkedList的指定位置
    * @param index
    * @param c
    * @return
    */
   public boolean addAll(int index,Collection<? extends E> c){
       checkPositionIndex(index);
       Object[] a = c.toArray();
       int numNew = a.length;
       if(numNew==0){
           return false;
       }
       Node<E> pred,succ;  //设置当前要插入节点的前后节点
       if(index==size){
           succ = null;
           pred = last;
       }else{
           succ = node(index);
           pred = succ.prev;
       }

       /****插入开始***/
       for(Object o : a){
           E e = (E)o;
           Node<E> newNode = new Node<>(pred,e,null);
           if(pred==null){
               first = newNode;
           }else{
               pred.next = newNode;
           }
           //此时把下一个要插入的节点的上一个节点变成刚刚插入的节点
           pred = newNode;
       }
       /****插入完成***/

       //设置前后节点的关系
       if(succ == null){
           last = pred;
       }else{
           pred.next = succ;
           succ.prev = pred;
       }
       size += numNew;
       return true;
   }
      

(2)删除操作

/**
     * 删除元素,默认删除第一个,并返回
     * @return
     */
    public E remove(){
        return removeFirst();
    }

    /**
     * 删除集合中指定位置的元素
     * @param index
     * @return
     */
    public E remove(int index){
        checkElementIndex(index);
        return unlink(node(index));
    }

    /**
     * 在集合中删除指定的对象
     * @param o
     * @return
     */
    public boolean remove(Object o){
        if(o == null){
            for(Node<E> x=first;x!=null;x=x.next){
                if(x.element == null){
                    unlink(x);
                    return true;
                }
            }
        }else{
            for(Node<E> x=first;x!=null;x=x.next){
                if(x.element.equals(o)){
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 删除链表的第一个元素并返回
     * @return
     */
    public E removeFirst(){
        Node<E> f = first;
        if(f == null){
            throw new NoSuchElementException();
        }
        return unlinkFirst(f);
    }

    /**
     * 删除链表的最后一个元素,并返回
     * @return
     */
    public E removeLast(){
        Node<E> l = last;
        if(l == null){
            throw new NoSuchElementException();
        }
        return unlinkLast(l);
    }

    /**
     * 清空链表     循环遍历全部置为null
     */
    public void clear(){
        for(Node<E> x=first;x!=null;x=x.next){
            x.element =null;
            x.prev = null;
            x.next = null;
            x = null;
        }
        first = last = null;
        size = 0;
    }

    /**
     * 删除一个非空节点x,并返回
     * @param x
     * @return
     */
    private E unlink(Node<E> x){
        E element = x.element;
        Node<E> pred = x.prev;
        Node<E> succ = x.next;
        if(pred == null){
            first = succ;
        }else{
            pred.next = succ;
            x.prev = null;
        }
        if(succ == null){
            last = pred;
        }else{
            succ.prev = pred;
            x.next = null;
        }
        x.element = null;
        size--;
        return element;
    }

    /**
     * 删除不为空的第一个节点
     * @param f
     */
    private E unlinkFirst(Node<E> f){
        E element = f.element;
        Node<E> succ = f.next;
        f.element = null;     //help gc
        f.next = null;
        first = succ;
        if(succ == null){
            last = null;
        }else{
            succ.prev = null;
        }
        size--;
        return element;
    }

    /**
     * 删除不为空的最后一个节点
     * @param l
     * @return
     */
    private E unlinkLast(Node<E> l){
        E element = l.element;
        Node<E> pred = l.prev;
        l.element = null;
        l.prev = null;
        last = pred;
        if(pred == null){
            first = null;
        }else{
            pred.next = null;
        }
        size--;
        return element;
    }

(3)更新操作

/**
     * 修改在链表中指定位置的元素
     * @param index
     * @param e
     * @return
     */
    public E set(int index,E e){
        checkElementIndex(index);
        Node<E> oldNode = node(index);
        E oldVal = oldNode.element;
        oldNode.element = e;
        return oldVal;
    }

(4)查询操作

/**
     * 根据index获取集合中的指定的元素
     * @param index
     * @return
     */
    public E get(int index){
        checkElementIndex(index);
        return node(index).element;
    }

    /**
     * 获取第一个节点上的元素
     * @return
     */
    public E getFirst(){
        Node<E> f = first;
        if(f == null){
            throw new NoSuchElementException();
        }
        return f.element;
    }

    /**
     * 获取最后一个节点的元素
     * @return
     */
    public E getLast(){
        Node<E> l = last;
        if(l == null){
            throw new NoSuchElementException();
        }
        return l.element;
    }

    /**
     * 根据index位置获取节点
     *  利用双向链表的特点提高查找效率
     * @param index
     * @return
     */
    private Node<E> node(int 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;
        }
    }

6.集合的一些常规方法

/**
     * 返回LinkedList的大小
     * @return
     */
    public int size(){
        return size;
    }

    /**
     * 判断集合是否为空
     * @return
     */
    public boolean isEmpty(){
        return size == 0;
    }
/**
     * 判断链表集合中是否包含此对象
     * @param o
     * @return
     */
    public boolean contains(Object o){
        return indexOf(o) >= 0;
    }

    /**
     * 获取对象o在链表集合中的索引位置
     * @param o
     * @return
     */
    public int indexOf(Object o){
        int index = 0;
        if(o == null){
            for(Node<E> x=first;x!=null;x=x.next){
                if(x.element == null){
                    return index;
                }
                index++;
            }
        }else{
            for(Node<E> x=first;x!=null;x=x.next){
                if(x.element.equals(o)){
                   return index;
                }
                index++;
            }
        }
        return -1;
    }

    /**
     * 返回此对象在链表中最后出现的位置
     * @param o
     * @return
     */
    public int lastIndexOf(Object o){
        int index = size-1;
        if(o == null){
            for(Node<E> x=last;x!=null;x=x.prev){
                if(x.element == null){
                    return index;
                }
                index--;
            }
        }else{
            for(Node<E> x=last;x!=null;x=x.prev){
                if(x.element.equals(o)){
                    return index;
                }
                index--;
            }
        }
        return -1;
    }

    /**
     *  添加元素的时候检查索引位置
     * @param index
     */
    private void checkPositionIndex(int index){
        if(index<0 || index>size) {
            throw new IndexOutOfBoundsException("Index:" + index + ",Size:" + size);
        }
    }

    /**
     * 在删除和获取元素时判断检查下标位置
     * @param index
     */
    private void checkElementIndex(int index){
        if(index<0 || index>=size) {
            throw new IndexOutOfBoundsException("Index:" + index + ",Size:" + size);
        }
    }

    /**
     * 返回LinkedList的Object数组
     * @return
     */
    public Object[] toArray(){
        Object[] o = new Object[size];
        int i=0;
        for(Node<E> x=first;x!=null;x=x.next){
            o[i++] = x.element;
        }
        return o;
    }

7.LinkedList作为双端队列的方法

/***    LinkedList实现了Deque<E>接口,Deque<E>接口对Queue<E>接口进行了扩展</>
     * </>  LinkedList作为队列使用
     *      一般用LinkedList作为队列使用较多,使用栈的时候最好还是使用Stack<E>这个类
     * </>*****/

    /**
     * 往队列尾部添加元素
     * @param e
     * @return
     */
    public boolean offer(E e){
        return add(e);
    }

    /**
     * 从队列头部取元素,并删除
     * @return
     */
    public E poll(){
        Node<E> f = first;
        if(f == null){
            return null;
        }
        return unlinkFirst(f);
    }

    /**
     * 从队列头部读取元素,但是不删除
     * @return
     */
    public E peek(){
        Node<E> f = first;
        if(f == null){
            return null;
        }
        return f.element;
    }

    public boolean offerFrist(E e){
        addFirst(e);
        return true;
    }

    public boolean offerLast(E e){
        addLast(e);
        return true;
    }

    public E pollFirst(){
        Node<E> f = first;
        if(f == null){
            return null;
        }
        return unlinkFirst(f);
    }

    public E pollLast(){
        Node<E> f = first;
        if(f == null){
            return null;
        }
        return unlinkLast(f);
    }

    public E peekFirst(){
        Node<E> f = first;
        if(f == null){
            return null;
        }
        return f.element;
    }

    public E peekLast(){
        Node<E> l = last;
        if(l == null){
            return null;
        }
        return l.element;
    }
  1. LinkedList作为栈的使用方法
/***    LinkedList还可以当成是栈来使用  *****/

    /**
     * 往栈顶添加元素,就是在链表前面添加元素作为第一个节点
     * @param e
     */
    public void push(E e){
        addFirst(e);
    }

    /**
     * 往栈顶取元素并删除,即删除链表前面删除元素,这样在就满足了栈的先进后出的原则
     *  还有个不删除的就是和peek()方法一样
     * @return
     */
    public E pop(){
        return removeFirst();
    }

看了这么多LinkedList的方法,现在我们来总结一下ArrayList和LinkedList的区别(也是顺序表和链表的区别):

(1)ArrayList是基于动态数组实现的,LinkedList是基于双向链表实现的。

(2)ArrayList支持随机访问(通过下标),LinkedList不支持。

(3)ArrayList的查询和尾部插入元素效率较高,中间插入和删除元素效率较低,因为有大量的数组复制操作。
LinkedList的插入和删除效率较高,只需要把节点指针改变一下就行,但是查询效率较低,即使有双向链表的特性可以从两个方向查找,但还是需要使用蛮力法的方式进行遍历,所以效率较低。

(4)ArrayList会造成一定的空间浪费,因为随时需要探测数组容量然后扩容;LinkedList通过节点方式进行存放数据,不存在空间浪费。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350