链表之Java实现双向链表

一、什么是链表

  定义:链式存储结构的特点是用一组任意的存储单元存储线性表的 数据元素,这组存储单元可以是连续的,也可以是不连续的。

  物理存储结构如下:图中每一个节点均代表存储的数据,并且都包含该节点的下一个节点的位置信息。

 二、链表的分类

  我们可以把链表分为3类:

    1)单链表 : 链表中的元素节点只能指向下一个节点或者空节点,节点直接不能相互指向。

    2)双向链表 : 链表中的每个元素节点都有两个指向,一个指向上一个节点,另一个指向下一个节点。

    3)循环链表 : 在单链表和双向链表的基础上,将两种链表的最后一个节点指向第一个节点的头部,从而形成循环。

  我们本节只用java实现双向链表。

三、双向链表的实现

  我们通过双向链表来手动实现LinkedList,具体看代码实现。

 

  1 class LinkedList<E>{
  2 
  3     private transient int size; //维护大小  transient序列化时该值不被序列化
  4 
  5     private transient Node<E> first; //第一个节点
  6 
  7     private transient Node<E> last; //最后一个节点
  8 
  9     public LinkedList(){
 10         this.size = 0; //初始化节点大小为0
 11     }
 12     //
 13     public boolean add(E element){ //在末尾添加节点
 14         final Node<E> l = last; //最后一个节点
 15         final Node<E> newNode = new Node(l, element, null); //构建需要添加的新节点
 16         last = newNode; //因为是往末尾添加元素,不管任何情况,该节点必定为最后一个节点,所以last标记为最后一个节点
 17         if(l == null) //如果最后一个节点为空,则说明该链表还没有节点,则创建的节点即为头节点也为尾节点
 18             first = newNode;
 19         else //不为空,则说明尾节点需要将下一个节点的位置指向刚新建的节点
 20             l.next = newNode;
 21         size ++;
 22         return true;
 23     }
 24     public boolean add(int index, E element){ //在index位置添加element,将原来index位置的节点挂在新添加的节点上
 25         checkIndex(index);
 26         if(index == size) //如果添加位置和size一样大,说明是在末尾添加
 27             add(element);
 28         else //非末尾添加
 29             linkNode(element, node(index));
 30         return true;
 31     }
 32     //
 33     public boolean remove(E element){ //删除元素为element的节点
 34         for(Node<E> x = first; x != null; x = x.next){ //依次循环,将链表中所有满足的element全部删除,这里有判空操作是因为传进来的elemet值不确定,为了避免发生空指针异常,需要做判空处理
 35             if((element != null && element.equals(x.item))
 36             || (element == null && x.item == element)){
 37                 unlink(x);
 38             }
 39         }
 40         return true;
 41     }
 42     public E remove(int index){ //删除下标index位置的元素
 43         checkIndex(index);
 44         return unlink(node(index));
 45     }
 46     //
 47     public E set(int index, E element){ //将下标index位置的节点元素设置为element
 48         checkIndex(index);
 49         Node<E> node = node(index);
 50         E oldValue = node.item;
 51         node.item = element; //将原来的元素更改为新的元素,其它连接关系不变
 52         return oldValue;
 53     }
 54     //
 55     public E get(int index){ //根据下标位置去获取节点元素
 56         return node(index).item;
 57     }
 58     public int size(){ //获取链表长度大小
 59         return this.size;
 60     }
 61 
 62     /**
 63      * 断开链接,建立新的链接
 64      * @param node 被删除的节点信息
 65      * @return 返回被删除的节点元素
 66      */
 67     private E unlink(Node<E> node){
 68         final Node<E> prev = node.prev; //保存node节点的上一个节点信息
 69         final E item = node.item; //保存node节点的元素信息
 70         final Node<E> next = node.next; //保存node节点的下一个节点信息
 71         if(prev == null){ //如果node节点的上一个节点信息为空,则说明node节点为头节点,则需要将头节点指针first指向node节点的下一个节点
 72             first = next;
 73         } else { //node上一个节点存在
 74             prev.next = next; //node的上一个节点的下一个节点指针指向node节点的下一个节点  
 75             node.prev = null; //断开node节点与上一个节点之间的连接关系
 76         }
 77         if(next == null) { //node的下一个节点为空,则说明node本身为尾节点,则需要更新last指向新的尾节点,即为node节点的上一个节点
 78             last = prev;
 79         } else { //node的下一个节点存在
 80             next.prev = prev; //需要将node节点的下一个节点的上一个节点指向node节点的上一个节点
 81             node.next = null; //断开node节点与下一个节点之间的连接关系
 82         }
 83         node.item = null; //经过上面两步操作之后,node节点的上下节点之间的关系已经断开,此时node变成了孤立的节点,再将该节点的元素置为null,方便gc进行回收
 84         size --;
 85         return item;
 86     }
 87 
 88     /**
 89      * 在node节点前面插入新节点
 90      * @param e 需要插入的元素
 91      * @param node 被插入节点位置
 92      */
 93     private void linkNode(E e, Node<E> node){
 94         final Node<E> pre = node.prev; //保存node节点的上一个节点信息
 95         final Node<E> newNode = new Node(pre, e, node); //构建新的节点,同时该节点的上一个节点信息及下一个节点信息均已包含
 96         node.prev = newNode; //将node节点上一个节点指向新建立的节点
 97         if(pre == null) //node节点的上一个节点为空,则说明node原本为头节点,此时在node节点前插入一个新节点,则头指针需要变更
 98             first = newNode;
 99         else
100             pre.next = newNode; //将node上一个节点的下一个节点指向新建立的节点
101         size ++;
102     }
103 
104     /**
105      * 找到index位置的节点,为了提升效率,这里先判断范围
106      * @param index 下标位置
107      * @return
108      */
109     private Node<E> node(int index){
110         //在前半段
111         if(index < (size >> 1)){
112             Node<E> node = first;
113             for(int i = 0; i < index; i++)
114                 node = node.next;
115             return node;
116         }
117         //在后半段
118         Node<E> node = last;
119         for(int i = size - 1; i > index; i--)
120             node = node.prev;
121         return node;
122     }
123 
124     //检查下标index是否越界
125     private void checkIndex(int index){
126         if(index < 0 || index > size)
127             throw new IndexOutOfBoundsException("index: " + index + ", size:" + size);
128     }
129 
130     //节点定义
131     private static class Node<E>{
132         E item; //数据元素
133         Node<E> next; //下一个节点
134         Node<E> prev; //上一个节点
135 
136         Node(Node<E> prev, E item, Node<E> next){
137             this.prev = prev;
138             this.item = item;
139             this.next = next;
140         }
141     }
142 }

   链表具备增删改查功能,通过上面代码,我们对双向链表也有了一个清楚的了解。具体也不多说,每一行都有注释。

 

posted @ 2020-11-30 23:22  bug_easy  阅读(242)  评论(0编辑  收藏  举报