javascript
JS版数据结构第三篇(链表)
單鏈表
定義
還是按照老規矩先看一下百度百科對單鏈表的定義
根據以上文字我們可以得出
- 單鏈表是一種鏈式的數據結構
- 它由一系列結點組成
- 每個結點包含兩個部分:
頭指針head和終端結點
- 單鏈表中每個結點的存儲地址是存放在其前趨結點next域(即它前一個節點的next指針)中
- 而開始結點無前趨,故應設頭指針head指向開始結點。
- 鏈表由頭指針唯一確定,單鏈表可以用頭指針的名字來命名。
- 終端結點無后繼,故終端結點的指針域為空,即NULL。
想象一下,每個結點都保存著指向下一個結點的指針,這樣只要給出頭指針head,
你就可以根據它獲取到鏈表中的任何一個元素了,將結點元素'連接'在一起就稱之為鏈表,
是不是還蠻形象的?
接下來我們可以嘗試自己設計一個單鏈表
例題-設計鏈表
LeetCode第707題? ??原題地址設計單鏈表的實現。單鏈表中的節點應該具有兩個屬性:val 和 next。val 是當前節點的值,next 是指向下一個節點的指針/引用。假設鏈表中的所有節點都是 0-index 的。
在鏈表類中實現這些功能:
- get(index):獲取鏈表中第 index 個節點的值。如果索引無效,則返回-1。
- addAtHead(val):在鏈表的第一個元素之前添加一個值為 val 的節點。插入后,新節點將成為鏈表的第一個節點。
- addAtTail(val):將值為 val 的節點追加到鏈表的最后一個元素。
- addAtIndex(index,val):在鏈表中的第 index 個節點之前添加值為 val 的節點。如果 index 等于鏈表的長度,則該節點將附加到鏈表的末尾。如果 index 大于鏈表長度,則不會插入節點。
- deleteAtIndex(index):如果索引 index 有效,則刪除鏈表中的第 index 個節點。
示例:
MyLinkedList linkedList = new MyLinkedList(); linkedList.addAtHead(1); linkedList.addAtTail(3); linkedList.addAtIndex(1,2); //鏈表變為1-> 2-> 3 linkedList.get(1); //返回2 linkedList.deleteAtIndex(1); //現在鏈表是1-> 3 linkedList.get(1); //返回3復制代碼題目要求我們設計一個基本的列表并實現增刪改查等基礎功能,在對單鏈表有一個基本的了解后應該很容易讀懂題目,我們直接開始解題
首先我們需要建立一個鏈表類(構造函數),這個也就是我們要設計的鏈表,它包含了:
_length?用于表示鏈表中的節點數量
head 分配一個節點作為鏈表的頭
- get(index)?根據索引查找節點對應的值
addAtHead(value) 向鏈表中第一個元素前添加一個節點
addAtTail(value) 向鏈表中最后元素后添加一個節點
addAtIndex(index,val)?在鏈表中的第 index 個節點之前添加值為 val 的節點
deleteAtIndex(index) 刪除指定位置的節點
然后我們需要一個節點類(構造函數),用來保存每個節點的value以及next屬性
data 存儲數據
next 指向鏈表中下一個節點的指針
兩個構造函數已經寫好了,接下來我們開始實現以下它的功能
- 方法1:? addAtHead(val)?
在鏈表的第一個元素之前添加一個值為 val 的節點,我們需要
- 方法2:addAtTail(val)
- 方法3:get(index)
- 方法4:addAtIndex(index,val)
代碼如下:
addAtIndex(index, val){// 若索引在0位之前 直接調用addAtHead方法if(index <= 0){return this.addAtHead(val)}// 如果index值大于0if(index >= 0 && index <= this._length){// 以val值實例化新結點let node = new Node(val)// 記錄當前遍歷的結點,初始為head let cur = this.head// 記錄遍歷次數let currentIndex = 0// 記錄當前遍歷結點的next指針let nextwhile(cur){if(currentIndex === index - 1){// 此時cur為要插入結點的前方結點// 用next保存插入元素前方結點的原next指針next = cur.nextcur.next = nodenode.next = nextthis._length++break}cur = cur.nextcurrentIndex++} } }復制代碼- 方法5:deleteAtIndex(index)
這樣我們就實現了一個完整的實現增刪改查功能的單鏈表了,完整代碼如下:
class Node {constructor (val) {this.value = valthis.next = null} } class MyLinkedList {constructor () {this._length = 0this.head = null}addAtHead (val) {let originHead = this.headthis.head = new Node(val)this.head.next = originHeadthis._length++}addAtTail (val) {let last = this.headlet node = new Node(val)while (last && last.next) {last = last.next}if (last) {last.next = node} else {this.head = node}this._length++}get (index) {let currentIndex = 0let cur = this.headif (index < 0 || this._length < index + 1) {return -1}while (cur) {if (currentIndex === index) {return cur.value}cur = cur.nextcurrentIndex++}}// 特定位置插入addAtIndex(index, val){ // 若索引在0位之前 直接調用addAtHead方法if(index <= 0){return this.addAtHead(val)}// 如果index值大于0if(index >= 0 && index <= this._length){// 以val值實例化新結點let node = new Node(val)// 記錄當前遍歷的結點,初始為head let cur = this.head// 記錄遍歷次數let currentIndex = 0// 記錄當前遍歷結點的next指針let nextwhile(cur){if(currentIndex === index - 1){// 此時cur為要插入結點的前方結點// 用next保存插入元素前方結點的原next指針next = cur.nextcur.next = nodenode.next = nextthis._length++break}cur = cur.nextcurrentIndex++} } } // 按索引刪除deleteAtIndex (index) {if (index >= 0 && index <= this._length - 1) {let cur = this.head// 如果刪第0位if (index === 0) {this.head = cur.nextthis._length--} else {let currentIndex = 0let nextwhile (cur) {if (currentIndex === index - 1) {// 此時cur是被刪除元素的前一位next = cur.next.next// 先將刪除元素清空 避免內存泄漏cur.next = nullcur.next = nextthis._length--}cur = cur.nextcurrentIndex++}}}} } 復制代碼循環鏈表
在學習雙向鏈表之前,我們先來看一下循環鏈表
定義
百度百科是這樣定義的
其實相對于單鏈表,只是鏈表的最后一位元素的next指向了頭指針
可以說是再簡單不過了。
例題-環形鏈表
LeetCode-141題 原題地址
給定一個鏈表,判斷鏈表中是否有環。
為了表示給定鏈表中的環,我們使用整數 pos 來表示鏈表尾連接到鏈表中的位置(索引從 0 開始)。 如果 pos 是 -1,則在該鏈表中沒有環。
示例1:輸入:head = [3,2,0,-4], pos = 1 輸出:true 解釋:鏈表中有一個環,其尾部連接到第二個節點。復制代碼示例2:
輸入:head = [1,2], pos = 0? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 輸出:true? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 解釋:鏈表中有一個環,其尾部連接到第一個節點。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??注意:這個題目里面的環形鏈表和我們定義中的循環有所不同,循環鏈表的定義是鏈表的最后一位元素一定指向頭指針,而這里題目中的是尾指針可能指向鏈表中的任何一個非尾指針。
那我們如何判斷是否有環呢?
我們正常的思路一定是從頭指針head開始遍歷這個鏈表,一直到它長度的最后一位,如果它的next指針一直不為Null,那么它就是有環的,這個時候問題來了,由于這個鏈表并不是我們自己設計的,他將鏈表傳進來時是已經處理好的狀態,他并沒有聲明length屬性,如果這樣遍歷的話他并沒有截止條件,會造成一個死循環,這里也困擾了我很久。
于是我看了一下這道題的評論,發現了一種很巧妙的'快慢指針'的解法。
我們先來看一下下面這個動畫
我們先分別聲明兩個快慢指針,
初始位置都指向head,控制快指針每次移動兩位,慢指針每次移動一位。
在上邊的這個動畫中快慢指針同時移動三次的時候fast指針指向了值為2的結點,,slow指針指向了。大家想象一下,當移動第四次的時候,他們會同時指向值為7的位置(原諒我動畫只錄到第三次)。? ?
那么是不是可以認為只要有環,兩個指針就會有指向同一個結點的情況呢?
根據這樣的思路,我們可以完成以下代碼:
var hasCycle = (head) => {let fast = headlet slow = headwhile( fast != null && fast.next != null){slow = slow.nextfast = fast.next.nextif( slow === fast){return true}}return false }復制代碼雙向鏈表
定義
其實說白了雙向鏈表就是在單鏈表的基礎上每個結點多了一個指向上一個結點的指針,了解完了單鏈表和環形鏈表,雙向鏈表應該是不難理解了。
實現雙向鏈表
雙項鏈表與單鏈表的結構類似,這里就不過多闡述了,在這里給出實現一個基本雙向鏈表的源碼供大家參考。
function Node(value) {this.data = value;this.previous = null;this.next = null; }function DoublyList() {this._length = 0;this.head = null;this.tail = null; }DoublyList.prototype.add = function(value) {var node = new Node(value);if (this._length) {this.tail.next = node;node.previous = this.tail;this.tail = node;} else {this.head = node;this.tail = node;}this._length++;return node; };DoublyList.prototype.searchNodeAt = function(position) {var currentNode = this.head,length = this._length,count = 1,message = {failure: 'Failure: non-existent node in this list.'};// 1st use-case: an invalid positionif (length === 0 || position < 1 || position > length) {throw new Error(message.failure);}// 2nd use-case: a valid positionwhile (count < position) {currentNode = currentNode.next;count++;}return currentNode; };DoublyList.prototype.remove = function(position) {var currentNode = this.head,length = this._length,count = 1,message = {failure: 'Failure: non-existent node in this list.'},beforeNodeToDelete = null,nodeToDelete = null,deletedNode = null;// 1st use-case: an invalid positionif (length === 0 || position < 1 || position > length) {throw new Error(message.failure);}// 2nd use-case: the first node is removedif (position === 1) {this.head = currentNode.next;// 2nd use-case: there is a second nodeif (!this.head) {this.head.previous = null;// 2nd use-case: there is no second node} else {this.tail = null;}// 3rd use-case: the last node is removed} else if (position === this._length) {this.tail = this.tail.previous;this.tail.next = null;// 4th use-case: a middle node is removed} else {while (count < position) {currentNode = currentNode.next;count++;}beforeNodeToDelete = currentNode.previous;nodeToDelete = currentNode;afterNodeToDelete = currentNode.next;beforeNodeToDelete.next = afterNodeToDelete;afterNodeToDelete.previous = beforeNodeToDelete;deletedNode = nodeToDelete;nodeToDelete = null;}this._length--;return message.success; };復制代碼參考
瘋狂的技術宅-?JavaScript數據結構:單向鏈表與雙向鏈表? ? ?原文鏈接百度百科
LeetCode原題
總結
今天我們用js分別實現了單鏈表,循環鏈表,雙鏈表的實現以及相關的LeetCode真題,相信以后在面試中再次碰到有關鏈表這種數據結構的題目你一定會游刃有余。下一篇我們將介紹矩陣。上一篇?JS版數據結構第二篇(隊列)?轉載于:https://juejin.im/post/5cc41225e51d456e6b5d7e2e
總結
以上是生活随笔為你收集整理的JS版数据结构第三篇(链表)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python Tornado
- 下一篇: GSLX680触摸屏驱动移植