動畫:面試如何輕鬆手寫鏈表?

寫在前邊

暑假參加的第一個公司的就讓我手寫一個雙向鏈表,並完成插入數據和刪除數據的操作。當時我很矇蔽,懵逼的不是思路,而是手寫,雖然寫出來了,但是很多邊界條件和代碼規範自我感覺不好,所以有了這些細心的總結。那麼今天的主題就是徒手寫鏈表,應聘者該如何下手?

我們通常寫鏈表準備應聘的時候,通常背加上理解,但是過了幾天又讓你寫。就會陌生了,雖然有點思路。還是模模糊糊,小鹿也有這個記性的“毛病”,“有毛病”就要治,怎麼治?我們必須在腦海裏形成一套可行的步驟和方法,在遇到手寫就不用手忙腳亂,而是穩穩當當,從頭到尾寫出一個漂亮的鏈表結構及操作。

一、熟悉結構

首先我們要知道鏈表的結構以及每個節點的結構,這是我們手寫鏈表的第一步,也是學習鏈表的第一步。我們知道,每個鏈表時這樣表示的:

那每個節點結構是由數據域和指針域組成,數據域是存放數據的,而指針域存放下一結點的地址。


我們可以通過數據域訪問到我們要的數據,而通過指針域訪問到當前結點以後的結點,那麼將這些結點串起來,就是一個鏈表。

那麼用代碼怎麼來表示呢?

我們通常會用到函數,我們如果將一個函數抽象成一個結點,那麼我們給函數添加兩個屬性,一個屬性是存放數據的屬性data,另一個屬性是存放指向下一個結點的指針屬性next。

你可能會問,如果我們有成千上萬個結點,要定義成千上萬個函數?有一個函數叫做構造函數,想必大家都聽說過,聲明一個構造函數就可以創造出成千上萬個結點實例。

function Node(data){
    this.data = data;
    this.next = null;
}

還有一個方法就是使用類的概念,在JavaScript中,類的概念在ES6纔出現,使用 Class 來聲明一個類,我們可以爲類添加兩個屬性,同上,一樣可以創造出多個結點實例。

class Node{
    constructor(data){
        this.data = data;
        this.next = null;
    }
}

二、理清思路

如果你把鏈表的結構搞得明明白白了,恭喜你,成功的進入第二關,但是你只超越了百分之20的人,繼續加油。

既然鏈表的結構弄明白了,那麼我們開始理思路,我們就先拿最簡單的單鏈表開刀,我們要完成兩個操作,插入數據和刪除數據。

如果我想插入數據,你可能會問,往哪裏插呢?有幾種插入的方法?

開始想,插入到單鏈表的頭部算一種。

在這裏插入圖片描述

然後插入到單鏈表的中間算一種。

在這裏插入圖片描述

插入到單鏈表尾部又算一種。

在這裏插入圖片描述

所有可能的情況就三種。那麼刪除呢?想必你也想到了,也一共三種,刪除頭部、刪除中間部分、刪除尾部。

PS:這是後期增加。

大家有沒有想過如果增加的結點是第一個結點,也就是在空鏈表中添加新結點的情況呢?所以寫代碼的時候要把這種特殊情況考慮進去。
然後還有刪除的情況,如果爲空鏈表了,就不能刪除了。如果刪除的是頭結點,還需要把頭指針向後移動一個,因爲當前的頭結點被刪除無效。

如果你覺的現在可以寫代碼了,那你就錯了,雖然我們的思路非常清晰,但是面試官僅僅考我們思路嗎?其實這一關你只打敗了百分之50%的人,最重點、最主要的是在下一個部分,邊界條件。

三、邊界條件

邊界條件是這五個步驟最容易犯錯的一部分,因爲通常考慮的不全面,導致了最後的面試未通過。如果想做好這一部分,也不難,跟着小鹿的方法走。

1、輸入邊界

首先我們先考慮用戶輸入的參數,比如傳入一個鏈表,我們首先要判斷鏈表是否爲空,如果爲空我們就不能讓它執行下邊的程序。再比如插入一個結點到指定結點的後邊,那麼你也要判斷輸入的結點是否爲空,而且還要判斷該結點是否存在該鏈表中。對於這些輸入值的判斷,小鹿給他同一起個名字叫做輸入邊界。

2、特殊邊界

特殊邊界考慮到一些特殊情況,比如插入數據,我們插入數據一般考慮到插入尾部,但是突然面試官插入到頭部,插入尾部的代碼並不適用於插入到頭部,所以呢需要考慮這種情況,刪除節點也是同樣思考。其實特殊邊界最主要考慮到一些邏輯上的特殊情況,考察應聘者的考慮的是否全面。小鹿給他起個名字叫做特殊邊界。

四、手寫代碼

1、定義結點

class Node{
    constructor(data){
        this.data = data;
        this.next = null;
    }
}

2、增加結點

咱們就以單鏈表中部添加數據爲例子,分解成每個步驟,每個步驟對應代碼如下:

2.1 保存臨時地址(4結點的地址),需要進行遍歷查找到3結點,也就是下列代碼的currentNode 結點。

在這裏插入圖片描述

//先查找該元素
let currentNode = this.findByValue(element);
// 保存 3 結點的下一結點地址(4 結點的地址)
let pre = currentNode.next

2.2 創建新結點,將新結點(5結點)的指針指向下一結點指針(4結點地址,已經在上一步驟保存下來了)

在這裏插入圖片描述

let newNode = new Node(value);
newNode.next = pre;

2.3 將3 的結點地址指向新結點(5結點)

在這裏插入圖片描述

currentNode.next = newNode;

以上步驟分析完畢,剩下的兩個在頭部插入和在尾部插入同樣的分析方式,將這兩個作爲練習題,課下自己試一試這個步驟。

2.4 刪除節點

刪除節點也分爲三種,頭部、中部、尾部,咱們就刪除中間結點爲例進行刪除,我們詳細看步驟操作。

我們先看刪除的全部動畫,然後再分步拆分。

在這裏插入圖片描述

斷開3結點的指針(斷開3結點相當於讓2結點直接指向4結點)

在這裏插入圖片描述

  let currentNode = this.head;
  // 用來記錄 3 結點的前一結點
  let preNode = null;
  // 遍歷查找 3 結點
  while(currentNode !== null && currentNode.data !== value){
         // 3 結點的前一結點
         preNode = currentNode;
         // 3 結點
         currentNode = currentNode.next;
}

讓結點2的指針指向4結點,完成刪除。

在這裏插入圖片描述

preNode.next = currentNode.next;

剩下的刪除頭結點和刪除尾結點同樣的步驟,自己動手嘗試下。

所有代碼實現:

  1/**
  2 * 2019/3/23
  3 * 公衆號:「一個不甘平凡的碼農」
  4 * @author 小鹿
  5 * 功能:單鏈表的插入、刪除、查找
  6 * 【插入】:插入到指定元素後方
  7 * 1、查找該元素是否存在?
  8 * 2、沒有找到返回 -1
  9 * 3、找到進行創建結點並插入鏈表。
 10 * 
 11 * 【查找】:按值查找/按索引查找
 12 * 1、判斷當前結點是否等於null,且是否等於給定值?
 13 * 2、判斷是否可以找到該值?
 14 * 3、沒有找到返回 -1;
 15 * 4、找到該值返回結點;
 16 * 
 17 * 【刪除】:按值刪除
 18 * 1、判斷是否找到該值?
 19 * 2、找到記錄前結點,進行刪除;
 20 * 3、找不到直接返回-1;
 21 */
 22//定義結點
 23class Node{
 24    constructor(data){
 25        this.data = data;
 26        this.next = null;
 27    }
 28}
 29
 30//定義鏈表
 31class LinkList{
 32    constructor(){
 33        //初始化頭結點
 34        this.head = new Node('head');
 35    }
 36
 37    //根據 value 查找結點
 38    findByValue = (value) =>{
 39        let currentNode = this.head;
 40        while(currentNode !== null && currentNode.data !== value){
 41            currentNode = currentNode.next;
 42        }
 43        //判斷該結點是否找到
 44        console.log(currentNode)
 45        return currentNode === null ? -1 : currentNode;
 46    }
 47
 48    //根據 index 查找結點
 49    findByIndex = (index) =>{
 50        let pos = 0;
 51        let currentNode = this.head;
 52        while(currentNode !== null && pos !== index){
 53            currentNode = currentNode.next;
 54            pos++;
 55        }
 56        //判斷是否找到該索引
 57        console.log(currentNode)
 58        return currentNode === null ? -1 : currentNode;
 59    }
 60
 61    //插入元素(指定元素向後插入)
 62    insert = (value,element) =>{
 63        //先查找該元素
 64        let currentNode = this.findByValue(element);
 65        //如果沒有找到
 66        if(currentNode == -1){
 67            console.log("未找到插入位置!")
 68            return;
 69        }
 70        let newNode = new Node(value);
 71        newNode.next = currentNode.next;
 72        currentNode.next = newNode;
 73    }
 74
 75    //根據值刪除結點
 76    delete = (value) =>{
 77        let currentNode = this.head;
 78        let preNode = null;
 79        while(currentNode !== null && currentNode.data !== value){
 80            preNode = currentNode;
 81            currentNode = currentNode.next;
 82        }
 83        if(currentNode == null) return -1; 
 84        preNode.next = currentNode.next;
 85    }
 86
 87     //遍歷所有結點
 88    print = () =>{
 89        let currentNode = this.head
 90        //如果結點不爲空
 91        while(currentNode !== null){
 92            console.log(currentNode.data)
 93            currentNode = currentNode.next;
 94        }
 95    }
 96}
 97
 98//測試
 99const list = new LinkList()
100list.insert('xiao','head');
101list.insert('lu','xiao');
102list.insert('ni','head');
103list.insert('hellow','head');
104list.print()
105console.log('-------------刪除元素------------')
106list.delete('ni')
107list.delete('xiao')
108list.print()
109console.log('-------------按值查找------------')
110list.findByValue('lu')
111console.log('-------------按索引查找------------')
112list.print()

五、測試用例

其實這裏的測試用例主要用於判斷我們寫的程序到底對不對,我們一般都會輸入一個自己認爲的情況進行測試,這是錯誤的做法。程序最容易出錯的還是邊界情況考慮不全面導致的,所以,我們爲了能夠測試程序的全面性,所以我們要按照以下步驟進行全面性的測試。

1、普通測試

普通測試就是輸入一個正常的值,比如單鏈表中插入數據

2、特殊測試

特殊輸入可以參照上邊邊界條件中的特殊邊界進行測試,比如在頭部插入數據,在尾部插入數據等特殊情況的測試。

3、輸入測試

對於輸入測試的內容參考上邊邊界條件中的輸入邊界,比如:輸入一個空鏈表測試一下程序能否正常的運行。

六、小結

做一個小結。今天的手寫鏈表主要從五部分下手,從前到後依次爲熟悉結構、理清思路、手寫代碼、測試用例。以後無論手寫什麼代碼,有五步走,對於面試完全沒有問題啦。

通過小鹿總結手寫鏈表的方法,不用刻意去背,只要把思路理清楚,邊界條件考慮全面,就不用去背,重複的練習。


下一篇:別再翻了,面試二叉樹看這 11 個就夠了~

推薦閱讀:

1、動畫:用動畫給面試官解釋 TCP 三次握手過程

2、動畫:用動畫給女朋友講解 TCP 四次分手過程



❤️ 不要忘記留下你學習的腳印 [點贊 + 收藏 + 評論]

文章都看完了,爲何不妨點個贊呢?嘻嘻,那就說明你很自私,你怕那麼好的文章讓別人也看到。開個小小玩笑。

其實我也很自私,我把我的一直以來堅持原創的公衆號:「小鹿動畫學編程」偷偷給你,裏邊匯聚了小鹿以動畫形式講解的數據結構與算法、網絡原理、Web 等技術文章。
在這裏插入圖片描述

動一動你的小手,點贊就完事了,每個人出一份力量(點贊 + 評論)就會讓更多的學習者加入進來!非常感謝! ̄ω ̄=


作者Info:

【作者】:小鹿

【原創公衆號】:小鹿動畫學編程。

【簡介】:和小鹿同學一起用動畫的方式從零基礎學編程,將 Web前端領域、數據結構與算法、網絡原理等通俗易懂的呈獻給小夥伴。先定個小目標,原創 1000 篇的動畫技術文章,和各位小夥伴共同努力一起學習!公衆號回覆 “資料” 送一從零自學資料大禮包!

【轉載說明】:轉載請說明出處,謝謝合作!~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章