日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

JS版数据结构第三篇(链表)

發布時間:2025/4/5 javascript 53 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JS版数据结构第三篇(链表) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
鏈表分為單鏈表,雙鏈表,以及環形鏈表,我將分三個模塊分別介紹,并有相應的題目對應講解。

單鏈表

定義

還是按照老規矩先看一下百度百科對單鏈表的定義


根據以上文字我們可以得出

  • 單鏈表是一種鏈式的數據結構
  • 它由一系列結點組成
  • 每個結點包含兩個部分:
  • 結點元素的數據
  • 指向下一個位置的指針(next)

  • 頭指針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) 刪除指定位置的節點

    // 鏈表類 class MyLinkedList{ constructor(){// 頭指針初始為空this.head = null// 長度初始為0this._length = 0}get (index) {} addAtHead (val) {}addAtTail (val) {} addAtIndex (index, val) {} deleteAtIndex (index) {}}復制代碼

    然后我們需要一個節點類(構造函數),用來保存每個節點的value以及next屬性

    • data 存儲數據

    • next 指向鏈表中下一個節點的指針

    // 節點類 class Node(value) {// 實例化時賦值this.val = value// next指針初始為nullthis.next = null }復制代碼

    兩個構造函數已經寫好了,接下來我們開始實現以下它的功能

    • 方法1:? addAtHead(val)?

    在鏈表的第一個元素之前添加一個值為 val 的節點,我們需要

  • 用一個變量oldHead保存當前鏈表中的頭指針head
  • 以val值實例化一個新的node節點,并將其作為新的頭指針head
  • 將新的頭指針的next指向oldHead
  • 代碼如下:

    addAtHead(val){let oldHead = this.headthis.head = new Node(val)this.head.next = oldHeadthis._length++ }復制代碼

    • 方法2:addAtTail(val)
    向鏈表中最后元素后添加一個節點,我們需要
  • 以val值實例化新的node節點
  • 根據頭指針head遍歷到鏈表中最后一個元素
  • 將最后一個元素的next指針指向新的node節點
  • 代碼如下:

    addAtTail (val) {let node = new Node(val)// 用一個變量保存鏈表最后一個節點 初始值為頭指針let last = this.head// 遍歷找到最后一個節點while(last && last.next){last = last.next}// 如果非空鏈表if(last){// 將最后一個節點指向插入的節點last.next = node}else{// 若為空鏈表 將頭指針指向新插入節點this.head = node}this._length++ }復制代碼

    • 方法3:get(index)
    根據索引查找鏈表中的節點,我們需要:
  • 聲明一個變量currentIndex記錄遍歷的次數,初始為0
  • 根據頭指針head的next指針遍歷鏈表,每查詢一次next,將currentIndex加1
  • 直到currentIndex與要查詢的index相等
  • 代碼如下:

    get(index){// 記錄遍歷次數 初始為0let currentIndex = 0// 記錄遍歷到的節點 初始為headlet cur = thi.head// 根據題意, 索引無效則返回 -1if(index < 0 || this._length < index + 1){return -1}// 直到查找到索引為index的元素為止while (cur){if(currentIndex = index){return cur.val}}cur = cur.nextcurrentIndex++ }復制代碼

    • 方法4:addAtIndex(index,val)
    從索引位置為index的前端插入值為val的元素,我們需要:
  • 以val值實例化一個新node結點
  • 找到要插入元素的前一位元素,改變它的next指針,指向新node結點
  • 將新插入的node結點的next指針指向原位置的結點
  • 但是代碼中我們要考慮到特殊的一些情況(在頭部或尾部插入,鏈表為空等情況)

    代碼如下:

    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)
    接下來實現我們的最后一個方法,根據索引刪除元素,我們需要:
  • 通過head指針遍歷到index位置的元素
  • 用變量將根據索引查找到的要刪除的元素下一位記錄下來
  • 將要刪除元素的前一位的next指針指向要刪除元素的下一位
  • 代碼如下:

    deleteIndex(index){// 若索引有效if(index >=0 && index <= this._length -1){let cur = this.head// 若刪除索引為0的元素,只需將head指針向下移動一位,不需要考慮刪除元素之前結點的指針if(index === 0){this.head = cur.nextthis._length--}else{// 若刪除索引非0let currentIndex = 0let nextwhile(cur){if(currentIndex === index-1){// 此時cur為要刪除元素的前一個結點next = cur.next.next// 先將刪除元素清空 避免內存泄漏cur.next = nullcur.next = nextthis._length--}cur = cur.nextcurrentIndex++}}} }復制代碼

    這樣我們就實現了一個完整的實現增刪改查功能的單鏈表了,完整代碼如下:

    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版数据结构第三篇(链表)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。