免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
用Python實現(xiàn)一個 LinkedList雙向鏈表

提到 LinkedList,我相信大部分 Java 開發(fā)者是知道的。但 Pythonner 也許并不知道。

在分享之前,我先說說為什么寫這篇文章。

大部分讀者知道我是一名 Android 開發(fā)者,而我最熟悉的語言也正是 Java 。集合其實在 Java 是一個很重要的概念,而 LinkedList 也只是集合中的一個實現(xiàn)類而已。當(dāng)然 LinkedList 并不是 Java 中唯一的集合,但它卻是 Java 中使用雙向鏈表的一個代表類。

很多 Java 開發(fā)者也許知道雙向鏈表的結(jié)構(gòu)以及好處什么,畢竟 JDK 里面已經(jīng)提供好了 LinkedList 這個類。

這次我也借著 JDK 中 LinkedList 的實現(xiàn)原理,用 Python 來實現(xiàn)一個這樣的數(shù)據(jù)結(jié)構(gòu)。這篇文章也將會使你更加深入的了解這個數(shù)據(jù)結(jié)構(gòu)。

Ok,正文...

至于“集合”是什么就不用細(xì)說,可以理解為一組元素的合集。

其實提到 LinkedList ,會有一個相對的集合 ArrayList。這兩個都是 Collection (集合) 的實現(xiàn)類。當(dāng)然這兩個也是有區(qū)別的,ArrayList 內(nèi)部是由數(shù)組實現(xiàn)的,而 LinkedList 則是由鏈表(雙向)實現(xiàn)的。

為什么會有這種數(shù)據(jù)結(jié)構(gòu)的集合?

在討論這個之前,我先科普下這兩個數(shù)據(jù)結(jié)構(gòu)。

數(shù)組大家應(yīng)該都知道,可以理解為一組線性、連續(xù)存儲的數(shù)據(jù)結(jié)構(gòu)。而鏈表則是由指針(指向)關(guān)系起來的一個數(shù)據(jù)結(jié)構(gòu)。

比如:

雙向鏈表的意思則是既有一個 prev(前驅(qū)指針) ,又有一個 next(后驅(qū)指針)來構(gòu)成的一種數(shù)據(jù)結(jié)構(gòu)。這兩個則是為了構(gòu)建出節(jié)點之間的關(guān)系。

再回到之前的問題,為什么存在這兩種數(shù)據(jù)結(jié)構(gòu)的集合?

大家可以想一下,如果我們要做 get 、set、 remove 操作那么針對 ArrayList 其實只需要花費常數(shù)時間,因為數(shù)組的索引大大提高了效率。

然而如果進行插入新值或者刪除操作時,效率則會很低。因為如果插入的是中間位置、或者最前端的位置,則需要對當(dāng)前操作位置后面的數(shù)據(jù)都要向后或者向前移動。

這個不難理解。

就像一個隊伍,如果你想插入到第一個位置,那么從第二個開始之后的每個人都需要向后移動一個位置。

因此這時候就需要鏈表這樣的數(shù)據(jù)結(jié)構(gòu)。每個節(jié)點都存在一個前驅(qū)和后驅(qū),我們只要修改指針指向的節(jié)點就可以完成插入或者刪除操作。

再拿一個隊伍舉例子:

在這個隊伍中每一個人都知道了自己前面是誰、后面是誰。那么當(dāng)你插入到第一個位置的時候,你只需要告訴隊伍的第一個人你在他前面即可,而后面的人也不需要關(guān)注自己的位置在哪,他們只需要關(guān)注自己前面和后面的人是誰。

不過這里需要注意一點,像這樣的雙向鏈表會出現(xiàn)最前端沒有前驅(qū)指針,后端沒有后驅(qū)指針。因此雙向鏈表會在前面追加一個 頭節(jié)點、后端增加一個 尾節(jié)點。也可以稱之為 “標(biāo)記節(jié)點”。

當(dāng)我們對鏈表進行 get 或者 set 操作的時候,其實也是需要花費常數(shù)時間。但鏈表的 get 其實效率比數(shù)組的低,因為鏈表的缺點就是不容易做索引,它不像數(shù)組可以有索引來查找。

不過鏈表的查找為了提高效率,也可以做相應(yīng)的優(yōu)化。比如雙向鏈表的一個特點就是兩端都可以作為遍歷的起點。

這樣做的好處就是,如果 查找 的位置是鏈表靠后的位置那么就可以直接從尾部開始向前查找。

當(dāng)然這點并不是鏈表唯一的一個優(yōu)點,另一個優(yōu)點則是對 remove 和 insert 的操作只需要花費常數(shù)時間。因為鏈表的刪除和插入只需要查找到對應(yīng)位置,然后修改指針即可。

所以綜上,我們?nèi)绻麑σ粋€集合的查找頻率比較高那么最好用數(shù)組作為數(shù)據(jù)結(jié)構(gòu),但如果對插入、刪除頻率比較高,那么選用鏈表則是一個高效率的決定。

扯了這么多,其實這兩種集合在 JDK 中已經(jīng)有提供。

那么 Python 能否也實現(xiàn)這樣的集合呢?答案是肯定的。

不過這篇文章只會去實現(xiàn) LinkedList (雙向鏈表)的集合,另一種 ArrayList(數(shù)組)不做實現(xiàn)。

在實現(xiàn) LinkedList 的過程中,我會用到 ”抽象類“ 的概念。這是為了讓代碼結(jié)構(gòu)更清晰,我將 LinkedList 對外提供的方法抽象出來,然后在子類中具體實現(xiàn)即可。

Ok~

編碼

1、在我們開始編寫 LinkedList 之前先考慮下需要定義哪些抽象方法。

首先我這邊先創(chuàng)建一個 Collection 的抽象類,而這個抽象類中提供了如下方法:

心細(xì)的朋友可能會注意到有個 iterator 的抽象方法。這個方法將會返回一個迭代器,用于我們對 LinkedList 的迭代,后面會講到。

不過這里我可以提一下,其實 Python 源碼中有一個抽象類 Iterator ,它的抽象方法是 next 。

2、接下來,我們就可以這樣來定義 LinkedList 類:

class LinkedList(Collection):

讓 LinedList 繼承自 Collection ,來實現(xiàn)我們的具體方法。

3、到這里我們的集合類已經(jīng)準(zhǔn)備好,但還缺個東西就是數(shù)據(jù)類。也可以理解為元素類,不過在鏈表中的元素也可以叫做節(jié)點。

如上是一個節(jié)點類,節(jié)點類中包括了當(dāng)前節(jié)點的數(shù)據(jù)、當(dāng)前節(jié)點的前驅(qū)和當(dāng)前節(jié)點的后驅(qū)。

4、在 Collection 中有個抽象方法 iterator 。這里我們還需要定義一個類,這個類則是自定義的迭代器。我們在 iterator 的方法中返回這個實例就行。

如上,繼承自 Iterator 的 子類。我們在構(gòu)造器中增加了 outter 這個入?yún)ⅲ且驗槲覀冊?LinkedListItertor 中需要使用到 LinkedList 中的參數(shù),因此我們在構(gòu)造器中增加 outter 傳入 LinkedList 的實例

5、開始實現(xiàn) LinkedList

上面幾步已經(jīng)提供了我們編寫 LinkedList 需要的基礎(chǔ)類,那么接下來我們就可以具體來實現(xiàn)邏輯了。

在實現(xiàn)邏輯之前需要再次提一下:前面的內(nèi)容有提到兩個”標(biāo)記節(jié)點“,一個頭節(jié)點、一個尾節(jié)點。

因此我們需要在構(gòu)造器中就增加這兩個標(biāo)記節(jié)點。

A、構(gòu)造器

之所以把這段邏輯單獨抽離到 doClear 中,是因為這段邏輯也是一個清空數(shù)據(jù)的邏輯。

B、addIndex(self,index,data)

接下來就實現(xiàn) ”在指定位置插入數(shù)據(jù)“。在鏈表中我們要插入一個數(shù)據(jù),就必須先拿到鏈表中當(dāng)前位置的節(jié)點。然后對其前驅(qū)進行指向即可。(回憶 前面的一個隊伍的例子)

所以我們需要先提供一個

getNodeWithIndex(index)

方法來獲取對應(yīng)位置的節(jié)點(Node)

上面這段代碼可以看到,我將邏輯實現(xiàn)放到了

getNodeWithIndexLowerUpper(index,lower,upper) 中。

這么做的原因也是上面提到的,既然這是一個雙向鏈表,那么我們當(dāng)然可以選擇性的從兩端開始遍歷。

即 :如果當(dāng)前 index 靠后 ,則從尾部開始向前遍歷。靠前的話 則從頭部向后遍歷。

既然 getNodeWithIndex 我們已經(jīng)寫好了,那么接下來就可以完善 add 方法了。首先思考下,add 到指定 index 中的含義其實是需要先拿到當(dāng)前 index 的 Node,然后將需要添加的 Node 插入到這個位置即可。

如上:

首先我們創(chuàng)建一個前驅(qū)指向當(dāng)前位置節(jié)點的前驅(qū),后驅(qū)指向當(dāng)前節(jié)點的 Node.

然后通過變換當(dāng)前節(jié)點前驅(qū)的指針和當(dāng)前位置的前驅(qū)就實現(xiàn)了插入的操作。

C、addData(self,data)

接下來我們來實現(xiàn)在尾部插入一個 Node。 其實就是在鏈表的尾部添加數(shù)據(jù)即可,就相當(dāng)于在 size() 的位置插入數(shù)據(jù)。調(diào)用 addIndex(size(),data) 就行。

D、size(self)

size 方法其實就很簡單了,因為我們每次 add 的時候會對 thisSize 進行自增。因此 thisSize 必然就是這個鏈表的長度。

E、isEmpty(self)

直接判斷 thisSize 即可。

F、remove(self,index)

理解了 add 的邏輯,那么 remove 則比較簡單。我們拿到需要 remove 的 Node 然后修改前驅(qū)和后驅(qū)即可。

OK~ 以上就將所有基礎(chǔ)的抽象方法已經(jīng)實現(xiàn)。接下來,就可以實現(xiàn)一個迭代器,用來迭代我們編寫的 LinkedList。

H、iterator(self)

在這里我們需要自定義一個迭代器。

其實自定義一個 Python 的迭代器也是很簡單的。

我們只需要繼承 Iterator,然后在抽象方法 next 中將數(shù)據(jù)遍歷即可。

當(dāng)然迭代器作用無疑是迭代操作,因此我們需要增加中斷迭代的條件。

其中 current 表示當(dāng)前節(jié)點的位置(指針),outter 是外部類(LinkedList)的引用。

在構(gòu)造器中我們將當(dāng)前指針初始到 beginMarker 的Next 節(jié)點,也就是第一個數(shù)據(jù)。

然后重寫 Iterator 的 next 方法,一直遍歷到指針指向 endMarker 。也就是指針到達(dá)尾節(jié)點的時候就中斷迭代。

這時候,我們只需要實現(xiàn) next 這個方法即可。

返回一個自定義迭代器的實例即可,入?yún)⑹钱?dāng)前 LinkedList 實例。

最后我們來測試一下這個 LinkedList

打印結(jié)果如下:

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
java主要集合類的數(shù)據(jù)結(jié)構(gòu)學(xué)習(xí)
Java集合類LinkedList淺析
Vector,ArrayList,LinkedList的特點和區(qū)別
鏈表 面試題(Java)
雙向鏈表的插入及刪除圖解
詳解雙向鏈表的基本操作(C語言)
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服