一、什么是双向链表

我们知道单链表只能从头遍历到尾或从尾遍历到头(一般从头遍历到尾),即链表相连的过程是单向的,实现的原理是上一个链表中有一个指向下一个的引用。它有一个比较明显的缺点:

我们可以轻松的到达下一个节点,但是回到前一个节点是很困难的,但是,在实际开发中,经常会遇到需要回到上一个节点的情况,所以这里就需要双向链表。

所谓双向链表就是:既可以从头遍历到尾,又可以从尾遍历到头的链表,但是,双向链表也是有缺点的,比如:每次在插入或删除某个节点的时候,需要处理四个节点的引用,而不是两个,并且相对于单链表而言,占用内存会更大一些,但是这些缺点和我们使用起来的方便程度相比,是微不足道的。
我们就来看看双向链表的结构图。如下所示:

在这里插入图片描述

双向链表的特点:

  • 可以使用一个head和一个tail分别指向头部和尾部的节点
  • 每个节点由三部分组成,前一个节点的指针(prev)、保存的元素(data)、后一个节点的指针(next)
  • 双向链表的第一个节点的prev是null
  • 双向链表的最后的节点的next是null

我们可以将其抽象为:

在这里插入图片描述

知道了双向链表的结构,我们在一起来看看双向链表的封装。

二、双向链表的封装

首先,我们封装一个DoublyLinkedList类,用于表示链表结构,在这个类里面在封装一个内部类Node,用于封装每一个节点上的信息(指向前一个节点的引用、数据和指向下一个节点的引用),然后在Node类中保存三个属性,一个是链表的长度,一个是链表中的头结点,一个是链表的尾节点。如下所示:

function DoublyLinkedList(){
	 this.head = null;
	 this.tail = null;
	 this.length = 0;
	 function Node(data){
		 this.data = data;
		 this.prev = null;
		 this.next = null;
	}
 

三、双向链表的常用操作

然后可以在里面添加双向链表常用的操作:

append(element:向列表尾部添加一个项

insert(position,element):向列表的特定位置插入一个项

get(position):获取对应位置的元素

indexOf(element):返回元素在列表中的索引,如果列表中没有该元素则返回-1

update(position,ele):修改某个位置的元素

removeAt(position):从列表的指定位置移除一项

remove(element):从列表中移除一项

isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0返回false

size():返回链表包含的元素个数,与数组的length属性相关

toString():由于列表项用到了Node类,需要重写继承自JavaScript对象默认的toString方法,让其输出元素的值

forwardString():返回正向遍历的节点字符串形式

backwardString():返回反向遍历的节点字符串形式

接下来们就来一个一个实现。

1、append(element)方法-----向列表尾部添加一个项

这个方法和单链表的方法相似,先创建一个新列表,在判断链表是否为空,如果为空,则直接让链表的头部指向新建立的链表。如果不为空,则让新节点的前驱指针指向链表尾部,链表尾部的节点指向新链表。

Doubly.prototype.append = function(data){
                var newNode = new Node(data);
                if(this.length == 0){
                    this.head = newNode;
                }else{
                    newNode.prev = this.tail;
                    this.tail.next = newNode;
                    }
                this.tail = newNode;
                this.length  = 1;
            }

2、将链表转化为字符串形式

1、toString():正向输出元素的值

这个方法主要是获取每一个元素,该方法默认获取链表的任何元素都必须从第一个节点开始,所以我们可以从头结点开始,循环遍历每一个节点,并且取出其中的element,拼接成字符串,并将字符串返回。具体方法为创建一个临时节点current,让这个临时节点指向双向链表的头部,然后通过next指针依次向后遍历,将每次遍历得到的数据进行拼接。

DoublyLinkedList.prototype.tostring = function(){
                var current = this.head;
                var str = '';
                while(current){
                    str  = current.data   ' ';
                    current = current.next;
                }
               return str;
            }

2、forwardString():返回正向遍历的节点字符串形式

该方法也是实现双向链表的正向打印并输出,所以我们这里可以直接调用上一个方法:

DoublyLinkedList.prototype.forwardString = function(){
               return this.toString()
            }

3、backwardString():返回反向遍历的节点字符串形式

这个方法主要是从后往前遍历获取每一个元素并打印,所以我们可以从尾结点开始,循环遍历每一个节点,并且取出其中的element,拼接成字符串,并将字符串返回。具体方法为创建一个临时节点current,让这个临时节点指向双向链表的尾部,然后通过prev指针依次向前遍历,将每次遍历得到的数据进行拼接。

 DoublyLinkedList.prototype.backwardString = function(){
                var current = this.tail;
                var str = '';
                //依次向前遍历,获取每一个节点
                while(current){
                    str  = current.data  ' ';
                    current = current.prev;
                }
                return str;
            }

验证:

例如我们通过append()方法创建一个含有三个数据的双向链表,然后分别将他们正向拼接和反向拼接:

var list = new DoublyLinkedList();
         list.append("a");
         list.append("b");
         list.append("c");
         list.append("d");
         console.log('toString()方法:' list.toString());
         console.log('forwardString()方法:' list.forwardString());
         console.log('backwardString()方法:' list.backwardString());

打印结果为:

在这里插入图片描述

验证成功。

3、insert(position,element):向列表的特定位置插入一个项

这个方法相对来说是比较复杂的,首先,先判断要插入的位置是否越界,在不越界的情况下,在根据链表的情况判断,如果链表为空,则插入节点为第一个元素,直接让头结点和尾节点的指针指向新创建的节点;如果链表不为空,则插入的位置有三种情况:插入到首位,插入到末尾和插入到中间部位。具体操作如下:

DoublyLinkedList.prototype.insert = function(position,data){
        var newNode = new Node(data);
         //越界判断,如果不满足,返回false
         if(position<0 || position>this.length){
             return false;
         }else{
             //再次判断
             //如果链表为空,直接插入
             if(position==0){
                 this.head = newNode;
                 this.tail = newNode;
             }else {
             //如果链表不为空
                 //如果插入位置为末尾
                 if(position == this.length){
                     this.tail.next = newNode;
                     newNode.prev = this.tail;
                     this.tail = newNode;
                 }else if(position == 0){
                     //如果插入位置为首位
                     this.head.prev = newNode;
                     newNode.next = this.head;
                     this.head = newNode;
                 }else{
                     //如果插入位置为中间
                     var index = 0;
                     var current = this.head;
                     while(index   <position){
                         current = current.next;
                     }
                         newNode.next = current;
                         newNode.prev = current.prev;
                         current.prev.next = newNode;
                         current.prev = newNode;
                 
             }
             this.length  = 1;
         }
        
     }
 }

验证:如果在1位置插入xl,如下所示:

list.insert(1,'xl')
console.log(list.toString());

运行结果为:

在这里插入图片描述

验证成功。

4、get(position):获取对应位置的元素

这个方法首先要判断位置是否越界,在不越界的情况下,定义一个临时节点和索引,让这个临时节点的指针指向头结点,遍历临时节点,同时索引自加,如果遍历道德节点的位置等于要获取元素的位置,则临时节点对应位置的元素就是要获得的元素。

DoublyLinkedList.prototype.get = function(position){
        
        if(position<0 || position>=this.length){
            return false;
        }else{
            var index = 0;
            var current = this.head;
            while(index   <position){
                current = current.next;
            }
            return current.data;
        }
    }

验证:查询position = 2的元素

console.log('list.get(2):' list.get(2));

结果为:

在这里插入图片描述

验证成功。

但是,这种查找方式有一个弊端,即只能从前向后查找,在某些情况下效率就会很低,所以我们就可以两头查找,具体查找方式为:当我们要查找的节点的位置小于或等于链表长度的一半,我们就可以从前向后查找,如果要查找的节点的位置大于长度的一半,我们就从后往前查找。实现代码为:

    DoublyLinkedList.prototype.get = function(position){
        
        if(position<0 || position>=this.length){
            return false;
        }else{
            var len = Math.floor(this.length/2);
            if(position<=len){
                var index = 0;
                var current = this.head;
                while(index   <position){
                 current = current.next;
                }
            }else{
                var index  = this.length-1;
                var current = this.tail;
                while(index-->position){
                    current = current.prev;
                }
            }
            return current.data;
        }
    }

5、indexOf(element):返回元素在列表中的索引

创建一个临时节点和索引,让临时节点指向头结点,依次向后遍历,同时让索引跟着递增,如果遍历的临时节点所得到的元素等于我们指定的元素,则此时的临时节点的位置就是我们目标位置,该位置的索引就是目标值的索引。

DoublyLinkedList.prototype.indexOf = function(data){
        var current = this.head;
        var index = 0;
        while(current){
            if(current.data === data){
            return index;
         }
         current = current.next;
         index   ;
        }
        return -1;
    }

在这里插入图片描述

验证成功。

6、 update(position,ele):修改某个位置的元素

首先判断要修改的位置是否越界,在不越界的情况下,定义一个临时节点和索引,让临时节点指向头结点,索引位置为0,遍历临时节点并判断临时节点此时的索引和要修改元素的位置是否相等,如果相等的话,此时临时节点的位置就是要修改元素的位置,可以直接给临时节点的数据域更改元素。

DoublyLinkedList.prototype.update = function(position,newData){
        if(position<0 || position>=this.length){
            return false;
        }else{
            var index = 0;
            var current = this.head;
            while(index   <position){
                current = curent.next;
            }
            current.data = newData;
            return true;
        }
    }

验证:将0位置的数据换成rc

list.update(0,'rc')
       console.log("list.update(0,'rc')为:");
       console.log(list.toString());

在这里插入图片描述

验证成功。

7、removeAt(position):从列表的指定位置移除一项

这个方法和insert()方法的思想相似,首先判断是否越界,在不越界的情况下,如果链表只有一个节点,则直接移除该项,让链表的头结点和尾节点均指向空。如果链表有多个节点的情况下,也分为三种情况,操作如下:

DoublyLinkedList.prototype.removeAt = function(position){
        if(position<0 || position>=this.length){
            return null;
        }else{
            var current = this.head;
            if(this.length ===1){
            //链表长度为1
                this.head = null;
                this.tail = null
            }else{//链表长度不为1
                if(position === 0){
                //删除首位
                    this.head.next.prev = null;
                    this.head = this.head.next;
                }else if(position === this.length-1){
                    this.tail.prev.next = null;
                    this.tail = this.tail.prev;
                }else{
                    var index = 0;
                    while(index   <position){
                        current = current.next;
                    }
                    current.prev.next = current.next;
                    current.next.pre = current.prev;
                }
            }
            this.length -=1;
            return current.data;
        }
    }

验证:删除位置3的数据:

list.removeAt(3)
       console.log("list.removeAt(3)为:");
       console.log(list.toString());

结果为:

在这里插入图片描述

验证成功

8、remove(element):从列表中移除一项

可以直接根据indexOf(element)方法获取元素在链表中的位置,在根据removeAt(position)方法将其删除。

 DoublyLinkedList.prototype.remove = function(data){
        var index = this.indexOf(data);
        return this.removeAt(index);
    }

验证:删除数据为rc的节点:

list.remove('rc');
       console.log("list.remove('rc')为:");
       console.log(list.toString());

在这里插入图片描述

9、isEmpty():判断链表是否为空

直接判断链表中元素的个数是否为零,如果为零则为空,返回true,否则不为空,返回false.

DoublyLinkedList.prototype.isEmpty = function(){
        return this.length ==0;
    }
    

验证:

console.log("链表是否为空:" list.isEmpty());     

在这里插入图片描述

10、size():返回链表包含的元素个数

链表长度就是元素个数。

DoublyLinkedList.prototype.size = function(){
        return this.length;
    }

验证:

 console.log("链表的元素个数为:" list.size());

在这里插入图片描述

以上就是JavaScript实现双向链表过程解析的详细内容,更多关于JavaScript 双向链表的资料请关注Devmax其它相关文章!

JavaScript实现双向链表过程解析的更多相关文章

  1. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  2. HTML5数字输入仅接受整数的实现代码

    这篇文章主要介绍了HTML5数字输入仅接受整数的实现代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. amaze ui 的使用详细教程

    这篇文章主要介绍了amaze ui 的使用详细教程,本文通过多种方法给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  4. html5简介_动力节点Java学院整理

    这篇文章主要介绍了html5简介,用于指定构建网页的元素,这些元素中的大多数都用于描述网页内容,有兴趣的可以了解一下

  5. ios 8 Homescreen webapp,关闭和打开iPad停止javascript

    我有一个适用于iPad的全屏HTML5网络应用程序,并且刚刚安装了IOS8来试用它,它一切正常,直到你关闭并重新启动iPad.一旦web应用程序重新启动javascript就会停止并加载新页面不会重新启动它.在iPad上的Safari中打开同一页面时,关闭和打开iPad会继续按预期工作.其他人注意到了这个或想出了一个解决方案吗?解决方法这似乎是我在iOS8.1.1更新中解决的.

  6. iOS 6 javascript与object.defineProperty的间歇性问题

    当访问使用较新的Object.defineProperty语法定义属性的对象的属性时,有没有其他人注意到新iOS6javascript引擎中的间歇性错误/问题?https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperty我正在看到javascript失败的情况,说

  7. ios – 如何使用JSExport导出内部类的方法

    解决方法似乎没有办法将内部类函数导出到javascript.我将内部类移出并创建了独立的类,它起作用了.

  8. 静音iOS推送通知与React Native应用程序在后台

    我有一个ReactNative应用程序,我试图获得一个发送到JavaScript处理程序的静默iOS推送通知.我看到的行为是AppDelegate中的didReceiveRemoteNotification函数被调用,但是我的JavaScript中的处理程序不会被调用,除非应用程序在前台,或者最近才被关闭.我很困惑的事情显然是应用程序正在被唤醒,并且它的didReceiveRemoteNotifi

  9. ios – 内存泄漏与UIWebView和Javascript

    清楚地包含一个Javascript文件到我的HTML是使UIWebView泄漏内存.当我重复使用相同的UIWebView对象时,或者每当我有内容实例化一个新的漏洞时,会出现泄漏的事实,导致我认为必须有一些JavaScript文件被loadHTMLString处理,导致泄漏.有人知道如何解决这个问题吗?

  10. iOS应用程序的UI自动化测试如何与乐器和Javascript

    从WWDC2010视频会议中了解iOS应用程序的自动化UI测试,但没有实践.从代码项目project,我们可以有一个例子.这个问题在这里听到有涉及这个的人.任何限制?解决方法我建议从AlexWollmer开始使用thisblogpost.他创建了一个非常有用的JavaScript库:tuneup_jswithtest()函数,它允许测试分离和有用的帮助者以及为自动化仪器编写测试的断言.

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部