当前位置: 移动技术网 > IT编程>开发语言>Java > 如何实现Java中一个简单的LinkedList

如何实现Java中一个简单的LinkedList

2019年07月22日  | 移动技术网IT编程  | 我要评论

linkedlist与arraylist都是list接口的具体实现类。linkedlist与arraylist在功能上也是大体一致,但是因为两者具体的实现方式不一致,所以在进行一些相同操作的时候,其效率也是有差别的。

对于抽象的数据结构——线性表而言,线性表分为两种,一种是顺序存储结构的顺序表,另一种是通过指针来描述其逻辑位置的链表。

针对于具体的java实现:

  1. 顺序存储的顺序表是用数组来实现的,以数组为基础进行封装各种操作而形成的list为arraylist
  2. 链表是用指针来描述其逻辑位置,在java中以双向链表为基础进行封装各种操作而形成的list为linkedlist

针对插入与删除操作,arraylist每插入一个元素,首先需要判断数组的空间够不够,不够要进行扩容,在有足够的空间的基础上,在指定的index位置上插入元素,但是该index及以后的元素都要后移。虽然删除操作不需要判断空间够不够,但同样需要该index及以后的元素向前移动,这些移动的操作会增加时间的复杂度。但是对于linkedlist就不一样,因为使用指针来指示其逻辑的位置,所以插入与删除的操作的时间复杂度都是 ** o(1) **

虽然对于arraylist而言,插入与删除的时间复杂度很高,但是对于查找指定位置的元素这种操作而言,就非常的快,因为可以通过数组直接得到该下标对应的元素。反而,linkedlist而言,无法直接返回指定位置的元素,需要一个个查询,其时间的复杂度就是 ** o(n) **

如何实现java的arraylist经典实体类一样,实现的目的主要在于练手以及掌握官方实现的原理和一些技巧,因此很多需要与其他类配合的方法和功能,就先不在这里实现如iterator等

所以,实现的linkedlist的方法如下:

add方法

get方法

indexof方法

remove方法

与实现arraylist的名字一样,为simplelinkedlist。源码地址,欢迎star,fork

构建一个双向链表

构建的代码如下:

 private static class node<e>{
 e item;
 node<e> next;
 node<e> prev;
 public node(e item, node<e> next, node<e> prev) {
 this.item = item;
 this.next = next;
 this.prev = prev;
 }
 } 

常规的双向链表的构建方法,一个数字域存放数组,一个前指针指向一个node类型的元素,一个后指针指向一个node类型的元素。

对于linkedlist的实现而言,还需要以下三个成员变量

 private int size;
 private node<e> first;
 private node<e> last;

add方法

这里实现的add方法是简单的add(e e)以及add(int index,e e)两个方法,addall()将其他集合转换linkedlist的方法,暂时放到以后去实现。

add方法两个重载方法,其分别对应不同的添加方式。先说add(e e)方法的实现。

 public boolean add(e element) {
 addatlast(element);
 return true;
 }

不指定位置添加元素,则默认添加到了链表的最后。addatlast的核心代码如下:

 private void addatlast(e element) {
 node<e> l = last;
 node<e> node = new node<e>(element, null, l);
 last = node;
 if (l == null) {
 first = node;
 } else {
 l.next = node;
 }
 size++;
 }

首先找到最后一位的node元素,然后根据element创建一个新的node元素,其next指向为null,prev指向为最后一位node元素。在创建完node元素之后,last就变成了先创建的node元素,接下来只需要把新node元素加到链表中即可。即让l对象(原最后一位,现倒数第二位元素的next指针,指向新node元素)。至此,新node元素的next指向null,prev指向倒数第二个元素,倒数第二个元素的next指向新node,就将node成功加入链表。

上述的操作也可以看出,其插入的操作非常省时间,比起arraylist,扩容,移动元素快很多。

add的第二个重载方法 add(int index ,e e),先看代码实现:

 public void add(int index, e element) {
 checkrangeforadd(index);
 if (index == size) {
 addatlast(element);
 } else {
 node<e> l = node(index);
 addbeforenode(element, l);
 }
 }

首先判断要插入的index是否在范围内,在的话,再执行后续的add操作。如果要插入的index刚好是最后一位,则执行上面讲的addatlast,如果不是,则得到index所对应的node元素,执行addbeforenode。

获取index所对应的node元素,是node方法,代码如下:

 private node<e> node(int index) {
 if (index < (size << 1)) {
 node<e> cursor = first;
 for (int i = 0; i < index; i++) {
 cursor = cursor.next;
 }
 return cursor;
 } else {
 node<e> cursor = last;
 for (int i = size - 1; i > index; i--) {
 cursor = cursor.prev;
 }
 return cursor;
 }
 }

这里的查找采用二分查找,节省查找时间,而且也应用到了双向链表的特点。首先判断index在前一半的范围内,还是后一半的范围内。如果是前一半,则游标node初始为first,用游标node元素的next,不断指向index所在的元素。如果是后一半,则游标node初始为last,用游标node元素的prev,不断指向index所在的元素。

在指定元素的前面插入新节点的addbeforenode的方法如下:

 private void addbeforenode(e element, node<e> specifiednode) {
 node<e> prenode = specifiednode.prev;
 node<e> newnode = new node<>(element, specifiednode, prenode);
 if (prenode == null) {
 first = newnode;
 } else {
 prenode.next = newnode;
 }
 specifiednode.prev = newnode;
 size++;
 }

插入的方式很简单,新节点的prev是原index元素的prev,新节点的next是原index元素。剩下的操作是把该node放到链表中,让原index元素的prev的next为新节点,但是要判断prenode是不是空,是的话,表示newnode为第一个元素,就是first。

至此,一个add方法,就实现完了。

get方法

get方法在有了上述node方法之后,就非常的简单。代码如下:

 public e get(int index) {
 checkrange(index);
 return node(index).item;
 }

checkrange检查index是否不在范围内。

 private void checkrange(int index) {
 if (index >= size || index < 0) {
 throw new indexoutofboundsexception("指定index超过界限");
 }
 }

indexof方法

indexof(object o)用来得到指定元素的下标。

 public int indexof(object element) {
 node<e> cursor = first;
 int count = 0;
 while (cursor != null) {
 if (element != null) {
 if (element.equals(cursor.item)) {
  return count;
 }
 }else{
 if (cursor.item == null) {
  return count;
 }
 }
 count ++;
 cursor = cursor.next;
 }
 return -1;
 }

与arraylist一样,从第一位开始查找,首先先判断element是不是null,分成两种情况。

remove方法

remove方法与add方法一样,同样有两个重载的方法,remove(object o)与remove(int index)

先看简单的remove(int index)方法,代码如下:

 public e remove(int index) {
 checkrange(index);
 return deletelink(index);
 }

deletelink是将该index所对应的节点的链接删除的方法,其代码如下:

 private e deletelink(int index) {
 node<e> l = node(index);
 e item = l.item;
 node<e> prevnode = l.prev;
 node<e> nextnode = l.next;
 if (prevnode == null) {
 first = nextnode;
 }else{
 prevnode.next = nextnode;
 l.next = null;
 }
 if (nextnode == null) {
 last = prevnode;
 }else{
 nextnode.prev = prevnode;
 l.prev = null;
 }
 size--;
 l.item = null;
 return item;
 }

首先获得该index对应的node元素,得到该node元素的前一个元素和后一个元素。接下来,只需要将前一个元素和后一个元素直接相连即可,其他只需要额外判断前一个元素和后一个元素是否为null就行。在判断前一个元素是否为null的时候,只需要操作前一个元素,在判断后一个元素是否为null的时候,也只需要操作后一个元素。最后,将要删除的元素各个引用至为null。

remove另一个重载方法remove(object o),在实现了indexof和deletelink方法之后,就非常简单。

 public boolean remove(object o) {
 int index = indexof(o);
 if (index < 0){
 return false;
 }
 deletelink(index);
 return true;
 }

获取该元素对应对应的下标,然后执行deletelink方法,完成remove操作。

总结

至此,一个功能简单的linkedlist就实现完成了,全部的代码可以看源码地址

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持移动技术网!

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网