在瞭解完什麼是數據結構以後,讓咱們一塊兒來探索下數據結構中常見的一種—鏈表。面試
鏈表是數據結構之一,其中的數據呈線性排列。在鏈表中,數據的添加和刪除都較爲方便,就是訪問比較耗費時間。算法
如上圖所示就是鏈表的概念圖,Blue、Yellow、Red 這 3 個字符串做爲數據被存儲於鏈表中,也就是數據域,每一個數據都有 1 個指針,即指針域,它指向下一個數據的內存地址,其中 Red 是最後 1 個數據,Red 的指針不指向任何位置,也就是爲 NULL,指向 NULL 的指針一般被稱爲空指針。數組
在鏈表中,數據通常都是分散存儲於內存中的,無須存儲在連續空間內。數據結構
由於數據都是分散存儲的,因此若是想要訪問數據,只能從第 1 個數據開始,順着指針的指向一一往下訪問(這即是順序訪問)。好比,想要找到 Red 這一數據,就得從 Blue 開始訪問,這以後,還要通過 Yellow,咱們才能找到 Red。ide
若是想要添加數據,只須要改變添加位置先後的指針指向就能夠,很是簡單。好比,在 Blue 和 Yellow 之間添加 Green。線程
首先將 Blue 的指針指向的位置變成 Green,而後再把 Green 的指針指向 Yellow,數據的添加就大功告成了。設計
數據的刪除也同樣,只要改變指針的指向就能夠,好比刪除 Yellow。3d
這時,只須要把 Green 指針指向的位置從 Yellow 變成 Red,刪除就完成了。雖然 Yellow 自己還存儲在內存中,可是無論從哪裏都沒法訪問這個數據,因此也就沒有特地去刪除它的必要了。從此須要用到 Yellow 所在的存儲空間時,只要用新數據覆蓋掉就能夠了。指針
那麼對鏈表的操做所需的運行時間究竟是多少呢?在這裏,咱們把鏈表中的數據量記成 n。訪問數據時,咱們須要從鏈表頭部開始查找(線性查找),若是目標數據在鏈表最後的話,須要的時間就是 O(n)。code
另外,添加數據只須要更改兩個指針的指向,因此耗費的時間與 n 無關。若是已經到達了添加數據的位置,那麼添加操做只需花費 O(1)的時間,刪除數據一樣也只需 O(1)的時間。
在對鏈表有了大概的認識之後,咱們用 Java 去實現屬於本身的鏈表:
public class ListNode { int val; ListNode next; ListNode(int x) { val = x; } } class MyLinkedList { int size; /** * 哨兵節點做爲僞頭 */ ListNode head; public MyLinkedList() { size = 0; head = new ListNode(0); } /** * 獲取鏈表第 index 個節點的值。若是索引是無效的,返回-1 * * @param index * @return */ public int get(int index) { // 若索引無效 if (index < 0 || index >= size) { return -1; } ListNode curr = head; // 從僞頭節點開始,向前走 index+1 步 for (int i = 0; i < index + 1; ++i) { curr = curr.next; } return curr.val; } /** * 在頭部插入節點 * * @param val */ public void addAtHead(int val) { addAtIndex(0, val); } /** * 在尾部插入節點 * * @param val */ public void addAtTail(int val) { addAtIndex(size, val); } /** * 在鏈表中的第 index 個節點前添加值爲 val 的一個節點。若是 index 等於鏈表的長度時,節點將被添加到鏈表的末尾。若是 index 大於鏈表長度,節點將沒法插入 * * @param index * @param val */ public void addAtIndex(int index, int val) { // 若是index大於長度,則不會插入該節點。 if (index > size) { return; } // 若是 index 爲負,則該節點將插入列表的開頭。 if (index < 0) { index = 0; } ++size; // 查找要添加的節點的前驅節點 ListNode pred = head; for (int i = 0; i < index; ++i) { pred = pred.next; } // 要添加的節點 ListNode toAdd = new ListNode(val); // 經過改變 next 來插入節點 toAdd.next = pred.next; pred.next = toAdd; } /** * 若是 index 是有效的,刪除鏈表中的第 index 個節點 * * @param index */ public void deleteAtIndex(int index) { // 若是 index 無效,則不執行任何操做 if (index < 0 || index >= size) { return; } size--; // 找到要刪除節點的前驅節點 ListNode pred = head; for (int i = 0; i < index; ++i) { pred = pred.next; } // 經過改變 next 來刪除節點 pred.next = pred.next.next; } }
到這裏,我相信你們應該對鏈表有了進一步的理解,你們能夠用不一樣的語言去設計實現下。
以上講述的鏈表是最基本的一種鏈表,除此以外,還存在幾種擴展方便的鏈表。
雖然上文中提到的鏈表在尾部沒有指針,但咱們也能夠在鏈表尾部使用指針,而且讓它指向鏈表頭部的數據,將鏈表變成環形,這即是循環鏈表,也叫環形鏈表。循環鏈表沒有頭和尾的概念,想要保存數量固定的最新數據時一般會使用這種鏈表。
循環鏈表
另外,以上提到的鏈表裏的每一個數據都只有一個指針,但咱們能夠把指針設定爲兩個,而且讓它們分別指向先後數據,這就是雙向鏈表。使用這種鏈表,不只能夠從前日後,還能夠從後往前遍歷數據,十分方便。
可是,雙向鏈表存在兩個缺點:一是指針數的增長會致使存儲空間需求增長;二是添加和刪除數據時須要改變動多指針的指向。
雙向鏈表
看完以後,相信你們都對鏈表和鏈表的基本操做有了必定的瞭解,還對循環鏈表和雙向鏈表有了初步的認識,你們可使用本身喜歡的語言去設計實現下單向鏈表,有能力的話能夠把循環鏈表和雙向鏈表也實現下。
說完鏈表,固然不能忘記常常和鏈表同時出如今面試官口中的—數組,將在接下來的文章對其進行展開介紹。
參考
《個人第一本算法書》
完
●什麼是數據結構?
●實現線程的方式到底有幾種?
●Full GC 和 Minor GC,傻傻分不清楚
武培軒有幫助?在看,轉發走一波喜歡做者