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學習分享-->集合-->鏈表