从头到尾,探索链表的无限可能性

何为链表

在计算机科学的世界里,有一种神奇的数据结构,它以一种独特的方式将数据节点连接在一起,创造出无限的可能性。它就是链表。从头到尾,这篇文章将带您踏上一场探索链表的奇妙之旅,揭开它隐藏的秘密,探寻它所带来的无限潜力。无论您是一名编程新手还是经验丰富的软件工程师,本文将为您详细解说链表的工作原理、优势与应用场景。准备好迎接数据的律动和魅力了吗?让我们一起跳进这个充满惊喜的链表世界吧!

链表是一种常见的线性数据结构,它由一系列节点组成,每个节点包含两部分:一个是存储数据的字段(通常称为"数据域"),另一个是指向下一个节点的引用(通常称为"指针"或"引用")。这样的设计使得链表中的节点在内存中并不需要连续的存储空间,它们可以分布在内存的任意位置,通过指针相互连接起来。

ini 复制代码
function ListNode(val) {
  this.val = val;
  this.next = null
}

let node1 = new ListNode(1)
node1.next = new ListNode(2)
node1.next.next = new ListNode(3)

这是一个简单的链表。该链表的头节点是node1,它的值为1,下一个节点是2,再下一个节点是3。而

链表的增删查改

在JS中,我们可以使用对象的方式来模拟链表。比如下面这个ListNode类:

kotlin 复制代码
class ListNode {
  constructor(val) {
    this.val = val;
    this.next = null;
  }
}

增:头部插入,尾部插入,指定位置插入。

  • 头部插入
javascript 复制代码
function addAtHead(head, val) {
  const newHead = new ListNode(val); // 创建一个新的节点,值为val
  newHead.next = head; // 将新节点的next指向原来的头节点
  return newHead; // 返回新的头节点
}

在这个函数内部,我们首先创建了一个新的节点newHead,它的值就是我们要插入的val。然后,将新节点的next指向原来的头节点,这样就把原来的链表整体往后移动了一位,而新节点成为了新的头节点。

最后,返回新的头节点即可。

  • 尾部插入
ini 复制代码
function addAtTail(head, val) {
  const newTail = new ListNode(val); // 创建一个新的节点,值为val
  if (!head) { // 如果原链表为空,则直接返回新节点作为头节点
    return newTail;
  }
  let cur = head;
  while (cur.next) { // 找到链表的尾节点
    cur = cur.next;
  }
  cur.next = newTail; // 将新节点连接到尾节点的next
  return head; // 返回头节点
}

在这个函数内部,我们首先创建了一个新的节点newTail,它的值就是我们要插入的val。然后,我们判断原链表是否为空。如果为空,说明原链表只有一个节点或者没有节点,此时直接将新节点作为头节点返回即可。

如果原链表不为空,我们需要找到原链表的尾节点。我们使用一个指针cur来遍历链表,直到找到尾节点,即cur.nextnull。然后,将新节点newTail连接到尾节点的next,即cur.next = newTail

最后,返回头节点即可。

  • 指定位置插入
ini 复制代码
function addAtIndex(head, index, val) {
  if (index < 0) { // 如果index小于0,直接插入到头部
    return addAtHead(head, val);
  }
  const newNode = new ListNode(val); // 创建新节点
  if (index === 0) { // 如果index等于0,直接插入到头部
    newNode.next = head;
    return newNode;
  }
  let cur = head;
  for (let i = 0; i < index - 1; i++) { // 找到要插入位置的前一个节点
    if (!cur) { // 如果cur为null,说明已经到达链表末尾,无法插入
      return head;
    }
    cur = cur.next;
  }
  if (!cur) { // 如果cur为null,说明已经到达链表末尾,无法插入
    return head;
  }
  newNode.next = cur.next; // 将新节点的next指向cur的next
  cur.next = newNode; // 将cur的next指向新节点
  return head;
}

在这个函数内部,首先判断了插入位置index的情况。如果index小于0,就直接调用addAtHead方法将新节点插入到头部;如果index等于0,也直接将新节点插入到头部;否则,需要找到要插入位置的前一个节点,然后进行插入操作。

具体的插入过程如下:

  1. 首先创建一个新的节点newNode,它的值为val
  2. 如果index为0,直接将newNodenext指向head,然后返回newNode作为新的头节点。
  3. 如果index不为0,使用一个指针cur来遍历链表,找到第index-1个节点,即要插入位置的前一个节点。
  4. 然后将newNodenext指向cur.next,将curnext指向newNode

删:头部删除,尾部删除,指定位置删除。

  • 头部删除
javascript 复制代码
function deleteAtHead(head) {
  if (!head) { // 如果链表为空,直接返回null
    return null;
  }
  const newHead = head.next; // 将头节点的下一个节点作为新的头节点
  head.next = null; // 将原头节点断开连接
  return newHead; // 返回新的头节点
}

在这个函数内部,首先判断链表是否为空。如果链表为空,直接返回null

如果链表不为空,我们需要进行删除操作。首先将头节点的下一个节点作为新的头节点,即将head.next赋值给newHead

然后将原头节点的next指针置为null,断开与后续节点的连接,以确保被删除的头节点不再与链表关联。

最后,返回新的头节点newHead

  • 尾部删除
javascript 复制代码
function deleteAtTail(head) {
  if (!head) { // 如果链表为空,直接返回null
    return null;
  }
  if (!head.next) { // 如果链表只有一个节点,删除后返回null
    return null;
  }
  let cur = head;
  while (cur.next.next) { // 找到倒数第二个节点
    cur = cur.next;
  }
  cur.next = null; // 将倒数第二个节点的next指向null,断开与尾节点的连接
  return head; // 返回头节点
}

在这个函数内部,首先判断链表是否为空。如果链表为空,直接返回null

然后判断链表是否只有一个节点。如果链表只有一个节点,删除后返回null

如果链表不为空且节点数大于1,我们需要进行删除操作。首先使用一个指针cur来遍历链表,找到倒数第二个节点,即要删除的节点的前一个节点。

然后将倒数第二个节点的next指针置为null,断开与尾节点的连接,以确保被删除的尾节点不再与链表关联。

最后,返回头节点head

  • 指定位置删除
ini 复制代码
function deleteAtPosition(head, position) {
  if (!head) { // 如果链表为空,直接返回null
    return null;
  }
  if (position === 1) { // 如果要删除的是头节点,直接返回头节点的下一个节点作为新的头节点
    return head.next;
  }
  let cur = head;
  let count = 1;
  while (cur && count < position - 1) { // 找到要删除节点的前一个节点
    cur = cur.next;
    count++;
  }
  if (!cur || !cur.next) { // 如果找不到要删除的位置或者要删除的节点不存在,直接返回头节点
    return head;
  }
  cur.next = cur.next.next; // 将前一个节点的next指向要删除节点的下一个节点,实现删除操作
  return head; // 返回头节点
}

在这个函数内部,首先判断链表是否为空。如果链表为空,直接返回null

然后判断要删除的节点是否是头节点。如果要删除的是头节点,直接返回头节点的下一个节点作为新的头节点。

如果要删除的节点不是头节点,我们需要进行删除操作。首先使用一个指针cur来遍历链表,找到要删除节点的前一个节点。

然后根据指定的位置position,通过循环移动cur指针,直到它指向要删除节点的前一个节点。

如果无法找到要删除的位置或者要删除的节点不存在(curcur.next为空),直接返回头节点。

最后,将前一个节点的next指针指向要删除节点的下一个节点,实现删除操作。

查:查找节点

ini 复制代码
function findNode(head, value) {
  let cur = head;
  while (cur) {
    if (cur.value === value) { // 如果当前节点的值等于目标值,返回当前节点
      return cur;
    }
    cur = cur.next; // 否则继续遍历下一个节点
  }
  return null; // 遍历完整个链表都没有找到目标值,返回null
}

在这个函数内部,我们使用一个指针cur来遍历链表。

在每次循环中,我们首先判断当前节点cur的值是否等于目标值value。如果相等,则表示找到了目标节点,直接返回当前节点cur

如果当前节点的值不等于目标值,我们将指针cur移动到下一个节点,继续遍历。

如果遍历完整个链表都没有找到目标值,即指针cur变为null,那么表示目标节点不存在于链表中,返回null

改:更新节点

ini 复制代码
function updateNode(head, position, value) {
  let cur = head;
  let count = 1;
  while (cur && count < position) { // 找到要更新的节点
    cur = cur.next;
    count++;
  }
  if (!cur) { // 如果要更新的位置超出链表长度,直接返回头节点
    return head;
  }
  cur.value = value; // 更新节点的值
  return head; // 返回头节点
}

在这个函数内部,我们使用一个指针cur来遍历链表,找到要更新的节点。

首先,我们判断链表是否为空。如果链表为空,直接返回头节点。

然后,我们通过循环移动指针cur,直到它指向要更新的节点的位置。我们使用计数器count来记录当前节点的位置。

如果要更新的位置超出了链表的长度(即cur变为null),则直接返回头节点。

接下来,我们将要更新节点的值修改为给定的新值。 最后,返回头节点。

总结

本文详细介绍了链表的增删改查,相信很多看到这里的不少小伙伴已经有疑惑了,链表和数组的差距怎么这么大,像数组的增删改查方便轻松的多。像链表这样麻烦的数据结构有什么用呢?直接用数组不好吗?

其实不管什么数据结构都有其优点与缺点。二者最大的区别便在于:由于链表中的元素在内存中不必连续存储,因此插入和删除一个元素的时间复杂度为O(1),而数组则需要将后面的元素全部向后移动,时间复杂度为O(n)。

感谢各位读者坚持看完本文,如果文章对你有所帮助,还望点个赞支持一下,感谢。

相关推荐
_斯洛伐克22 分钟前
uniapp 知识点
前端·javascript·uni-app
木木与呆呆1 小时前
Chrome截取网页全屏
前端·chrome
向宇it2 小时前
fastadmin搜索刷新列表,怎么限制用户频繁点击?
开发语言·前端·javascript·fastadmin
Ylucius3 小时前
常见服务器大全----都是什么?又有何作用?区别联系是什么?---web,应用,数据库,文件,消息队列服务器,Tomat,Nginx,vite.....
java·前端·javascript·chrome·学习·node.js·express
alicca5 小时前
递归手撕,JSON 字符串化和解析,加权树结构的字符串解析对象,解析并返回DOM 树结构(DOMParser),解析带有层级的文本
前端·javascript
李是啥也不会5 小时前
JavaScript中的输出方式
开发语言·前端·javascript
炒毛豆5 小时前
ant design vue中带勾选表格报Tree missing follow keys: ‘undefined‘解决方法
前端·javascript·vue.js
大多_C5 小时前
python中logging的用法
前端·python
林某人的代码日常5 小时前
前端之render函数的理解
前端·vue.js·react.js
isea5337 小时前
如何只用 CSS 制作网格?
前端·css