Skip to content

7.JavaScript 数据结构与算法(七)双向链表 #7

Description

@webVueBlog

单向链表和双向链表

单向链表

  • 只能从头遍历到尾或者从尾遍历到头(一般从头到尾)。
  • 链表相连的过程是单向的,实现原理是上一个节点中有指向下一个节点的引用。
  • 单向链表有一个比较明显的缺点:可以轻松到达下一个节点,但回到前一个节点很难,在实际开发中, 经常会遇到需要回到上一个节点的情况。

双向链表

  • 既可以从头遍历到尾,也可以从尾遍历到头。
  • 链表相连的过程是双向的。实现原理是一个节点既有向前连接的引用,也有一个向后连接的引用。
  • 双向链表可以有效的解决单向链表存在的问题。
  • 双向链表缺点:
    • 每次在插入或删除某个节点时,都需要处理四个引用,而不是两个,实现起来会困难些。
    • 相对于单向链表,所占内存空间更大一些。
    • 但是,相对于双向链表的便利性而言,这些缺点微不足道。

双向链表结构

  • 双向链表不仅有 head 指针指向第一个节点,而且有 tail 指针指向最后一个节点。
  • 每一个节点由三部分组成:item 储存数据、prev 指向前一个节点、next 指向后一个节点。
  • 双向链表的第一个节点的 prev 指向 null。
  • 双向链表的最后一个节点的 next 指向 null。

双向链表常见的操作

  • append(element) 向链表尾部追加一个新元素。
  • insert(position, element) 向链表的指定位置插入一个新元素。
  • getElement(position) 获取指定位置的元素。
  • indexOf(element) 返回元素在链表中的索引。如果链表中没有该元素就返回 -1。
  • update(position, element) 修改指定位置上的元素。
  • removeAt(position) 从链表中的删除指定位置的元素。
  • remove(element) 从链表删除指定的元素。
  • isEmpty() 如果链表中不包含任何元素,返回 trun,如果链表长度大于 0 则返回 false
  • size() 返回链表包含的元素个数,与数组的 length 属性类似。
  • toString() 由于链表项使用了 Node 类,就需要重写继承自 JavaScript 对象默认的 toString 方法,让其只输出元素的值。
  • forwardString() 返回正向遍历节点字符串形式。
  • backwordString() 返回反向遍历的节点的字符串形式。

双向链表的封装

创建双向链表类 DoublyLinkedList

  • DoublyNode 类继承单向链表的 Node 类,新添加 this.prev 属性,该属性用于指向上一个节点。
  • DoublyLinkedList 类继承 LinkedList 类,新添加 this.tail 属性,该属性指向末尾的节点。
// 双向链表的节点类(继承单向链表的节点类)classDoublyNodeextendsNode{constructor(element){super(element);this.prev=null;}}// 双向链表类继承单向链表类classDoublyLinkedListextendsLinkedList{constructor(){super();this.tail=null}}

append(element)

// append(element) 往双向链表尾部追加一个新的元素// 重写 append()append(element){// 创建双向链表节点constnewNode=newDoublyNode(element);// 追加元素if(this.head===null){this.head=newNode;this.tail=newNode;}else{// 跟单向链表不同,不用通过循环找到最后一个节点this.tail.next=newNode;newNode.prev=this.tail;this.tail=newNode;}this.length++;}

insert(position, element)

// insert(position,data) 插入元素// 重写insert()insert(position,element){// position 越界判断if(position<0||position>this.length)returnfalse;// 创建新的双向链表节点constnewNode=newDoublyNode(element);// 判断多种插入情况if(position===0){// 在第0个位置插入if(this.head===null){this.head=newNode;this.tail=newNode;}else{newNode.next=this.head;this.head.prev=newNode;this.head=newNode;}}elseif(position===this.length){this.tail.next=newNode;newNode.prev=this.tail;this.tail=newNode;}else{// 在0~this.length位置中间插入lettargetIndex=0;letcurrentNode=this.head;letpreviousNode=null;// 找到要插入位置的节点while(targetIndex++<position){previousNode=currentNode;currentNode=currentNode.next;}// 交换节点信息previousNode.next=newNode;newNode.pre=previousNode;newNode.next=currentNode;currentNode.prev=newNode;}this.length++;returntrue;}

removeAt(position)

// removeAt() 删除指定位置的节点// 重写 removeAt()removeAt(position){// position越界判断if(position<0||position>this.length-1)returnnull;// 根据不同情况删除元素letcurrentNode=this.head;if(position===0){// 删除第一个节点的情况if(this.length===1){// 链表内只有一个节点的情况this.head=null;this.tail=null;}else{// 链表内有多个节点的情况this.head=this.head.nextthis.head.prev=null}}elseif(position===this.length-1){// 删除最后一个节点的情况currentNode=this.tailthis.tail.prev.next=null;this.tail=this.tail.prev;}else{// 删除 0 ~ this.length - 1 里面节点的情况lettargetIndex=0;letpreviousNode=null;while(targetIndex++<position){previousNode=currentNode;currentNode=currentNode.next;}previousNode.next=currentNode.next;currentNode.next.prev=previousNode}this.length--;returncurrentNode.data;}

update(position, data)

// update(position, data) 修改指定位置的节点// 重写 update()update(position,data){// 删除position位置的节点constresult=this.removeAt(position);// 在position位置插入元素this.insert(position,data);returnresult;}

forwardToString()

// forwardToString() 链表数据从前往后以字符串形式返回forwardToString(){letcurrentNode=this.head;letresult='';// 遍历所有的节点,拼接为字符串,直到节点为nullwhile(currentNode){result+=currentNode.data+'--';currentNode=currentNode.next;}returnresult;}

backwardString()

// backwardString() 链表数据从后往前以字符串形式返回backwardString(){letcurrentNode=this.tailletresult='';// 遍历所有的节点,拼接为字符串,直到节点为nullwhile(currentNode){result+=currentNode.data+'--';currentNode=currentNode.prev;}returnresult;}

其他方法的实现

双向链表的其他方法通过继承单向链表来实现。

完整实现

classDoublyLinkedListextendsLinkedList{constructor(){super();this.tail=null;}// ------------ 链表的常见操作 ------------ //// append(element) 往双向链表尾部追加一个新的元素// 重写 append()append(element){// 1、创建双向链表节点constnewNode=newDoublyNode(element);// 2、追加元素if(this.head===null){this.head=newNode;this.tail=newNode;}else{// !!跟单向链表不同,不用通过循环找到最后一个节点// 巧妙之处this.tail.next=newNode;newNode.prev=this.tail;this.tail=newNode;}this.length++;}// insert(position, data) 插入元素// 重写 insert()insert(position,element){// 1、position 越界判断if(position<0||position>this.length)returnfalse;// 2、创建新的双向链表节点constnewNode=newDoublyNode(element);// 3、判断多种插入情况if(position===0){// 在第 0 个位置插入if(this.head===null){this.head=newNode;this.tail=newNode;}else{//== 巧妙之处:相处腾出 this.head 空间,留个 newNode 来赋值 ==//newNode.next=this.head;this.head.perv=newNode;this.head=newNode;}}elseif(position===this.length){// 在最后一个位置插入this.tail.next=newNode;newNode.prev=this.tail;this.tail=newNode;}else{// 在 0 ~ this.length 位置中间插入lettargetIndex=0;letcurrentNode=this.head;letpreviousNode=null;// 找到要插入位置的节点while(targetIndex++<position){previousNode=currentNode;currentNode=currentNode.next;}// 交换节点信息previousNode.next=newNode;newNode.prev=previousNode;newNode.next=currentNode;currentNode.prev=newNode;}this.length++;returntrue;}// getData() 继承单向链表getData(position){returnsuper.getData(position);}// indexOf() 继承单向链表indexOf(data){returnsuper.indexOf(data);}// removeAt() 删除指定位置的节点// 重写 removeAt()removeAt(position){// 1、position 越界判断if(position<0||position>this.length-1)returnnull;// 2、根据不同情况删除元素letcurrentNode=this.head;if(position===0){// 删除第一个节点的情况if(this.length===1){// 链表内只有一个节点的情况this.head=null;this.tail=null;}else{// 链表内有多个节点的情况this.head=this.head.next;this.head.prev=null;}}elseif(position===this.length-1){// 删除最后一个节点的情况currentNode=this.tail;this.tail.prev.next=null;this.tail=this.tail.prev;}else{// 删除 0 ~ this.length - 1 里面节点的情况lettargetIndex=0;letpreviousNode=null;while(targetIndex++<position){previousNode=currentNode;currentNode=currentNode.next;}previousNode.next=currentNode.next;currentNode.next.perv=previousNode;}this.length--;returncurrentNode.data;}// update(position, data) 修改指定位置的节点// 重写 update()update(position,data){// 1、删除 position 位置的节点constresult=this.removeAt(position);// 2、在 position 位置插入元素this.insert(position,data);returnresult;}// remove(data) 删除指定 data 所在的节点(继承单向链表)remove(data){returnsuper.remove(data);}// isEmpty() 判断链表是否为空isEmpty(){returnsuper.isEmpty();}// size() 获取链表的长度size(){returnsuper.size();}// forwardToString() 链表数据从前往后以字符串形式返回forwardToString(){letcurrentNode=this.head;letresult="";// 遍历所有的节点,拼接为字符串,直到节点为 nullwhile(currentNode){result+=currentNode.data+"--";currentNode=currentNode.next;}returnresult;}// backwardString() 链表数据从后往前以字符串形式返回backwardString(){letcurrentNode=this.tail;letresult="";// 遍历所有的节点,拼接为字符串,直到节点为 nullwhile(currentNode){result+=currentNode.data+"--";currentNode=currentNode.prev;}returnresult;}}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions