首页 > 代码库 > Java学习分享-->集合-->链表
Java学习分享-->集合-->链表
链表是一个有序集合,它将每个对象存放在独立的结点中,每个结点还存放着下一个结点的引用。在Java中由于链表是双向链接的,每个结点还存放着前一个结点的引用。
(图片引自Java核心技术 卷1 基础知识)
删除链表中间的一个元素,只需要更新被删除元素附近的结点。假设我们有三个结点,删除第二个结点后,第一个结点将原本存放第二个结点的引用更新为第三个结点的引用(这里对应我们前面提到的“每个结点还存放着下一个结点的引用”),而第三个结点将原本存放第二个结点的引用更新为第一个结点的引用(这里对应我们前面提到的“每个结点还存放着前一个结点的引用”)。
(图片引自Java核心技术 卷1 基础知识)
正常调用add()方法是将元素添加进链表的尾部,但可能会出现把元素添加进链表中间的情况。这时就需要使用ListIterator来实现,从名称上来我们知道这也是一种迭代器,不过这个迭代器中提供了一个add()方法(注意要和链表本身的add()方法分开理解)。调用迭代器的add()方法会在迭代器位置之前添加一个元素。而在另外一种Set类型中,由于其中的元素是无序的,其Iterator接口中就没有add()方法。因此我们得出一个结论:“只有对自然有序的集合使用迭代器添加元素才有实际意义”。
那这一实现过程是怎样的呢?首先我们需要明确要将新元素添加到链表中哪个已存在的元素之后,确定了位置之后利用迭代器的next()方法越过到已存在的元素之后,再通过调用迭代器的add()方法就可以把新元素添加到链表的指定位置。
在明确了上面的操作步骤后,有以下几个问题可以来思考下?
①需要向链表的表头添加元素
1)我们可以在拿到刚刚返回的ListIterator对象后就调用迭代器的add()方法,以下为example
List<String> list = new LinkedList<String>(); list.add("Andy"); ListIterator<String> iterator = list.listIterator(); iterator.add("Amy"); while (iterator.hasNext()) { //这里输出的结果是Andy System.out.println(iterator.next()); } iterator = list.listIterator(); while (iterator.hasNext()) { //这里会输出两次,一次是Amy,一次是Andy System.out.println(iterator.next()); }
为什么第一个while循环中的println只输出一次,而且输出的还是Andy?
根据我们前面提到的“调用迭代器的add()方法会在迭代器位置之前添加一个元素”,在我们第一次拿到这个迭代器对象的引用时,迭代器的位置在Andy这个元素前面,通过调用迭代器的add()方法,Amy这个元素被添加到了迭代器所在位置的前面,这样在执行hasNext()方法时,迭代器对象中还有可供访问的元素Andy,返回true。再调用next()就返回了元素Andy的引用。
而在执行第二个while之前,重新拿到了迭代器对象的引用,此时迭代器的位置就位于Amy之前,由于迭代器对象中有两个可供访问的元素,因此println分别打印出了Amy和Andy。
②需要向链表的表尾添加元素
1)直接调用链表本身的add()方法
2)当迭代器的hasNext()方法返回false时,这说明迭代器已经越过链表的最后一个元素,再调用迭代器的add()方法
List<String> list = new LinkedList<String>(); list.add("Andy"); ListIterator<String> iterator = list.listIterator(); while (iterator.hasNext()) { iterator.next(); } iterator.add("Amy"); iterator = list.listIterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); }
③链表中有多少个位置是可以添加元素的
如果链表中有n个元素,那么就会有n+1个位置可以添加元素。例如链表中有AB两个元素,那么A的前面、AB的中间、B的后面都是可以添加元素的。
我们前面提到的add()方法(这里指链表本身的add()方法),是向链表的尾部添加元素。链表中还有一个set()方法,它是做什么用的呢?set()方法会将传入的新元素取代调用next()或previous()方法返回的元素,此时这个链表就被修改了。需要额外说明的是add()、remove()方法是对链表结构性的修改,而set()方法不被视为结构性修改。
链表不支持快速随机访问,如果要查看链表中的第n个元素,就必须从头开始,越过n-1个元素。这意味着如果你使用get(2)来访问链表中的第三个元素,就必须越过前两个元素。如果要访问链表中第四个元素呢?get(3)这种方式好像可行,虽然它确实也访问到了第四个元素,但这一过程付出了越过前三个元素的代价。
想一想如果使用迭代器呢?当我们用迭代器访问到链表中第三个元素后,再去访问第四个元素会这么麻烦吗?
不会,因为此时迭代器只需要再向后移动,越过第四个元素,就能返回刚刚所越过的这个元素的引用。
鉴于此如果你需要通过整数索引来访问元素,那么就不应该选用链表。
那么说了这么多,我们在什么场景下选择链表呢,唯一理由就是想要尽量减少在列表中间插入或删除元素所付出的代价。
最后附几个与链表有关的笔试题:
一、请写出println打印的结果
LinkedList<String> list = new LinkedList<String>(); list.add("one"); list.add("two"); list.add("three"); list.add("four"); list.add("five");
ListIterator<String> iteratorOne = list.listIterator(2); ListIterator<String> iteratorTwo = list.listIterator(iteratorOne.nextIndex());
if (iteratorTwo.hasNext()) { System.out.println(iteratorTwo.next()); }
二、请写出各println打印的结果
List<String> a = new LinkedList<>(); a.add("Amy"); a.add("Carl"); a.add("Erica"); List<String> b = new LinkedList<>(); b.add("Bob"); b.add("Doug"); b.add("Frances"); b.add("Gloria"); ListIterator<String> aIter = a.listIterator(); Iterator<String> bIter = b.iterator(); while (bIter.hasNext()) { if (aIter.hasNext()) aIter.next(); aIter.add(bIter.next()); } System.out.println(a); bIter = b.iterator(); while (bIter.hasNext()) { bIter.next(); if (bIter.hasNext()) { bIter.next(); bIter.remove(); } } System.out.println(b); a.removeAll(b); System.out.println(a);
Java学习分享-->集合-->链表