上一篇文章中介紹了幾種常見鏈表的含義,今天介紹下如何寫出正確的鏈表代碼。node
咱們通常設計的鏈表有兩個類。Node
類用來表示節點,LinkedList
類提供了一些輔助方法,好比說結點的增刪改查,以及顯示列表元素等方法。 接下來看看如何用 js
代碼表示一個鏈表。git
代碼演示:github
{
var Node = function(data) {
this.data = data;
this.next = null;
};
var node1 = new Node(1);
var node2 = new Node(2);
var node3 = new Node(3);
node1.next = node2;
node2.next = node3;
console.log(node1.data);
console.log(node1.next.data);
console.log(node1.next.next.data);
}
複製代碼
Node
類包含兩個屬性:data
用來保存節點上的數據,next
用來保存指向下一個節點的連接。算法
{
var LList = function() {
this.head = new Node('head');
this.find = find;
this.insert = insert;
this.remove = remove;
this.display = display;
}
}
複製代碼
LList
類提供了對鏈表進行操做的方法。該類中使用一個 Node
類來保存鏈表中的頭結點。當有新元素插入時,next
就會指向新的元素。bash
怎麼樣,簡單吧,你已經學會鏈表了數據結構
但,這只是基本的鏈表表示法,水還很深。app
把上一篇文章中的單鏈表圖搬過來,方便參考post
這裏的指針或者引用,他們的意思都是同樣的,都是存儲所指對象的內存地址。this
將某個變量賦值給指針,實際上就是將某個變量的地址賦值給指針,或者反過來講,指針中存儲了這個變量的內存地址,指向了這個變量,經過指針就能找到這個變量。spa
看下面的僞代碼表示什麼意思:
p -> next = q;
複製代碼
這行代碼就是說 p 結點中的 next 指針存儲了 q 結點的內存地址。
再看下面的代碼表示什麼:
p -> next = p -> next -> next;
複製代碼
這行代碼表示,p 結點的 next 指針存儲了 p 結點的下下一個結點的內存地址。
如今應該能有所體會指針或者引用的概念了吧。
在寫鏈表代碼的時候,尤爲是咱們的指針,會不斷的改變,指來指去的。因此在寫的時候,必定注意不要弄丟了指針。
以下圖中單鏈表的插入操做
咱們但願在結點 B 和相鄰的結點 C 之間插入 D 結點。假設當前指針 p 指向 B 結點。若是你將代碼實現成下面這個樣子,就會發生指針丟失和內存泄露。
// 僞代碼
p -> next = d; // 將 p 的 next 指針指向 D 結點;
d -> next = p -> next // 將 D 的結點的 next 指針指向 C? 結點。
複製代碼
咱們來分析下:p -> next 指針在完成第一步操做後,已經再也不指向結點 C 了,而是指向新增長的結點 D。第二行的代碼至關於將 D 賦值給 d->next,本身指向本身。所以,整個鏈表也就被截斷了。
因此咱們添加結點時,必定要注意操做的順序,要先將結點 D 的 next 指針指向結點 C,再把結點 B 的指針指向 D,這樣纔不會丟失指針。對於剛纔的那段代碼,你知道怎麼修改纔是正確的了吧。
首先來回顧下剛纔所說的單鏈表的插入操做。若是在 p 結點後面增長一個新的結點,只須要關注如下兩步便可。
new_node -> next = p -> next;
p -> next = new_node;
複製代碼
可是,當咱們向一個空鏈表中插入第一個結點時,就須要特殊處理了。當鏈表爲空時,也就是鏈表的head爲空,那直接賦值便可,以下:
if(head == null) {
head = new_node;
}
複製代碼
看一段完整的添加節點代碼:
// 添加一個新結點 tail:表示尾結點
append(data) {
const node = new Node(data);
if (!this.head) {
this.head = node;
this.tail = this.head;
} else {
this.tail.next = node;
this.tail = node;
}
}
複製代碼
若是頭結點不存在的話,頭結點等於尾結點。若是頭結點存在的話,利用尾結點來擴充鏈表的數據,別忘了再移動 tail
成爲尾結點。
再來看單鏈表結點的刪除操做。若是在p結點後刪除一個結點,只須要關注一步便可:
p -> next = p -> next -> next;
複製代碼
可是,當鏈表中只剩一個結點head時,也須要特殊處理才能夠,以下:
if(head -> next == null){
head = null;
}
複製代碼
刪除的代碼邏輯請查看 github-鏈表-remove
我提供的方法比較繁瑣,當閱讀了《數據結構與算法JavaScript描述》的時候,發現書上寫的方法特別簡潔。在此分享一下。
如下是此書中的刪除結點代碼:
{
/* 首先根據要刪除的元素,檢查每個結點的下一個結點中是否存儲着要刪除的數據。 若是找到,則返回該結點,即前一個結點。 */
function findPvevious(item) {
var currNode = this.head;
while(!(currNode.next == null) && (currNode.next.element != item)) {
currNode = currNode.next;
}
return currNode;
}
// 而後找到前一個結點後,利用上文提到的單鏈表刪除操做進行刪除。
function remove(item) {
var prevNode = this.findPvevious(item);
if(!(prevNode.next == null)) {
prevNode.next = prevNode.next.next;
}
}
}
複製代碼
因此寫鏈表代碼時,要常常注意邊界條件是否考慮到了:
好比一些單鏈表的增刪改查等,指針老是不斷的改變。這個時候若是大腦思考不過來的話,能夠簡單的畫個示意圖輔助一下。好比說單鏈表的增長結點操做,能夠畫出增長先後的鏈表變化。
請前往 github 查看 鏈表常見的方法
《數據結構與算法JavaScript描述》
鏈表這種數據結構,確實比較容易懂,可是想寫出好相關的操做代碼,確實不易,指針或者引用也是個人薄弱環節,指來指去便記不清該怎麼指啦!動物的關節對於動物來說很是的重要,指針感受就是鏈表的關節,鏈表像一輛火車,每一個車箱的鏈接全靠車箱間的鏈接軸。