用js来实现那些数据结构08(链表02-双向链表)

其实无论在任何语言中,一种数据结构往往会有很多的延伸和变种以应对不同场景的需要。其实前面我们所学过的栈和队列也是可以用链表来实现的。有兴趣的小伙伴可以自己尝试着去实现以下。有点跑题了…,我们还是

大家好,又见面了,我是你们的朋友全栈君。

  其实无论在任何语言中,一种数据结构往往会有很多的延伸和变种以应对不同场景的需要。其实前面我们所学过的栈和队列也是可以用链表来实现的。有兴趣的小伙伴可以自己尝试着去实现以下。

  有点跑题了…,我们还是说回链表,在基础链表之外,还有双向链表和循环链表和双向循环链表。这篇文章会详细的介绍一下双向链表,但是不会详细的去讲解循环链表。因为其实真的没有太大的区别。链表和循环链表的唯一的区别在于,最后一个元素指向下一个元素的指针不是null,而是head。

  其实循环链表只能从头到尾的循环,而双向循环链表可以两个方向循环,想怎么玩怎么玩。

  嗯…又跑题了,我们还是来说双向链表吧。

  顾名思义…双向链表就是….双向链表!额…开个玩笑…咱们进入正题吧….

  其实简单说双向链表与链表的区别就在于,双向链表不仅仅有一个指向下一个节点元素的指针,还同时拥有一个指向上一个节点元素的指针。前后都可以链接,故,称之为双向链表。

  那么既然是双向的指针,所以我们的代码需要新增一些东西。

  由于双向链表内的一些方法与链表无异,所以这里只说明一下那些区别明显有重要意义的地方。不再贴上所有的代码。

function DoublyLinkedList() {
  let Node = function (element) {
    this.element = element;
    this.next = null;
    //在双向链表中,这里多了个指向前一个节点元素的指针prev
    this.prev = null;
  }

  let length = 0;
  let head = null;
  //同样的这里多了一个保存链表最后一项节点的引用变量,为什么要加这个变量?
  //因为是双向链表,普通链表只能从头到尾的迭代各节点元素,一方面是因为普通链表中只有一个存储头部节点元素的head变量。
  //但是双向链表可以从尾部开始迭代,这就是tail的意义。
  let tail = null;
}

  这就是双向链表的类的变动(不包括其中的方法),我们可以看到只是多了node节点元素中prev(前一个)节点元素的指针,还有tail变量对尾部节点元素的引用。

  那么下面我们来看看insert方法的变化。

//我们来看看双向链表中insert方法,普通链表中,我们只需要控制next指针就可以了,但是在双向链表中,在控制next指针的同时,我们还要控制prev指针
this.insert = function (position,element) {
  //在普通链表中在任意位置添加元素有两种情况,一个是添加到头部,另外一个是除了头部以外的其他位置,
  //在双向链表中除了这两种情况,还多了一种,添加在链表尾部
  if(position >= 0 && position <= length) {
    let node = new Node(element);
    let current = head;
    let previous;
    let index = 0;
    //添加到头部的情况
    if(position === 0) {
      //这里,如果head为null,也就是说该链表是没有任何节点元素的情况,那么加入的这个节点元素在链表中是唯一的
      //所以,head引用为node,tail的引用也为node
      if(!head) {
        head = node;
        tail = node;
      //那么如果,head不为null,说明链表中存在至少一个元素。
      } else {
        //由于current就是head,那么要插入节点元素的话只要把node的next指针指向current,就说明我们在current前面插入了该节点元素。
        node.next = current;
        //因为是双向列表,我们还要给current.prev一个指向。
        current.prev = node;
        //那么既然我们在current前面插入了元素,这里也就要改变head的引用,变为我们插入的node
        head = node;
      }
      //如果我们想要插入尾部的情况
    } else if(position === length) {
      //这里稍微有趣一点,这里我们要在尾部加入元素,不用像普通链表那样迭代到最后一项再操作。
      //我们只需要把current直接置为tail的引用就可以了,方便快捷
      current = tail;
      //那么我们已经拿到了最后一项节点元素的引用并且设置为了current。
      //我们只需要把current(tail)的next指向node节点元素,并且把node的prev只想current。
      //其实就是说,current节点的next指针不再是null了,因为我们在它的后面增加了一个“插入元素”,所以它的next指针为node
      //而此时node的prev指针也就理所当然的指向了current。
      current.next = node;
      node.prev = current;
      //插入元素完成,但是我们此时的tail其实是current不是node,所以更改一下tail的引用。
      tail = node;
    } else {
      while(index++ < position) {
        //依次往后移动...不多说
        previous = current;
        current = current.next;
      }
      //在移动到需要插入节点元素的位置时。
      //我们要插入在current的前面,自然就会有下面的结果了
      node.next = current;
      previous.next = node;
      //但是我们由于是双向链表,我们不仅仅要修改next指针,还要修改prev指针
      current.prev = node;
      node.prev = previous;
    }
    length++;
    return true;
  } else {
    return false;
  }
}

  其实insert方法在双向链表中,只是多了一种尾部情况的判断以及prev指针的改变,注释已经说的很详细了,不多说废话,我们继续看看removeAt方法在双向链表中的实现。

this.removeAt = function (position) {
  if(position > -1 && position < length) {
    let current = head,previous,index = 0;

    if(position === 0) {
      head = current.next;
      if(length === 1) {
        tail = null
      } else {
        head.prev = null;
      }
    } else if(position === length - 1) {
      current = tail;
      tail = current.prev;
      tail.next = null;
    } else {
      while(index++ < position) {
        previous = current;
        current = current.next;
      }

      previous.next = current.next;
      current.next.prev = previous;
    }

    length --;
    return current.element;
  } else {
    return null;
  }
}

  这是双向链表的removeAt方法,我不想解释了,因为我觉得如果你认真的阅读了这两篇文章,这个方法你绝对可以看懂了。如果你还是看不懂,请从头再来

  这里我们就基本介绍完了双向链表…等等…不是还有其它的方法么?怎么不说了?

  insert可以在任意位置插入元素,removeAt可以在任意位置移除元素,想要实现其它方法就不难了吧。。。。。再说下去也是重复前面说过的内容了。实在无须如此….

  那么我们对于链表的了解就告一段落,下一篇文章我们一起来看看集合这个东东。感觉会比链表好玩一些。嗯…对,跟数学中的集合有关系。

  

  最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/124425.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • pythoncharm注释快捷键_JAVA注释快捷键

    pythoncharm注释快捷键_JAVA注释快捷键Pycharm中常用快捷键使用及注释方式1.快捷键设置(Ctrl+Alt+s)或File—>Settings—>Keymap—>搜索栏搜索’format’—>Code快速创建文件(Alt+Insert)快速注释代码(Ctrl+/)快速取消注释代码(Ctrl+/)复制一行代码(Ctrl+D)撤销对代码的修改(Ctrl+…

    2022年8月27日
    5
  • java中的static关键字的作用?

    java中的static关键字的作用?是静态修饰符,什么叫静态修饰符呢?大家都知道,在程序中任何变量或者代码都是在编译时由系统自动分配内存来存储的,而所谓静态就是指在编译后所分配的内存会一直存在,直到程序退出内存才会释放这个空间,也就是只要程序在运行,那么这块内存就会一直存在。这样做有什么意义呢?在Java程序里面,所有的东西都是对象,而对象的抽象就是类,对于一个类而言,如果要使用他的成员,那么普通情况下必须先实例化对象后,通过对象

    2022年7月8日
    22
  • serdes知识详解_discussed是什么意思

    serdes知识详解_discussed是什么意思理解SerDesFPGA发展到今天,SerDes(Serializer-Deserializer)基本上是标配了。从PCI到PCIExpress,从ATA到SATA,从并行ADC接口到JESD204,从RIO到SerialRIO,…等等,都是在借助SerDes来提高性能。SerDes是非常复杂的数模混合设计,用户手册的内容只是描述了森林里面的一棵小树,并不能够解释SerDes是怎么工作

    2025年8月9日
    4
  • linux进程通信之信号[通俗易懂]

    linux进程通信之信号

    2022年1月21日
    41
  • U盘越狱iPhone绕ID最新教程及各种坑解决,吐血之作(超详细超简单教程)[通俗易懂]

    U盘越狱iPhone绕ID最新教程及各种坑解决,吐血之作(超详细超简单教程)[通俗易懂]iPhone解锁ID超详细教程及各种坑解决,吐血之作目录iPhone解锁ID超详细教程及各种坑解决,吐血之作一、工具下载二、工具安装三、操作教程这是安装多个苹果版本及虚拟机版本后成功的教程,由于资源上传到百度云盘下载只有几十KB,所以为了大家能够尽快的体验上苹果系统,文章中涉及的所有工具请大家加QQ群进行交流下载:1064543120一、工具下载准备一台Windows系统电脑 准备一个>2G存储U盘 下载群文件中balenaEtcher-Se…

    2022年9月23日
    4
  • springboot设置时区不起作用_docker设置时区

    springboot设置时区不起作用_docker设置时区第一步:确认docker时区进入容器中dockerexec-it容器namebash查看容器时区:date第二步确认数据库时区SELECTTIMEDIFF(NOW(),UTC_TIMESTAMP);如果显示的是08:00:00则是cst时区。如果不是cst时区,则执行Sql:setglobaltime_zone=’+8:00′;##修改mysql全局时区为北京时间,即我们所在的东8区settime_zone=’+8:00′;.

    2022年9月25日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号