单向链表和双向链表
单向链表
- 只能从头遍历到尾或者从尾遍历到头(一般从头到尾)。
- 链表相连的过程是单向的,实现原理是上一个节点中有指向下一个节点的引用。
- 单向链表有一个比较明显的缺点:可以轻松到达下一个节点,但回到前一个节点很难,在实际开发中, 经常会遇到需要回到上一个节点的情况。
双向链表
- 既可以从头遍历到尾,也可以从尾遍历到头。
- 链表相连的过程是双向的。实现原理是一个节点既有向前连接的引用,也有一个向后连接的引用。
- 双向链表可以有效的解决单向链表存在的问题。
- 双向链表缺点:
- 每次在插入或删除某个节点时,都需要处理四个引用,而不是两个,实现起来会困难些。
- 相对于单向链表,所占内存空间更大一些。
- 但是,相对于双向链表的便利性而言,这些缺点微不足道。
双向链表结构
- 双向链表不仅有 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;}}
单向链表和双向链表
单向链表
双向链表
双向链表结构
双向链表常见的操作
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
this.prev属性,该属性用于指向上一个节点。this.tail属性,该属性指向末尾的节点。append(element)
insert(position, element)
removeAt(position)
update(position, data)
forwardToString()
backwardString()
其他方法的实现
双向链表的其他方法通过继承单向链表来实现。
完整实现