JS 数据结构:链表

article/2025/11/9 13:14:25

单链表

每个节点中只包含一个指针域的链表称为单链表

头结点—其指针域指向表中第一个结点的指针(头结点不是必须的,只是习惯上加上头结点,而头结点的数据域一般记录的是该链表的相关数据,如:链表长度)。

单链表由头指针唯一确定,因此单链表可以用头指针的名字来命名。

例如:若头指针为head,则可把链表称为“表head”。
在这里插入图片描述
话不多说,直接来看看链表的相关操作吧!!!

定义结点结构体

class Node {constructor(value) {this.value = value;this.next = null;}
}
class Head {constructor() {this.count = 0;this.next = null;}
}

插入

这一部分可以说是链表操作的精华部分,理解这一部分代码很关键哦!

尾插

尾插法就是在链表尾部插入新节点,那么要做的第一步当然是找到链表的尾结点了,找到后直接尾结点指针指向新节点就好啦,是不是很简单!!!

function insert(head, value) {const node = new Node(value);if (head.next == null) {//若链表为空尾结点即为头结点head.next = node;} else {let tem = head.next;while (tem.next != NULL) {//链表不为空则不断往后遍历tem = tem.next;}tem.next = node;}
}

在这里插入图片描述

头插

头插法就是在链表的头部插入新节点,而头结点的指针就是指向链表第一个节点的指针,所以将新节点指针域指向头结点原来指向的节点,再将头结点指向新节点即可。啊这,晕了?没关系,来,上图。

function head_insert(head, value) {const node = new Node(value);node.next = head.next;head.next = node;
}

在这里插入图片描述
如图所示,头插法需要两步完成:1.将新节点指针域指针指向图中首元结点。2.将头结点指针域指针指向新节点(完成这一步的时候图中打×的地方就断开了)。想一想这两步的顺序能否颠倒?答案是不能,至于为什么好好想一想吧!

前插

即为在指定元素前插入新节点

function pre_insert(head, value, element) {const node = new Node(value);if (head.next == null) {//如果链表为空head.next = node;} else {//链表不为空let pre = head, tem = head.next;while (tem != null) {if (tem.value == element) {//找到目标节点node.next = tem;pre.next = node;return;}tem = tem.next;pre = pre.next;}}console.log("指定元素不存在,插入失败!!!");
}

在这里插入图片描述

后插

在指定元素后插入新节点。

function back_insert(head, value, element) {const node = new Node(value);if (head.next == null) {head.next = node;} else {let tem = head.next;while (tem.next != null) {//遍历查找指定元素if (tem.value == element) {break;}tem = tem.next;}node.next = tem.next;tem.next = node;return;}console.log("指定元素不存在,插入失败!!!");
}

在这里插入图片描述
链表的插入只需要改变指针域指向的节点即可,单链表的后插要比前插简单一点。

删除

找到目标节点,使其前驱结点指向其指向的下一个节点即可。

function del(head, element) {//删除let tem = head.next;//临时节点if (head.next == null) {//链表为空console.log("链表为空!!!");return;} else if (head.next.data == element) {//第一个节点为目标节点head.next = tem.next;return;} else {//第一个节点不是目标节点let pre = head;//tem前驱节点while (tem != null) {if (tem.value == element) {pre.next = tem.next;return;}pre = pre.next;tem = tem.next;}console.log("链表中没有此元素!!!");}
}

在这里插入图片描述
单链表的其它操作比较简单且容易理解,具体看完整代码和注释。

分解 & 合并

将两个链表合成一个,先找到第一个链表的尾节点,将第二个链表的第一个节点作为尾节点的后续节点插入就好了。分解则是逆过程。

//合并
function combine(head1, head2) {let tem = head1.next;if (tem == null) {//head1链表为空head1直接指向head2指向的节点head1.next = head2.next;return;}while (tem.next != null) {//若head1不为空,找head1尾节点,使其指向head2的首节点tem = tem.next;}tem.next = head2.next;
}
//分解
function resolve(head, element) {if (head.next == null) {console.log("链表为空,分解失败!!!");return null;}let head1 = new Head();//为新头节点分配内存空间head1.next = null;let tem = head.next;while (tem != null) {//寻找目标节点if (tem.value == element) {//将新头节点指向目标节点指向的节点,将目标节点的指针域置为空head1.next = tem.next;tem.next = null;return head1;}tem = tem.next;}console.log("未找到标记点,分解失败!!!");return null;
}

在这里插入图片描述

其它操作

//查询
function search(head, element) {let number = 1;let tem = head.next;if (tem == null) {console.log("链表为空!!!");return;}while (tem != null) {//遍历if (tem.value == element) {console.log("所查找的元素为链表第" + number + "个节点!",);return;}number++;tem = tem.next;}console.log("目标元素不存在!!!");
}
//从大到小排序
function sorted(head) {if (head.next == null) {console.log("链表为空,排序失败!!!");return;}let tem1 = head.next;while (tem1 != null) {let tem2 = tem1.next;while (tem2 != null) {if (tem1.value < tem2.value) {let p = tem1.value;tem1.value = tem2.value;tem2.value = p;}tem2 = tem2.next;}tem1 = tem1.next;}
}
// 返回链表的长度
function length(head) {let len = 0;let tem = head.next;while (tem != null) {len++;tem = tem.next;}return len;
}
//输出链表
function print(head) {if (head.next == null) {console.log("链表为空,无法输出!!!");return;} else {let tem = head.next;while (tem != null) {console.log(tem.value);tem = tem.next;}return;}
}

双向链表(Double linked list)

单链表的每个结点再增加一个指向其前趋的指针域 pre,这样形成的链表有两条不同方向的链,称之为双向链表。

特点:

  1. 双链表一般也由头指针head唯一确定。
  2. 每一结点均有:
    数据域(value)
    左链域(pre)指向前趋结点.
    右链域(next)指向后继。
    是一种对称结构(既有前趋势,又有后继)
  3. 双链表的前插和后插难度是一样的。

缺点

指针数的增加会导致存储空间需求增加;二是添加和删除数据时需要改变更多指针的指向。

节点

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

双向链表的大部分操作与单链表非常类似,只是在操作的时候改变指针稍稍不同,这里只重点说明一下变化较大的操作。

插入

插入空链表或在链表尾部插入 这种情况相对来说比较简单。
在这里插入图片描述
在链表中间插入需要移动的指针较多具体看图。
在这里插入图片描述

// 在指定位置插入
function insert(head, value, index = 0) {if (index >= 0 && index <= head.count) {//指定位置是否合法const node = new Node(value);let num = 0, tem = head;while (num != index && tem != null) {//找指定位置的前驱节点tem = tem.next;num++;}if (tem.next == null) {node.pre = tem;tem.next = node;} else {node.pre = tem;node.next = tem.next;tem.next.pre = node;tem.next = node;}head.count++;return node;} else {console.log('插入位置错误');}
}

查询

单链表只能从头结点往后遍历查找,但双向链表可从链表任意位置开始查找。而我给出的示例中是同时从头尾开始向中间遍历查找,这样会加快便利的速度。我这里是在求链表长度的时候记录下尾结点。

//查询 
function search(head, element, tail = null) {let number = 1;if (head.next == null) {console.log("链表为空!!!");return;}head = head.next;if (tail) { // 如果传入尾节点就行二分查找while (head != tail && tail.next != head) {if (head.value == element) {console.log("所查找的元素为链表第" + number + "个节点!");return head;} else if (tail.value == element) {console.log("所查找的元素为链表倒数第" + number + "个节点!");return tail;}number++;head = head.next;tail = tail.pre;}} else {// 从头开始遍历while (head != null) {if (head.value == element) {console.log("所查找的元素为链表第" + number + "个节点!");return head;}number++;head = head.next;}}console.log("目标元素不存在!!!");
}

在这里插入图片描述
如图:tem利用节点next指针从前往后遍历,temp利用节点pre指针从后往前遍历,遍历结束条件为:
若有奇数个节点,则结束时tem一定等于temp;节点为偶数个时,结束时tem->next一定等于temp或者temp->pre一定等于tem。

删除

双链表删除要比单链表简单一些,因为它不需要额外的寻找指定节点的前驱结点。如上图:若要删除p节点,只需将head节点next指针指向rear节点,而rear节点pre指针指向head节点,最后再释放掉p节点所占内存就完成了删除操作。

//删除
function del(head, element = undefined, node = null) {let tem = head.next;//临时节点if (head.next == null) {//链表为空console.log("链表为空!!!");return;}if (element && !node) {// 传入的是一个值while (tem != null) {// 循环找值if (tem.value == element) {tem.pre.next = tem.next;if (tem.next) {tem.next.pre = tem.pre;}head.count--;return;}tem = tem.next;}} else if (!element && node) {// 传入的是要删除的节点对象node.pre.next = node.next;if (node.next) {node.next.pre = node.pre;}head.count--;return;}console.log("链表中没有此元素!!!");
}

其它操作

//输出函数
function print(head) {if (head.next == null) {console.log("链表为空,无法输出!!!");return;} else {tem = head.next;while (tem != null) {console.log(tem.value);tem = tem.next;}return;}
}
//合并
function combine(head1, head2, tail = null) {if (tail) {// 如果传入 head1 链表的尾节点tail.next = head2.next;head2.next.pre = tail;head1.count += head2.count;} else {// 如果未传入 head1 链表的尾节点let tem = head1;while (tem.next != null) {// 循环遍历找 head1 尾节点tem = tem.next;}tem.next = head2.next;head2.next.pre = tem;head1.count += head2.count;}
}

循环链表(Circular linked list)

整个链表形成一个环,从表中任一结点出发均可找到表中其它结点。
特点:

  1. 表中最后一个结点的指针指向第一个结点或表头结点(如有表头结点的话)
  2. 循环链表的运算与单链表基本一致。但两者判断是否到表尾的条件不同: 单链表:判断某结点的链域是否为空。循环链表:判断某结点的链域值是否等于头指针。

插入 & 删除

循环链表的插入与单链表极为相似,唯独在尾插、头插、删除‘尾节点‘和删除第一个节点的时候有点区别,因为需要移动’尾节点‘的指针。如下图是’尾插‘,删除’尾节点‘是逆过程。

其实本质上循环链表并没有严格意义上的尾节点,因为该链表就相当于一个环,所以‘尾插’就是严格意义上的在链表中间插入。
在这里插入图片描述
删除头结点如下图所示,头插是逆过程。
在这里插入图片描述

// 指定位置插入
function insert(head, value, index = 0) {if (index >= 0 && index <= head.count) {//指定位置是否合法const node = new Node(value);let num = 0;if (index === 0) {//处理头插if (head.next == null) {// 链表为空的情况head.next = node;node.next = node;} else {// 链表不为空的情况let tem = head.next;node.next = head.next;while (tem.next != head.next) {// 找到尾节点tem = tem.next;}head.next = node;tem.next = node;}} else {//处理尾插和在链表中间插入let tem = head;while (num != index && tem != null) {//找指定位置的前驱节点tem = tem.next;num++;}node.next = tem.next;tem.next = node;}head.count++;return node;} else {console.log('插入位置错误');}
}
// 删除某一元素
function del(head, element = undefined) {let tem = head.next;//临时节点if (head.next == null) {//链表为空console.log("链表为空!!!");return;}if (head.count === 1) {// 处理链表只有一个节点的情乱搞if (head.next.value === element) {head.next = null;return;}} else {while (tem.next != head.next) {// 循环找目标节点的前驱if (tem.next.value === element) {tem.next = tem.next.next;while (tem.next != head.next) {// 循环找尾节点tem = tem.next;}tem.next = head.next;// 使尾节点指针指向新的第一个节点head.count--;return;}tem = tem.next;}}console.log("链表中没有此元素!!!");
}

合并

两循环链表的合并只需要将第一个链表的尾节点指向第二个链表的头节点,第二个链表的尾节点指向第一个链表的头节点即可。其实就是交换两链表的尾节点的指针值。如图所示:
在这里插入图片描述

// 合并链表
function combin(head, head1) {let tem = head.next, tem1 = head1.next;;//临时节点while (tem.next && tem.next != head.next) {// 找链表1的尾节点tem = tem.next;}while (tem1.next && tem1.next != head1.next) {// 找链表2的尾节点tem1 = tem1.next;}// 交换两尾节点的指针值const temp = tem.next;tem.next = tem1.next;tem1.next = temp;
}

循环链表的其它操作与单链表和双向链表极为相似,就不在赘述其操作了。

链表的内容基本上就这么多了,希望能够就对你有所帮助。欢迎各位小伙伴在下方留言区留言评论或提问!我是孤城浪人,一名正在前端路上摸爬滚打的菜鸟,欢迎你的关注。


http://chatgpt.dhexx.cn/article/hI5HVId5.shtml

相关文章

数据结构中链表和列表的区别

顺序表和链表由于存储结构上的差异&#xff0c;导致它们具有不同的特点&#xff0c;适用于不同的场景。通过系统地学习顺序 表和链表我们知道&#xff0c;虽然它们同属于线性表&#xff0c;但数据的存储结构有本质的不同。 • 顺序表存储数据&#xff0c;需预先申请一整块足够…

数据结构(六)——循环链表

一、循序链表简介 1、循环链表的定义 循环链表的任意元素都有一个前驱和一个后继&#xff0c;所有数据元素在关系上构成逻辑上的环。 循环链表是一种特殊的单链表&#xff0c;尾结点的指针指向首结点的地址。 循环链表的逻辑关系图如下&#xff1a; 2、循环链表的设计实现 …

数据结构-使用链表实现栈

目录结构 Stack接口 package LinkedListStack;public interface Stack<E> {int getSize();boolean isEmpty();void push(E e); //向栈中添加元素E pop();//向栈中取出元素E peek();//查看栈顶的元素 }LinkedList类 package LinkedListStack;public class LinkedList<…

数据结构 | 链表的实现

目录 单链表双链表数组结构和链式结构的对比 线性表中&#xff0c;除了顺序表这一重要的结构&#xff0c;还有链式结构&#xff0c;而这也是我们常说的链表。 一般是通过定义结构体(类)的方式来表示链表的每一个结点。一般而言&#xff0c;链表的结点都会有数据域和地址域。数据…

Java数据结构之链表

目录 一.单链表 1.单链表的介绍和内存布局 2.单链表的添加和遍历 3.单链表的插入 4.单链表的删除 二.双向链表 1.添加节点 2.遍历节点 3.插入节点 4.删除结点 5.测试 三.单向环形链表 1.问题的引出 ​编辑 2.构建环形链表 1.创建结点 3.测试 3.约瑟夫问题代码的…

c++数据结构:链表

链表是一种物理存储单元上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点&#xff08;链表中每一个元素称为结点&#xff09;组成&#xff0c;结点可以在运行时动态生成。每个结点包括两个部分&#xff1a;一个是…

java数据结构-链表详解

文章目录 1.数据结构-链表详解1.1单链表1.1.1单链表节点的尾部添加1.1.2单链表节点的自动排序添加1.1.3单链表节点的修改1.1.4单链表节点的删除 1.2单链表面试题1.2.1单链表的有效节点个数1.2.2单链表倒数第k个结点1.2.3单链表反转1.2.4单链表逆序打印 1.3双向链表1.3.1双向链表…

C语言数据结构链表(图文)

目录 一、链表的简单理解与引入 1.1 链表的引入 1.2 节点的理解 1.3 链表的分类 二、常用链表功能的实现 2.1 首先是节点的定义&#xff0c; 2.2 节点的创建 2.3 单链表的尾插 2.4 单链表的尾删 2.5 单链表的头插 2.6 链表的头删 2.7 单…

【数据结构】链表的学习总结

目录 1.链表的概念2.链表的结构1️⃣链表中单个结点的结构2️⃣链表的整体结构 3.链表的分类4.链表的实现1️⃣单向无头非循环2️⃣双向带头循环 1.链表的概念 链表&#xff0c;是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表 中的指针…

C++数据结构之链表(详解)

主要参考文章地址 01.链表基础知识 | 算法通关手册 (itcharge.cn)&#xff09; 本次内容是对链表的总结&#xff0c;可以看了上面的文章之后。 在看我下面的内容&#xff0c;做一个简短的复习&#xff0c;且本内容的代码均用C实现&#xff0c;而参考资料的代码则为python。 …

[数据结构]链表之单链表(详解)

文章目录 [数据结构]链表之单链表前言1.链表1.1链表的概念及结构1.2单链表与顺序表的区别与优缺点1.3八种链表类型、单向带头循环链表单向带头非循环链表单向不带头循环链表单向不带头非循环链表双向带头循环链表双向带头非循环链表双向不带头循环链表双向不带头非循环链表 2.单…

【数据结构与算法】详解什么是链表,并用代码手动实现一个链表结构

本系列文章【数据结构与算法】所有完整代码已上传 github&#xff0c;想要完整代码的小伙伴可以直接去那获取&#xff0c;可以的话欢迎点个Star哦~下面放上跳转链接 https://github.com/Lpyexplore/structureAndAlgorithm-JS 本文将来讲解一下一种常见的线性数据结构—链表&a…

数据结构-链表篇

数据结构中数组和链表是是使用频率最高的基础数据结构。数组作为数据存储结构有一定的缺陷。在无序数组中&#xff0c;搜索性能差&#xff0c;在有序数组中&#xff0c;插入效率又很低&#xff0c;而且这两种数组的删除效率都很低&#xff0c;并且数组在创建后&#xff0c;其大…

数据结构之——链表

目录 一、链表的概念及结构 二、单链表的实现&#xff08;无头单向非循环链表&#xff09; 1.单链表节点定义 2.单链表的接口实现 &#xff08;1&#xff09;动态申请一个节点 &#xff08;2&#xff09;单链表打印 &#xff08;3&#xff09;单链表的销毁 &#xff0…

【数据结构】链表

单链表 这张图是我们待会要实现的功能&#xff0c;我会尽可能的将每一步都说的很详细&#xff0c;方便理解。 链表的概念及结构 概念&#xff1a;链表是一种 物理存储结构上非连续 、非顺序的存储结构&#xff0c;数据元素的 逻辑顺序 是通过链表中的 指针链 接 次序实现的 。…

数据结构之链表

目录 一、链表的特点 二、虚拟头结点 三、链表的实现 1、定义LinkedList 2、 构造方法 3、基本方法 4、添加元素 5、查找元素 6、修改元素 7、删除元素 链表是一种物理存储单元上非连续、非顺序的数据结构。前几篇我们讲到的数组也好&#xff0c;基于数组实现的栈…

[数据结构] 链表(图文超详解讲解)

文章目录 一、链表是什么&#xff1f;二、链表 1.链表的结构2.链表方法的代码实现总结 一、链表是什么&#xff1f; 链表是一种物理存储结构上非连续存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。 二、链表 1.链表的结构 链表的结构如图: 链…

数据结构---单向链表,双向链表,单向环形链表

链表介绍 链表是以节点的方式来存储,是链式存储每个节点包含 data 域&#xff0c; next 域:指向下一个节点.如图:发现链表的各个节点不一定是连续存储.链表分带头节点的链表和没有头节点的链表&#xff0c;根据实际的需求来确定 修改节点功能 思路(1) 先找到该节点&#xff0c…

数据结构与算法——线性表(链表篇)

&#x1f60a;数据结构与算法——线性表&#xff08;链表篇&#xff09; &#x1f680;前言&#x1f680;线性链表&#xff08;单链表&#xff09;&#x1f6a2;概念&#x1f6a2;基本操作&#x1f47b;插入操作⛅按位序插入⛅指定结点的后插操作⛅指定节点的前插操作 &#x1…

什么是接口测试?为什么要做接口测试?

1. 什么是接口测试&#xff1f;为什么要做接口测试&#xff1f; 接口测试是测试系统组件间接口的一种测试。接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。测试的重点是要检查数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及系统间的相互…