【必学系列】JavaScript中的数据结构—链表

作者:Hanpeng_Chen

公众号:前端极客技术

文章首发个人博客:[JavaScript中的数据结构—链表 | 代码视界](https://www.chenhanpeng.com/javascript-linked-list/ 前)

前言

数据结构与算法在前端开发工程师的日常工作中也许不常用,但在这对前端工程师要求日益提高的时代,如果对数据结构、算法思维、代码效率等知识拥有足够的储备,那么我们将拥有更强的竞争力。

话不多说,我们接下来学习一种数据结构:链表(Linked list)。

链表

数组对于每个开发来说是非常熟悉的一种数据结构。链表是一种比数组稍微复杂一点的数据结构,掌握起来也比数组稍难一些。

链表是一种与数组类似的线性数据结构,但与数组的元素存储在特定的内存位置或索引中不同,链表的每个元素都是独立的对象,它包含一个指向该列表中下一个对象的指针或链接。

链表的每个元素(我们通常称为节点)包含两项:

  • 存储的数据:数据可以是任何有效的数据类型。
  • 指针:到下一节点的链接

链表的结构可以由很多种,它可以是单链表或双链表,也可以是已排序的或未排序的,环形的或非环形的。如果一个链表是单向的,那么链表中的每个元素没有指向前一个元素的指针。已排序的和未排序的链表较好理解。常见的有:单向链表、双向链表、单向循环链表和双向循环链表。

从上图我们可以看出,循环链表和单链表的区别在于:单链表的尾元素指向的Null,而循环链表的尾元素指向的是链表的头部元素。

欢迎关注我的微信公众号:前端极客技术(FrontGeek)

单向链表

链表和数组一样,也支持数据的查找、插入和删除。

由于链表是非连续的,想要访问第i个元素就不像数组那么方便,而是需要根据指针一个节点一个节点一次遍历,直到找到相应的节点。

因为链表的数据本身是非连续的空间,所以它的插入或删除操作,不需要像数组那边挪动原来的数据,因此在链表中插入数据、删除数据是非常快的。

设计链表

我们设计链表包含两个类:一个是Node类用来表示节点;另一个是LinkedList类提供插入节点、删除节点等操作。

头结点head的next初始化为null,每当调用插入方法时,next就会指向新的元素

  • Node
class Node {
  constructor(el) {
    this.el = el;
    this.next = null;
  }
}
  • LinkedList
class LinkedList {
  constructor(){
    this.head = new Node('head')
  }

  // 用于查找
  find(){}

  // 插入节点
  insert(){}

  // 删除节点
  remove(){}
}

查找

从头结点开始查找,如果没找到就把当前指针往后移,找到就返回该元素,如果遍历完没找到就直接返回null。

代码实现如下:

  find(item){
    let currentNode = this.head
    while (currentNode !== null && currentNode.el !== item) {
      currentNode = currentNode.next;
    }
    return currentNode
  }

插入新节点

要往链表中插入新节点,需要知道在哪个节点后面插入。那么我们就需要知道在哪里插入和插入的元素是什么。

知道在哪个节点后面插入后,首先我们要先找到这个节点的位置,这里我们就可以用上面实现的查找方法。

找到节点后,我们先创建一个新节点,把新节点的next指针指向找到的这个节点next指向的对应节点,再把找到的这个节点的next指针指向新节点,数据的插入就完成了。具体过程如下图所示:

代码实现如下:

// 插入节点
  // el:要插入的数据
  // item:数据插入到这个节点后面
  insert(el, item){
    const newNode = new Node(el)
    const cur = this.find(item)
    newNode.next = cur.next
    cur.next = newNode
  }

删除节点

删除节点和插入节点类似,首先要找到相应节点的前一个节点,找到后,让它的next指向待删除节点的下一个节点。如下图所示:

代码实现如下:

  findPre(item) {
    let cur = this.head
    while (cur.next !== null && cur.next.el !== item) {
      cur = cur.next
    }
    return cur
  }

  // 删除节点
  remove(item){
    const preNode = this.findPre(item)
    if (preNode.next !== null) {
      preNode.next = preNode.next.next
    }
  }

单向链表完整代码:

// 单项链表

// 节点
class Node {
  constructor(el) {
    this.el = el;
    this.next = null;
  }
}

class LinkedList {
  constructor(){
    this.head = new Node('head')
  }

  // 用于查找
  find(item){
    let currentNode = this.head
    while (currentNode !== null && currentNode.el !== item) {
      currentNode = currentNode.next;
    }
    return currentNode
  }

  findPre(item) {
    let cur = this.head
    while (cur.next !== null && cur.next.el !== item) {
      cur = cur.next
    }
    return cur
  }

  // 插入节点
  // el:要插入的数据
  // item:数据插入到这个节点后面
  insert(el, item){
    const newNode = new Node(el)
    const cur = this.find(item)
    newNode.next = cur.next
    cur.next = newNode
  }

  // 删除节点
  remove(item){
    const preNode = this.findPre(item)
    if (preNode.next !== null) {
      preNode.next = preNode.next.next
    }
  }
}

双向链表

双向链表,顾名思义就是它有两个方向,除了next指针指向下一个节点外,比单向链表多了一个prev指针,用来指向上一个节点。

双向链表如下图所示:

和单向链表相比,在存储相同的数据情况下,双向链表要比单向链表占用更多的空间,但双向链表往往会比单向链表更加灵活。例如:

双向链表删除节点时,因为有prev指向上一个节点,就不需要像单向链表一样去寻找待删除节点的前驱节点,使得删除节点的效率提高了。

接下来我们来看下如何实现双向链表:

双向链表的设计

对于节点类,我们只要在单向链表的基础上,加上一个prev指针。

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

另外双向链表的查找和单向链表一样,这里就不再细说,我们直接来看下插入节点。

插入

双向链表的插入和单向链表相似,多了一个prev指针,只要将新节点的prev指向前驱节点,将后驱节点的prev指向新节点。如下图所示:

实现代码如下:

insert(el, item){
    const newNode = new Node(el)
    const cur = this.find(item)
    newNode.next = cur.next
    newNode.prev = cur
    cur.next = newNode
    cur.next.prev = newNode
  }

删除

双向链表的删除 remove 方法比单链表效率高,不需要查找前驱节点,只要找出待删除节点,然后将该节点的前驱 next 属性指向待删除节点的后继,设置该节点后继 previous 属性,指向待删除节点的前驱即可。

实现代码如下:

remove(item){
    const node = this.find(item)
    node.prev.next = node.next
    node.next.prev = node.prev
    node.next = null
    node.prev = null
  }

双向链表完整代码如下:

// 双向链表
class Node {
  constructor(el) {
    this.el = el
    this.next = null
    this.prev = null
  }
}

class LinkedList {
  constructor(){
    this.head = new Node('head')
  }

  // 用于查找
  find(item){
    let currentNode = this.head
    while (currentNode !== null && currentNode.el !== item) {
      currentNode = currentNode.next;
    }
    return currentNode
  }

  // 插入节点
  // el:要插入的数据
  // item:数据插入到这个节点后面
  insert(el, item){
    const newNode = new Node(el)
    const cur = this.find(item)
    newNode.next = cur.next
    newNode.prev = cur
    cur.next = newNode
    cur.next.prev = newNode
  }

  // 删除节点
  remove(item){
    const node = this.find(item)
    node.prev.next = node.next
    node.next.prev = node.prev
    node.next = null
    node.prev = null
  }
}

单向循环链表

单向循环链表和单向链表相似,节点类型都一样,唯一的区别就是在创建循环链表的时候,让其头结点的next属性指向它本身。

head.next = head

单向循环链表如上图所示,具体的实现细节不再一一细说,我们直接来看实现代码:

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

class LinkedList {
  constructor(){
    this.head = new Node('head')
    this.head.next = this.head
  }

  // 用于查找
  find(item){
    let currentNode = this.head
    while (currentNode !== null && currentNode.el !== item) {
      currentNode = currentNode.next;
    }
    return currentNode
  }

  findPre(item) {
    let cur = this.head
    while (cur.next !== null && cur.next.el !== item) {
      cur = cur.next
    }
    return cur
  }

  // 插入节点
  // el:要插入的数据
  // item:数据插入到这个节点后面
  insert(el, item){
    const newNode = new Node(el)
    const cur = this.find(item)
    newNode.next = cur.next
    cur.next = newNode
  }

  // 删除节点
  remove(item){
    const preNode = this.findPre(item)
    if (preNode.next !== null) {
      preNode.next = preNode.next.next
    }
  }
}

双向循环链表

既然单链表可以有循环链表,双向链表也可以是循环链表。双向循环链表头结点的prev指针指向尾结点,尾结点的next指针指向头结点。如下图所示:

实现代码如下:


本文所有示例代码见:linked-list

参考文章:

如果你觉得这篇内容对你有帮助的话:

1、点赞支持下吧,让更多的人也能看到这篇内容

2、关注公众号:前端极客技术,我们一起学习一起进步。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
链表是一种线性数据结构,可以用来存储一系列元素。它由一系列节点组成,每个节点包含两部分:一个值和一个指向下一个节点的指针。 Python可以使用类来实现链表数据结构。具体实现方法如下: 1. 定义一个类表示节点,节点包含两个属性:value和next。 ```python class Node: def __init__(self, value): self.value = value self.next = None ``` 2. 定义一个链表类,链表包含一个头节点。 ```python class LinkedList: def __init__(self): self.head = None ``` 3. 实现链表的常用方法:添加节点、删除节点、查找节点等。 ```python class LinkedList: def __init__(self): self.head = None # 添加节点 def add(self, value): new_node = Node(value) if not self.head: self.head = new_node else: current_node = self.head while current_node.next: current_node = current_node.next current_node.next = new_node # 删除节点 def remove(self, value): current_node = self.head previous_node = None while current_node and current_node.value != value: previous_node = current_node current_node = current_node.next if current_node: if previous_node: previous_node.next = current_node.next else: self.head = current_node.next # 查找节点 def find(self, value): current_node = self.head while current_node and current_node.value != value: current_node = current_node.next return current_node ``` 4. 使用链表。 ```python # 创建链表 linked_list = LinkedList() # 添加节点 linked_list.add(1) linked_list.add(2) linked_list.add(3) # 删除节点 linked_list.remove(2) # 查找节点 node = linked_list.find(3) if node: print(node.value) else: print("Node not found.") ``` 链表是一种常用的数据结构,在Python可以使用类来实现。链表的添加、删除和查找节点的操作都可以通过遍历链表来完成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值