原生JS实现双向链表
1.前言
双向链表和单向链表的区别在于,在链表中,一个节点只有链向下一个节点的链接,而在双向链表中,链接是双向的:一个链向下一个元素,另一个链向前一个元素,如下图所示:
从图中可以看到,双向链表中,在每个节点Node
里有prev
属性(指向上一个节点的指针)和next
属性(指向下一个节点的指针),并且在链表中也有head
属性(用来存储链表第一项的引用)和tail
属性(用来存储链表最后一项的引用)。
2.代码实现
首先,我们可以先创建一个双向链表DoublyLinkedList类:
//创建一个Node辅助类,用来生成节点
class Node{
constructor(value){
this.value = value;
this.next = null;
this.prev = null;
}
}
class DoublyLinkedList{
constructor(){
this.head = null;
this.tail = null;
this.length = 0;
}
append(element){
}
find(value){
}
insert(position,element){
}
remove(value) {
}
removeAt(position){
}
size(){
}
isEmpty(){
}
nextPrint() {
}
prevPrint() {
}
}
在创建DoublyLinkedList类时,我们还需要创建一个Node辅助类。Node类表示要加入链表中的项。它包含一个value
属性,即要添加到链表中的值,一个next
属性,即指向链表中下一个节点项的指针,以及一个prev
属性,即指向链表中上一个节点项的指针。
DoublyLinkedList类也有存储链表项的数量的length
属性(内部/私有变量)。
我们还需要存储第一个节点和最后一个节点的引用。为此,可以把这两个个引用分别存储在head
和tail
的变量中。
此外,DoublyLinkedList类中还包含了如下一些方法:
- append(value):向链表尾部追加一个新元素
- find(value):根据元素值查找元素,并返回该元素
- insert(position, value):向链表的特定位置插入一个新的项
- remove(value):根据元素值删除元素,并返回该元素
- removeAt(position):根据元素位置删除元素,并返回该元素
- isEmpty():判断链表是否为空,是返回true,否返回false
- size():返回链表包含的元素个数
- nextPrint():顺序遍历打印该链表
- prevPrint():逆序遍历打印该链表
3.具体方法实现
3.1 append(value):向链表尾部追加一个新元素
/**
* 向链表尾部追加一个新元素
* @param {} element 要追加的新元素
*/
append(value){
let node = new Node(value);
let current = this.head;
if (!this.head) { //如果链表为空
this.head = node;
this.tail = node;
}else{
current = this.tail;
current.next = node;
node.prev = current;
this.tail = node;
}
this.length ++;
}
3.2 find(value):根据元素值查找元素,并返回该元素
/**
* 根据元素值查找元素,并返回该元素
* @param {*} value
*/
find(value){
let current = this.head;
if (!this.head) { //如果链表为空
console.log("这是一个空链表!!!");
return null;
}
if (current.value == value) {
return current;
}
while (current.next) {
current = current.next;
if (current.value === value) {
return current
}
}
console.log("没有找到该元素!!!");
return null;
}
3.3 insert(position, value):向链表的特定位置插入一个新的项
/**
* 向链表的特定位置插入一个新的项
* @param {Number} position 要插入的位置
* @param {*} element 要插入的新元素值
*/
insert(position,element){
if (position >= 0 && position <= this.length) {
let node = new Node(element);
let current = this.head;
let previous = null;
let index = 0;
if (position === 0) { //如果在第一个位置插入
if (!this.head) { //如果链表为空
this.head = node;
this.tail = node;
}else{
node.next = current;
current.prev = node;
this.head = node;
}
} else if(position === this.length){ //如果在最后一个位置插入
current = this.tail;
current.next = node;
node.prev = current;
this.tail = node;
}else { //如果在中间任意一个位置插入
while (index < position) {
previous = current;
current = current.next;
index++;
}
node.next = current;
previous.next = node;
current.prev = node;
node.prev = previous;
}
this.length++;
return true;
}else {
console.log('该位置不存在!');
return false;
}
}
代码说明:
向任意位置插入一个新元素,总共可归纳为三种情况:
- 在列表的第一个位置(列表的起点)插入一个新元素。如果列表为空,只需要把head和tail都指向这个新节点。如果不为空,current变量将是对列表中第一个元素的引用。就像我们在链表中所做的,把node.next设为current,而head将指向node(它将成为列表中的第一个元素)。不同之处在于,我们还需要为指向上一个元素的指针设一个值current.prev指针将由指向null变为指向新元素。node.prev指针已经是null,因此不需要再更新任何东西。
- 在列表最后添加一个新元素。因为我们还控制着指向最后一个元素的指针(tail)。current变量将引用最后一个元素。然后开始建立第一个链接:node.prev将引用current。current.next指针(指向null)将指向node(由于构造函数,node.next已经指向了null)。然后只剩一件事了,就是更新tail,它将由指
向current变为指向node。 - 在列表中间插入一个新元素。通过迭代列表,直到到达要找的位置。我们将在current和previous元素之间插入新元素。首先,node.next将指向current,而previous.next将指向node,这样就不会丢失节点之间的链接。然后需要处理所有的链接:current.prev将指向node,而node.prev将指向previous。
3.4 remove(value):根据元素值删除元素,并返回该元素
remove(value) {
let current = this.find(value);
if (current == null) {
console.log("没有找到该元素!!!");
return null;
} else {
current.prev.next = current.next;
current.next.prev = current.prev;
}
this.length--;
return current;
}
3.5 removeAt(position):根据元素位置删除元素,并返回该元素
removeAt(position){
if (position >= 0 && position <= this.length) {
let current = this.head;
let previous = null;
let index = 0;
if (position === 0) { //如果删除第一个位置
head = current.next;
head.prev = null;
if (this.length === 1) { //如果链表中只有一个元素
this.tail = null;
}
} else if (position === this.length-1) { //如果删除最后一个位置
current = this.tail;
this.tail = current.prev;
this.tail.next = null;
} else {
while (index < position) {
previous = current;
current = current.next;
index++;
}
previous.next = current.next;
current.next.prev = previous;
}
this.length--;
return current;
} else {
console.log('该位置不存在!');
return null;
}
}
代码说明:
从任意位置删除一个元素,总共也可归纳为三种情况:
- 从头部移除第一个元素。current变量是对列表中第一个元素的引用,也就是我们想移除的元素。需要做的就是改变head 的引用, 将其从current 改为下一个元素
。但我们还需要更新current.next指向上一个元素的指针(因为第一个元素的prev指针是null)。因此,把head.prev的引用改为null(因为head也指向列表中新的第一个元素,或者也可以用current.next.prev)。由于还需要控制tail的引用,我们可以检查要移除的元素是否是第一个元素,如果是,只需要把tail也设为null。 - 从尾部最后一个位置移除元素。既然已经有了对最后一个元素的引用(tail),我
们就不需要为找到它而迭代列表。这样我们也就可以把tail的引用赋给current变量,接下来,需要把tail的引用更新为列表中倒数第二个元素(current.prev,或者tail.prev也可以)。既然tail指向了倒数第二个元素,我们就只需要把next指针更新为null(tail.next= null)。 - 从列表中间移除一个元素。首先需要迭代列表,直到到达要找的位置。current变量所引用的就是要移除的元素。那么要移除它,我们可以通过更新previous.next和current.next.prev的引用,在列表中跳过它。因此,previous.next将指向current.next,而current.next.prev将指向previous。
3.6 isEmpty():判断链表是否为空,是返回true,否返回false
isEmpty(){
if (this.length === 0) {
return true;
} else {
return false;
}
}
3.7 size():返回链表包含的元素个数
size(){
return this.length
}
3.8 nextPrint():顺序遍历打印该链表
nextPrint() {
var current = this.head;
while (current != null) {
console.log(current.value);
current = current.next;
}
}
3.9 prevPrint():逆序遍历打印该链表
prevPrint() {
var current = this.tail;
while (current != null) {
console.log(current.value);
current = current.prev;
}
}
4. 完整代码
完整代码请戳☞☞☞DoublyLinkedList
原生JS实现双向链表的更多相关文章
- 原生JS封装Ajax插件(同域&&jsonp跨域)
抛出一个问题,其实所谓的熟悉原生JS,怎样的程度才是熟悉呢? 最近都在做原生JS熟悉的练习... 用原生Js封装了一个Ajax插件,引入一般的项目,传传数据,感觉还是可行的...简单说说思路,如有不正 ...
- 常用原生JS方法总结(兼容性写法)
经常会用到原生JS来写前端...但是原生JS的一些方法在适应各个浏览器的时候写法有的也不怎么一样的... 今天下班有点累... 就来总结一下简单的东西吧…… 备注:一下的方法都是包裹在一个EventU ...
- 原生JS实现"旋转木马"效果的图片轮播插件
一.写在最前面 最近都忙一些杂七杂八的事情,复习软考.研读经典...好像都好久没写过博客了... 我自己写过三个图片轮播,一个是简单的原生JS实现的,没有什么动画效果的,一个是结合JQuery实现的, ...
- 再谈React.js实现原生js拖拽效果
前几天写的那个拖拽,自己留下的疑问...这次在热心博友的提示下又修正了一些小小的bug,也加了拖拽的边缘检测部分...就再聊聊拖拽吧 一.不要直接操作dom元素 react中使用了虚拟dom的概念,目 ...
- React.js实现原生js拖拽效果及思考
一.起因&思路 不知不觉,已经好几天没写博客了...近来除了研究React,还做了公司官网... 一直想写一个原生js拖拽效果,又加上近来学react学得比较嗨.所以就用react来实现这个拖 ...
- 原生JS实现全屏切换以及导航栏滑动隐藏及显示——重构前
思路分析: 向后滚动鼠标滚轮,页面向下全屏切换:向前滚动滚轮,页面向上全屏切换.切换过程为动画效果. 第一屏时,导航栏固定在页面顶部,切换到第二屏时,导航条向左滑动隐藏.切换回第一屏时,导航栏向右滑动 ...
- 原生js实现autocomplete插件
在实际的项目中,能用别人写好的插件实现相关功能是最好不过,为了节约时间成本,因为有的项目比较紧急,没充分时间让你自己来写,即便写了,你还要花大量时间调试兼容性.但是出于学习的目的,你可以利用闲暇时间, ...
- 原生js封装ajax:传json,str,excel文件上传表单提交
由于项目中需要在提交ajax前设置header信息,jquery的ajax实现不了,我们自己封装几个常用的ajax方法. jQuery的ajax普通封装 var ajaxFn = function(u ...
- 原生JS实现购物车结算功能代码+zepto版
html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3 ...
随机推荐
- 【Java】登录验证码
Html: <input id="verifyCode" name="verifyCode" type="text" value=&q ...
- PHP生成唯一ID的方法
PHP自带生成唯一id的函数:uniqid() 它是基于当前时间微秒数的 用法如下: echo uniqid(); //13位的字符串 echo uniqid("php_"); / ...
- Python多任务之协程
前言 协程的核心点在于协程的使用,即只需要了解怎么使用协程即可:但如果你想了解协程是怎么实现的,就需要了解依次了解可迭代,迭代器,生成器了: 如果你只想看协程的使用,那么只需要看第一部分内容就行了:如 ...
- spring5 源码深度解析----- 事务的回滚和提交(100%理解事务)
上一篇文章讲解了获取事务,并且通过获取的connection设置只读.隔离级别等,这篇文章讲解剩下的事务的回滚和提交 回滚处理 之前已经完成了目标方法运行前的事务准备工作,而这些准备工作最大的目的无非 ...
- Git版本控制之ubuntu搭建Git服务器
Git是一个开源的分布式版本控制系统,可以有效.高效的处理从很小到非常大的项目版本管理.使得开发者可以通过克隆(git clone),在本地机器上拷贝一个完整的Git仓库,也可以将代码提交到Git服务 ...
- Oracle11g安装与基本使用
目录 安装 修改用户密码 配置文件修改 使用PLSQL连接Oracle数据库 如何执行SQL 语句 本教程基于oracle11g和PLSQL进行 下载资源见百度网盘链接:https://pan.bai ...
- php有orm吗
ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中.本质上就是将数据从一种形式转换到另外一种形式. ORM提供了所有SQL语句的生成,代码人员远离了数据库概念.从 ...
- PHP代码审计基础-中级篇
初级篇更多是对那些已有的版本漏洞分析,存在安全问题的函数进行讲解,中级篇更多是针对用户输入对漏洞进行利用 中级篇更多是考虑由用户输入导致的安全问题. 预备工具首先要有php本地环境可以调试代码 总结就 ...
- 14.Linux压缩/打包
今天来讲解一下压缩和打包的相关命令,首先得先明确两个概念,即:压缩和打包 压缩:将文件或目录进行压强,使文件或目录大小变小 打包:表示将目录中的所有内容,捆绑在一起,方便传输,打包后的文件会变大,不一 ...
- 02--Java Jshell的使用 最适合入门的Java教程
JShell JShell目标 Java Shell 工具(简称:JShell)是一个用于学习Java编程语言和构建Java代码原型的交互式工具.JShell是一个Read-Evaluate-Prin ...