我拿單鏈表的插入操做爲例來給你分析一下node
如圖所示,咱們但願在結點a和相鄰的結點b之間插入結點x,假設當前指針p指向結點a。若是咱們將代碼實現變成下面這個樣子,就會發⽣指針丟失和內存泄露。python
p->next = x; // 將 p 的 next 指針指向 x 結點; x->next = p->next; // 將 x 的結點的 next 指針指向 b 結點;
new_node->next = p->next; p->next = new_node;
剛剛的邏輯就不能用了、須要進行下面這樣的特殊處理、其中head表示鏈表的頭結點、對於單鏈表的插入操做,第一個結點和其餘結點的插入邏輯是不同面試
if (head == null) { head = new_node; }
若是要刪除結點P的後繼結點,咱們只須要一行代碼就能夠搞定編程
p->next = p->next->next;
跟插入相似數組
if (head->next == null) { head = null; }
從前面的一步一步分析,咱們能夠看出,針對鏈表的插入、刪除操做,須要對插入第一個結點和刪除最後一個結點的狀況進行特殊處理。這樣代碼實現起來就會很繁瑣,不簡潔,並且也容易由於考慮不全而出錯。如何來解決這個問題呢?性能
我畫了一個帶頭鏈表,你能夠發現,哨兵結點是不存儲數據的。由於哨兵結點一直存在,因此插入第一個結點和插入其餘結點,刪除最後一個結點和刪除其餘結點,均可以統一爲相同的代碼實現邏輯了。spa
我再舉一個很是簡單的例子。代碼我是用C語言語實現的,不涉及語言方面的高級語法、很容易看懂,你能夠類比到你熟悉的語。3d
代碼一指針
// 在數組 a 中,查找 key,返回 key 所在的位置 // 其中,n 表示數組 a 的長度 int find(char* a, int n, char key) { // 邊界條件處理,若是 a 爲空,或者 n<=0,說明數組中沒有數據,就不用 while 循環比較了 if(a == null || n <= 0) { return -1; } int i = 0; // 這裏有兩個比較操做:i<n 和 a[i]==key. while (i < n) { if (a[i] == key) { return i; } ++i; } return -1; }
代碼二調試
// 在數組 a 中,查找 key,返回 key 所在的位置 // 其中,n 表示數組 a 的長度 // 我舉 2 個例子,你能夠拿例子走一下代碼 // a = {4, 2, 3, 5, 9, 6} n=6 key = 7 // a = {4, 2, 3, 5, 9, 6} n=6 key = 6 int find(char* a, int n, char key) { if(a == null || n <= 0) { return -1; } // 這裏由於要將 a[n-1] 的值替換成 key,因此要特殊處理這個值 if (a[n-1] == key) { return n-1; } // 把 a[n-1] 的值臨時保存在變量 tmp 中,以便以後恢復。tmp=6。 // 之因此這樣作的目的是:但願 find() 代碼不要改變 a 數組中的內容 char tmp = a[n-1]; // 把 key 的值放到 a[n-1] 中,此時 a = {4, 2, 3, 5, 9, 7} a[n-1] = key; int i = 0; // while 循環比起代碼一,少了 i<n 這個比較操做 while (a[i] != key) { ++i; } // 恢復 a[n-1] 原來的值, 此時 a= {4, 2, 3, 5, 9, 6} a[n-1] = tmp; if (i == n-1) { // 若是 i == n-1 說明,在 0...n-2 之間都沒有 key,因此返回 -1 return -1; } else { // 不然,返回 i,就是等於 key 值的元素的下標 return i; } }
對比兩段代碼,在字符串a很⻓的時候,好比幾萬、幾十萬,你以爲哪段代碼運行得更快點呢?答案是代碼二,由於兩段代碼中執行次數最多就是while循環那一部分。第二段代碼中,咱們經過⼀個哨兵a[n-1] = key
,成功省掉了一個個⽐較語句i<n,不要小看這一條語句,當累積執行萬次、幾十萬次時,累積的時間就很明顯了。
固然,這只是爲了舉例說明哨兵的做用,你寫代碼的時候千萬不要寫第⼆段那樣的代碼,由於可讀性太差了。大部分狀況下,咱們並不須要如此追求極致的性能。
你能夠找個個具體的例子,把它畫在紙上,釋放一些腦容量,留更多的給邏輯思考,這樣就會感受到思路清晰不少。好比往單鏈表中插入一個數據這樣一個操做,我通常都是把各類狀況都舉一個例子,畫
出插入前和插入後的鏈表變化,如圖所示:
看圖寫代碼,是否是就簡單多啦,並且咱們寫完代碼以後,也能夠舉幾個例子、畫在紙上,照着代碼走一遍,很容易就能發現代碼中的Bug
若是你已經理解並掌握了我前面所講的方法法,可是手寫鏈表代碼仍是會出現各類各樣的錯誤,也不要着急。由於我最開始學的時候,這種情況也持續了一段時間。
如今我寫這些代碼,簡直就和「玩兒」同樣,其實也沒有什麼技巧,就是把常見的鏈表操做都本身多寫幾遍,出問題就一點一點調試,熟能生巧!
因此,我精選了5個常⻅的鏈表操做。你只要把這幾個操做都能寫熟練,不熟就多寫幾遍,我保證你以後不再會懼怕寫鏈表代碼。
我以爲寫鏈表代碼是最考驗邏輯思惟能力的,由於鏈表代碼導出都是指針的操做、邊界條件的處理,稍有不慎就容易產生Bug鏈表代碼寫的好壞,能夠看出一我的寫代碼是否夠細心,
考慮問題是否全面、思惟是否縝密、因此,這也是不少面試官喜歡讓人手寫鏈表代碼的緣由,因此,這一節講到的東西,你必定要本身寫代碼實現一下,纔有效果