1. 程式人生 > >資料結構與演算法之連結串列篇(下)

資料結構與演算法之連結串列篇(下)

Q:如何輕鬆寫出正確的連結串列程式碼?

總結起來,就是投入時間+技巧;

一、投入時間:

         只要願意投入時間,大多數人都是可以學會的,比如說,如果你真能花上一個週末或者一整天時間,就去寫連結串列反轉這一個程式碼,多謝幾遍,一直練到能毫不費力地寫出Bug free的程式碼。

二、技巧:

1、理解技巧或引用的含義

        連結串列的結構並不是很難,但是一旦它和指標混在一起,就容易讓人摸不著頭腦,所以要寫對連結串列程式碼,首先要理解好指標。

        有些語言有“指標”概念,比如C語言;有些語言沒有指標,取而代之的是“引用”,比如:Java,Python,實際上都是儲存所指物件的記憶體地址。

        指標的理解,可以總結為下面一句話:

        將某個變數賦值給指標,實際上就是將這個變數的地址賦值給指標,或者反過來,指標中儲存了這個變數的地址,指向了這個變數,通過指標就可一找到這個變數。舉個例子:

         在編寫連結串列程式碼的時候,我們經常會這樣寫程式碼:p->next=q;這行程式碼就是說,p結點中的next指標儲存了q結點的記憶體地址;

       複雜一點:p->next=p->next->next,這行程式碼就是說,p結點的next指標儲存了p結點的下下一個結點的記憶體地址。

    2、警惕指標丟失和記憶體洩漏

        以單鏈表的插入操作為例,來進行分析:

         在結點a和相鄰的結點b之間插入結點x,假設當前指標p指向結點a。如果我們將程式碼實現成下面這個樣子,就會發生指標丟失和記憶體洩漏。

 p->next指標在完成第一步操作之後,已經不再指向結點b了,而是指向結點x。第2行程式碼相當於將x賦值給x-next,自己指向自己。因此,整個連結串列也就斷成了兩半,從結點b往後的結點都無法訪問了。

         對於有些語言來說,比如C語言,記憶體管理是由程式設計師負責的,如果沒有手動釋放結點對應的記憶體空間,就會產生記憶體洩漏。所以我們插入結點時,一定要注意操作的順序,要先將結點x 的next指標指向結點b,再把結點a的next指標指向結點x ,對於剛剛插入程式碼,我們只需要將第一行和第二行的程式碼順序顛倒以下就可以。

        同理,刪除連結串列結點時,也一定要記得釋放記憶體空間,否則,也會出現記憶體洩漏的問題。當然對於像JAVA,就不必考慮這麼多。

3、利用哨兵簡化實現難度

但我們要向一個空連結串列中插入第一個結點,需要進行下面的特殊處理,其中head表示連結串列的頭結點。所以,對於單鏈表的插入操作,第一個結點和其他結點的插入邏輯是不一樣的。

對於單鏈表結點刪除操作,如果要刪除結點p的後繼結點,只需一行程式碼就可以搞定。

但是,如果我們要刪除連結串列中的最後一個結點,前面的刪除程式碼就失效了。那麼跟刪除類似,我們也需要對於種情況特殊處理。程式碼如下:

我們可以看出,對於連結串列的插入和刪除操作,需要對插入的第一個結點和刪除最後一個結點的情況進行特殊處理。程式碼實現起來比較繁瑣,那麼如何讓解決呢?

那麼我們就需要引入“哨兵”,由上面可知,head=null表示連結串列中沒有結點,其中head表示頭指標結點,指向連結串列的第一個結點。

如果引入“哨兵”結點,在任何時候,不管連結串列是不是空,head一直會指向這個哨兵結點,我們也把這種帶有“哨兵”結點的連結串列叫做帶頭連結串列,相反,沒有“哨兵”結點的連結串列叫做不帶頭連結串列。

下圖是一個帶頭連結串列,你可以發現,“哨兵”結點是不儲存資料的,因為“哨兵”結點一直存在,所以插入第一個結點和插入其他結點,刪除最後一個結點和刪除其他結點,都可以統一為相同的程式碼實現邏輯了。

這種用“哨兵"簡化變成難度技巧,在很多程式碼實現中都有用到,比如:插入排序,歸併排序、動態規劃等等。舉個例子感受一下:

         程式碼一:

程式碼二:

 對於兩段程式碼,在字串很長的時候,程式碼二執行更快,因為兩段程式碼中執行次數最多的部分就是while迴圈那一部分。第二段程式碼中,我們通過一個“哨兵”a[n-1]=key,成功省掉了一個比較語句i<n,不要小看這一條語句,當累積執行幾萬次的時候,累積的時間就很明顯了。

4、重點留意邊界條件處理

 檢查連結串列程式碼是否正確的邊界條件

(1)、如果連結串列為空,程式碼是否能正常工作?

(2)、如果連結串列只包含一個結點時,程式碼是否能正常工作?

(3)、如果連結串列只包含兩個結點時,程式碼是否能正常工作?

(4)、程式碼邏輯在處理頭結點和尾結點的時候,能否正常工作?

當然,邊界條件不止我列舉的那些,針對不同的場景,可能還有特定的邊界條件,需要具體思考,不過套路一樣。

5、舉例畫圖,輔助思考

舉個例子:往單鏈表中插入一個數據,我們可以把各種情況都列舉出來,畫出插入前與插入後的變化(如下圖)

6、多寫多練,沒有捷徑

對於不熟練的操作程式碼,多寫幾遍,出了問題一點一點除錯,熟能生巧。

Ps:五種常見的連結串列操作


歡迎大家掃碼關注微信公眾號,其中含有有大量免費的人工智慧、影象處理、IT資料:

                                                                             Change,There is no better way !