国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JS數(shù)據(jù)結(jié)構(gòu)與算法_鏈表

NeverSayNever / 1720人閱讀

摘要:上一篇數(shù)據(jù)結(jié)構(gòu)與算法棧隊(duì)列下一篇數(shù)據(jù)結(jié)構(gòu)與算法集合字典寫在前面說(shuō)明數(shù)據(jù)結(jié)構(gòu)與算法系列文章的代碼和示例均可在此找到上一篇博客發(fā)布以后,僅幾天的時(shí)間竟然成為了我寫博客以來(lái)點(diǎn)贊數(shù)最多的一篇博客。

上一篇:JS數(shù)據(jù)結(jié)構(gòu)與算法_棧&隊(duì)列
下一篇:JS數(shù)據(jù)結(jié)構(gòu)與算法_集合&字典

寫在前面
說(shuō)明:JS數(shù)據(jù)結(jié)構(gòu)與算法 系列文章的代碼和示例均可在此找到

上一篇博客發(fā)布以后,僅幾天的時(shí)間竟然成為了我寫博客以來(lái)點(diǎn)贊數(shù)最多的一篇博客。歡喜之余,不由得思考背后的原因,前端er離數(shù)據(jù)結(jié)構(gòu)與算法太遙遠(yuǎn)了,論壇里也少有人去專門為數(shù)據(jù)結(jié)構(gòu)與算法撰文,才使得這看似平平的文章收獲如此。不過(guò),這樣也更加堅(jiān)定了我繼續(xù)學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)與算法的決心(雖然只是入門級(jí)的)

一、鏈表數(shù)據(jù)結(jié)構(gòu)

相較于之前學(xué)習(xí)的 棧/隊(duì)列 只關(guān)心 棧頂/首尾 的模式,鏈表更加像是數(shù)組。鏈表和數(shù)組都是用于存儲(chǔ)有序元素的集合,但有幾點(diǎn)大不相同

鏈表不同于數(shù)組,鏈表中的元素在內(nèi)存中并不是連續(xù)放置的

鏈表添加或移除元素不需要移動(dòng)其他元素

數(shù)組可以直接訪問(wèn)任何一個(gè)位置的元素,鏈表必須從表頭開(kāi)始迭代到指定位置訪問(wèn)

下面是單鏈表的基本結(jié)構(gòu)

長(zhǎng)度為3的單鏈表

每個(gè)元素由一個(gè)存儲(chǔ)元素本身data的節(jié)點(diǎn)和一個(gè)指向下一個(gè)元素的引用next(也稱指針或鏈接)組成

尾節(jié)點(diǎn)的引用next指向?yàn)?b>null

類比:尋寶游戲,你有一條線索,這條線索是指向?qū)ふ蚁乱粭l線索的地點(diǎn)的指針。你順著這條鏈接去下一個(gè)地點(diǎn),得到另一條指向再下一處的線索。得到列表中間的線索的唯一辦法,就是從起點(diǎn)(第一條線索)順著列表尋找

二、鏈表的實(shí)現(xiàn)

鏈表的實(shí)現(xiàn)不像之前介紹的棧和隊(duì)列一般依賴于數(shù)組(至少我們目前是這樣實(shí)現(xiàn)的),它必須自己構(gòu)建類并組織邏輯實(shí)現(xiàn)。我們先創(chuàng)建一個(gè)Node

// 節(jié)點(diǎn)基類
class Node {
  constructor(data) {
    this.data = data;
    this.next = null;
  }
}

一般單鏈表有以下幾種方法:

append 在鏈表尾部添加一個(gè)元素

insert 在指定位置插入元素

removeAt 在指定位置刪除元素

getNode 獲取指定位置的元素

print 打印整個(gè)鏈表

indexOf 查找鏈表中是否有某個(gè)元素,有則返回索引,沒(méi)有則返回-1

getHead 獲取鏈表頭部

getTail 獲取鏈表尾部(有些并未實(shí)現(xiàn)尾部)

size 返回鏈表包含的元素個(gè)數(shù)

clear 清空鏈表

// 初始化鏈表
class LinkedList {
  constructor() {
    this._head = null;
    this._tail = null;
    this._length = 0;
  }
  // 方法...
}

下面我們來(lái)實(shí)現(xiàn)幾個(gè)重要的方法

2.1 append方法

在鏈表尾部添加一個(gè)新的元素可分為兩種情況:

原鏈表中無(wú)元素,添加元素后,headtail均指向新元素

原鏈表中有元素,更新tail元素(如下)

代碼實(shí)現(xiàn)

// 在鏈表尾端添加元素
append(data) {
  const newNode = new Node(data);
  if (this._length === 0) {
    this._head = newNode;
    this._tail = newNode;
  } else {
    this._tail.next = newNode;
    this._tail = newNode;
  }

  this._length += 1;
  return true;
}
2.2 print方法

為方便驗(yàn)證,我們先實(shí)現(xiàn)print方法。方法雖簡(jiǎn)單,這里卻涉及到鏈表遍歷精髓

// 打印鏈表
print() {
  let ret = [];
  // 遍歷需從鏈表頭部開(kāi)始
  let currNode = this._head;
  // 單鏈表最終指向null,作為結(jié)束標(biāo)志
  while (currNode) {
    ret.push(currNode.data);
    // 輪詢至下一節(jié)點(diǎn)
    currNode = currNode.next;
  }
  console.log(ret.join(" --> "));
}

// 驗(yàn)證
const link = new LinkedList();
link.append(1);
link.append(2);
link.append(3);

link.print(); // 1 --> 2 --> 3
2.3 getNode方法

獲取指定索引位置的節(jié)點(diǎn),依次遍歷鏈表,直到指定位置返回

// 獲取指定位置元素
getNode(index) {
  let currNode = this._head;
  let currIndex = 0;
  while (currIndex < index) {
    currIndex += 1;
    currNode = currNode.next;
  }
  return currNode;
}

// 驗(yàn)證【銜接上面的鏈表實(shí)例】
console.log(link.getNode(0));
// Node { data: 1, next: Node { data: 2, next: Node { data: 3, next: null } } }
console.log(link.getNode(3)); // null
2.4 insert方法

插入元素,需要考慮三種情況

插入尾部,相當(dāng)于append

插入首部,替代_head并指向原有頭部元素

中間,需要斷開(kāi)原有鏈接并重新組合(如下)

代碼實(shí)現(xiàn)

// 在鏈表指定位置插入元素
insert(index, data) {
  // 不滿足條件的索引值
  if (index < 0 || index > this._length) return false;
  // 插入尾部
  if (index === this._length) return this.append(data);

  const insertNode = new Node(data);
  if (index === 0) {
    // 插入首部
    insertNode.next = this._head;
    this._head = insertNode;
  } else {
    // 找到原有位置節(jié)點(diǎn)
    const prevTargetNode = this.getNode(index - 1);
    const targetNode = prevTargetNode.next;
    // 重塑節(jié)點(diǎn)連接
    prevTargetNode.next = insertNode;
    insertNode.next = targetNode;
  }

  this._length += 1;
  return true;
}

// 驗(yàn)證
link.insert(0, 0);
link.insert(4, 4);
link.insert(5, 5);
link.print(); // 0 --> 1 --> 2 --> 3 --> 4 --> 5
2.5 removeAt方法

在指定位置刪除元素同添加元素類似

首部:重新定義_head

其他:找到目標(biāo)位置的前后元素,重塑連接,如果目標(biāo)位置為尾部,則重新定義_tail

代碼實(shí)現(xiàn)

// 在鏈表指定位置移除元素
removeAt(index) {
  if (index < 0 || index >= this._length) return false;

  if (index === 0) {
    this._head = this._head.next;
  } else {
    const prevNode = this.getNode(index - 1);
    const delNode = prevNode.next;
    const nextNode = delNode.next;
    // 若移除為最后一個(gè)元素
    if (!nextNode) this._tail = prevNode;
    prevNode.next = nextNode;
  }

  this._length -= 1;
  return true;
}

// 驗(yàn)證
link.removeAt(3);
link.print(); // 0 --> 1 --> 2 --> 4 --> 5
2.6 其它方法

完整的鏈表代碼,可點(diǎn)此獲取

// 判斷數(shù)據(jù)是否存在于鏈表內(nèi),存在返回index,否則返回-1
indexOf(data) {
  let currNode = this._head;
  let index = 0;
  while (currNode) {
    if (currNode.data === data) return index;
    index += 1;
    currNode = currNode.next;
  }

  return -1;
}

getHead() {
  return this._head;
}

getTail() {
  return this._tail;
}

size() {
  return this._length;
}

isEmpty() {
  return !this._length;
}

clear() {
  this._head = null;
  this._tail = null;
  this._length = 0;
}
三、鏈表的應(yīng)用 3.1 基于鏈表實(shí)現(xiàn)的StackQueue

基于鏈表實(shí)現(xiàn)棧

class Stack {
  constructor() {
    this._link = new LinkedList();
  }
  push(item) {
    this._link.append(item);
  }
  pop() {
    const tailIndex = this._link - 1;
    return this._link.removeAt(tailIndex);
  }
  peek() {
    if (this._link.size() === 0) return undefined;
    return this._link.getTail().data;
  }
  size() {
    return this._link.size();
  }
  isEmpty() {
    return this._link.isEmpty();
  }
  clear() {
    this._link.clear()
  }
}

基于鏈表實(shí)現(xiàn)隊(duì)列

class Queue {
  constructor() {
    this._link = new LinkedList();
  }
  enqueue(item) {
    this._link.append(item);
  }
  dequeue() {
    return this._link.removeAt(0);
  }
  head() {
    if (this._link.size() === 0) return undefined;
    return this._link.getHead().data;
  }
  tail() {
    if (this._link.size() === 0) return undefined;
    return this._link.getTail().data;
  }
  size() {
    return this._link.size();
  }
  isEmpty() {
    return this._link.isEmpty();
  }
  clear() {
    this._link.clear()
  }
}
3.2 鏈表翻轉(zhuǎn)【面試常考】

(1)迭代法

迭代法的核心就是currNode.next = prevNode,然后從頭部一次向后輪詢

代碼實(shí)現(xiàn)

reverse() {
  if (!this._head) return false;

  let prevNode = null;
  let currNode = this._head;
  while (currNode) {
    // 記錄下一節(jié)點(diǎn)并重塑連接
    const nextNode = currNode.next;
    currNode.next = prevNode;
    // 輪詢至下一節(jié)點(diǎn)
    prevNode = currNode;
    currNode = nextNode;
  }

  // 交換首尾
  let temp = this._tail;
  this._tail = this._head;
  this._head = temp;

  return true;
}

(2)遞歸法

遞歸的本質(zhì)就是執(zhí)行到當(dāng)前位置時(shí),自己并不去解決,而是等下一階段執(zhí)行。直到遞歸終止條件,然后再依次向上執(zhí)行

function _reverseByRecusive(node) {
  if (!node) return null;
  if (!node.next) return node; // 遞歸終止條件

  var reversedHead = _reverseByRecusive(node.next);
  node.next.next = node;
  node.next = null;
  
  return reversedHead;
};

_reverseByRecusive(this._head);
3.3 鏈表逆向輸出

利用遞歸,反向輸出

function _reversePrint(node){
  if(!node) return;// 遞歸終止條件
  _reversePrint(node.next);
  console.log(node.data);
};
四、雙向鏈表和循環(huán)鏈表 4.1 雙向鏈表

雙向鏈表和普通鏈表的區(qū)別在于,在鏈表中,一個(gè)節(jié)點(diǎn)只有鏈向下一個(gè)節(jié)點(diǎn)的鏈接,而在雙向鏈表中,鏈接是雙向的:一個(gè)鏈向下一個(gè)元素,另一個(gè)鏈向前一個(gè)元素,如下圖

正是因?yàn)檫@種變化,使得鏈表相鄰節(jié)點(diǎn)之間不僅只有單向關(guān)系,可以通過(guò)prev來(lái)訪問(wèn)當(dāng)前節(jié)點(diǎn)的上一節(jié)點(diǎn)。相應(yīng)的,雙向循環(huán)鏈表的基類也有變化

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

繼承單向鏈表后,最終的雙向循環(huán)鏈表DoublyLinkedList如下【prev對(duì)應(yīng)的更改為NEW

class DoublyLinkedList extends LinkedList {
  constructor() {
    super();
  }

  append(data) {
    const newNode = new DoublyNode(data);

    if (this._length === 0) {
      this._head = newNode;
      this._tail = newNode;
    } else {
      newNode.prev = this._tail; // NEW
      this._tail.next = newNode;
      this._tail = newNode;
    }

    this._length += 1;
    return true;
  }

  insert(index, data) {
    if (index < 0 || index > this._length) return false;
    if (index === this._length) return this.append(data);

    const insertNode = new DoublyNode(data);
    if (index === 0) {
      insertNode.prev = null; // NEW
      this._head.prev = insertNode; // NEW
      insertNode.next = this._head;
      this._head = insertNode;
    } else {
      // 找到原有位置節(jié)點(diǎn)
      const prevTargetNode = this.getNode(index - 1);
      const targetNode = prevTargetNode.next;
      // 重塑節(jié)點(diǎn)連接
      prevTargetNode.next = insertNode;
      insertNode.next = targetNode;
      insertNode.prev = prevTargetNode; // NEW
      targetNode.prev = insertNode; // NEW
    }

    this._length += 1;
    return true;
  }

  removeAt(index) {
    if (index < 0 || index > this._length) return false;

    let delNode;

    if (index === 0) {
      delNode = this._head;
      this._head = this._head.next;
      this._head.prev = null; // NEW
    } else {
      const prevNode = this.getNode(index - 1);
      delNode = prevNode.next;
      const nextNode = delNode.next;
      // 若移除為最后一個(gè)元素
      if (!nextNode) {
        this._tail = prevNode;
        this._tail.next = null; // NEW
      } else {
        prevNode.next = nextNode; // NEW
        nextNode.prev = prevNode; // NEW
      }
    }

    this._length -= 1;
    return delNode.data;
  }
}
4.2 循環(huán)鏈表
循環(huán)鏈表可以像鏈表一樣只有單向引用,也可以像雙向鏈表一樣有雙向引用。循環(huán)鏈表和鏈 表之間唯一的區(qū)別在于,單向循環(huán)鏈表最后一個(gè)節(jié)點(diǎn)指向下一個(gè)節(jié)點(diǎn)的指針tail.next不是引用null, 而是指向第一個(gè)節(jié)點(diǎn)head,雙向循環(huán)鏈表的第一個(gè)節(jié)點(diǎn)指向上一節(jié)點(diǎn)的指針head.prev不是引用null,而是指向最后一個(gè)節(jié)點(diǎn)tail

總結(jié)

鏈表的實(shí)現(xiàn)較于棧和隊(duì)列的實(shí)現(xiàn)復(fù)雜許多,同樣的,鏈表的功能更加強(qiáng)大

我們可以通過(guò)鏈表實(shí)現(xiàn)棧和隊(duì)列,同樣也可以通過(guò)鏈表來(lái)實(shí)現(xiàn)棧和隊(duì)列的問(wèn)題

鏈表更像是數(shù)組一樣的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),同時(shí)也避免了數(shù)組操作中刪除或插入元素對(duì)其他元素的影響

上一篇:JS數(shù)據(jù)結(jié)構(gòu)與算法_棧&隊(duì)列
下一篇:JS數(shù)據(jù)結(jié)構(gòu)與算法_集合&字典

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/101290.html

相關(guān)文章

  • 嘮叨一下js對(duì)象哈希表那些事

    摘要:的擴(kuò)展知識(shí)對(duì)于哈希表來(lái)說(shuō),最重要的莫過(guò)于生成哈希串的哈希算法和處理沖突的策略了。由于鏈表的查找需要遍歷,如果我們將鏈表?yè)Q成樹或者哈希表結(jié)構(gòu),那么就能大幅提高沖突元素的查找效率。 最近在整理數(shù)據(jù)結(jié)構(gòu)和算法相關(guān)的知識(shí),小茄專門在github上開(kāi)了個(gè)repo https://github.com/qieguo2016...,后續(xù)內(nèi)容也會(huì)更新到這里,歡迎圍觀加星星! js對(duì)象 js中的對(duì)象是基...

    Nosee 評(píng)論0 收藏0
  • 探索vue源碼之緩存篇

    摘要:中采用算法來(lái)實(shí)現(xiàn)緩存的高效管理。通過(guò)這兩個(gè)屬性,將緩存中的變量連接起來(lái)。以上圖舉例緩存這個(gè)對(duì)象中保存了三個(gè)變量。如果緩存數(shù)組為空,則返回將為傳入?yún)?shù)的緩存對(duì)象標(biāo)識(shí)為最常使用的,即,并調(diào)整雙向鏈表,返回改變后的。 vue.js入坑也有了小半年的時(shí)間了,圈子里一直流傳著其源碼優(yōu)雅、簡(jiǎn)潔的傳說(shuō)。最近的一次技術(shù)分享會(huì),同事分享vue.js源碼的緩存部分,鄙人將其整理出來(lái),與大家一起學(xué)習(xí) 一、從...

    Forest10 評(píng)論0 收藏0
  • JS數(shù)據(jù)結(jié)構(gòu)算法_棧&隊(duì)列

    摘要:下一篇數(shù)據(jù)結(jié)構(gòu)與算法鏈表寫在前面說(shuō)明數(shù)據(jù)結(jié)構(gòu)與算法系列文章的代碼和示例均可在此找到原計(jì)劃是把你不知道的三部全部看完的,偶然間朋友推薦了數(shù)據(jù)結(jié)構(gòu)與算法的一套入門視頻,學(xué)之。手頭上恰好有學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)與算法的書籍,便轉(zhuǎn)而先把數(shù)據(jù)結(jié)構(gòu)與算法學(xué)習(xí)。 下一篇:JS數(shù)據(jù)結(jié)構(gòu)與算法_鏈表 寫在前面 說(shuō)明:JS數(shù)據(jù)結(jié)構(gòu)與算法 系列文章的代碼和示例均可在此找到 原計(jì)劃是把《你不知道的Javascript》...

    AndroidTraveler 評(píng)論0 收藏0
  • web技術(shù)分享| LRU 緩存淘汰算法

    摘要:雙向鏈表用于管理緩存數(shù)據(jù)結(jié)點(diǎn)的順序,新增數(shù)據(jù)和緩存命中最近被訪問(wèn)的數(shù)據(jù)被放置在結(jié)點(diǎn),尾部的結(jié)點(diǎn)根據(jù)內(nèi)存大小進(jìn)行淘汰。 了解 LRU 之前,我們應(yīng)該了解一下緩存,大家都知道計(jì)算機(jī)具有緩存內(nèi)存,可以臨時(shí)存儲(chǔ)最常用的數(shù)據(jù),當(dāng)緩存數(shù)據(jù)超過(guò)一定大小時(shí),系統(tǒng)會(huì)進(jìn)行回收,以便釋放出空間來(lái)緩存新的數(shù)據(jù),但從系統(tǒng)中檢索數(shù)據(jù)的成本...

    graf 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<