一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

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

 AnonymousV臉 2019-01-01

提到 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ǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    欧洲精品一区二区三区四区| 丰满人妻一二三区av| 国产精品一区二区有码| 日韩1区二区三区麻豆| 欧美一级特黄特色大色大片| 四十女人口红哪个色好看| 91人妻久久精品一区二区三区| 亚洲熟妇熟女久久精品| 国产欧美日韩综合精品二区| 久久香蕉综合网精品视频| 91人妻丝袜一区二区三区| 一区二区三区日本高清| 久久精品偷拍视频观看| 国产中文另类天堂二区| 欧美日韩国产精品第五页| 中文字幕五月婷婷免费| 又色又爽又黄的三级视频| 国产三级不卡在线观看视频| 成年女人午夜在线视频| 人人妻人人澡人人夜夜| 国产超碰在线观看免费| 欧美日韩国产黑人一区| 人妻内射精品一区二区| 午夜国产成人福利视频| 福利在线午夜绝顶三级| 亚洲男人天堂网在线视频| 操白丝女孩在线观看免费高清| 亚洲五月婷婷中文字幕| 色婷婷激情五月天丁香| 精品欧美国产一二三区| 国产亚洲系列91精品| 日韩一区二区三区18| 激情亚洲内射一区二区三区| 国产亚洲精品久久99| 国产日产欧美精品视频| 欧美一区二区三区十区| 国产欧美日韩在线一区二区| 日本免费一本一二区三区| 日本熟妇熟女久久综合| 中国一区二区三区人妻| 免费特黄一级一区二区三区|