对比普通的双向链表,双向循环链表的尾节点的 next 指向头节点,头节点的 prev 指向尾节点。

add方法
add(item) { // 生成新的节点 const newNode = new Node(item);
// 判断是否为空链表 if (!this.head) { // 原本是空链表 newNode.next = newNode; newNode.prev = newNode; this.head = newNode; this.tail = newNode; } else { // 说明当前的双向链表是有节点 newNode.prev = this.tail; this.tail.next = newNode;
newNode.next = this.head; // newNode作为最后一个节点,要指向回头节点 this.head.prev = newNode;
this.tail = newNode; }
// 无论上面进入哪个分支,都是添加 // 长度会增长 this.size++;}print方法
print(){ if(!this.head) return; // 如果链表为空,直接返回
let current = this.head; // current --> 头节点 let result = []; // 存储节点的内容
do{ result.push(current.data); current = current.next; }while(current !== this.head);
console.log(result.join("<->"));}addAt方法
// 接收两个参数// 1. 要插入的下标// 2. 新节点的数据addAt(index, item) { if (index < 0 || index > this.size) { throw new Error("索引值无效"); }
// 创建新的节点 const newNode = new Node(item);
// 没有进入上面的if,说明索引没有问题,接下来开始根据索引进行插入操作 if (index === 0) { if(!this.head){ // 链表为空,直接插入 newNode.next = newNode; newNode.prev = newNode; this.head = newNode; this.tail = newNode; } else { // 说明链表不为空,插入到头部,之前的头部节点就会成为新节点的 next newNode.next = this.head; newNode.prev = this.tail; this.head.prev = newNode; this.tail.next = newNode; this.head = newNode; } } else if (index === this.size) { // 进入此分支,说明是要插入到尾部 this.add(item); } else { // 插入到中间 // 这里会涉及到需要找到具体的位置 let current = this.head; let counter = 0; // 计数器,用于标注节点的个数,起到一个类似于数组下标的作用 while (current) { if (counter === index) { // 更新各个节点的指向 newNode.prev = current.prev; newNode.next = current; current.prev.next = newNode; current.prev = newNode; break; } // 不停的更新节点 current = current.next; counter++; } } this.size++;}remove方法
// 接收一个参数// 要删除的数据节点对应的数据remove(item) { let current = this.head; // 一开始指向头节点
if(!this.head) return false; // 如果链表为空,直接返回
do{ // 这里面就需要去寻找对应数据的那个节点 if (current.data === item) { // 进入此分支,说明找到了要删除的节点 if (current === this.head && current === this.tail) { // 进入此分支,说明整个双向链表只有一个节点 // 将头尾节点都置为空 this.head = null; this.tail = null; } else if (current === this.head) { // 说明当前要删除的节点是头节点 this.head = this.head.next; this.head.prev = this.tail; this.tail.next = this.head; } else if (current === this.tail) { // 说明要删除的节点为尾节点 this.tail = this.tail.prev; this.tail.next = this.head; this.head.prev = this.tail; } else { // 说明就是中间节点 current.prev.next = current.next; current.next.prev = current.prev; } // 无论上面进入哪一个分支,都会删除一个节点 this.size--; return true; } current = current.next; // 更新节点 } while(current !== this.head);
return false;}removeAt方法
// 接收一个参数// 要删除的节点所对应的索引值removeAt(index) { if (index < 0 || index >= this.size) { throw new Error("索引值有误"); }
let current = this.head; // 先记录头节点 let counter = 0; // 计数器,对下标进行计数
if (index === 0) { // 进入此分支,说明删除的是头节点 if (this.head === this.tail) { // 说明当前的双链表只有一个节点 this.head = null; this.tail = null; } else { this.head = this.head.next; this.head.prev = this.tail; this.tail.next = this.head; } } else { // 说明删除的是后面的节点 do{ if (counter === index) { // 找到要删除的节点的时候,会进入该分支 if (current === this.tail) { // 说明要删除的节点是尾节点 this.tail = current.prev; // 更新尾节点 this.tail.next = this.head; this.head.prev = this.tail; } else { // 说明就是中间的节点 current.prev.next = current.next; current.next.prev = current.prev; } break; } // 不断的更新节点 current = current.next; counter++; } while(current !== this.head); } this.size--;}reverse方法
reverse() { if (!this.head) return; // 如果是空链表,直接返回
let current = this.head; // 缓存头节点 let prev = null; // 用于缓存上一个节点 let next = null; // 用于缓存下一个节点
do{ next = current.next; current.next = prev; current.prev = next; prev = current; current = next; }while(current !== this.head);
// 更新头节点和尾节点 this.tail = this.head; // 原来的头变成了尾巴 this.head = prev; // 然后更新头节点 this.tail.next = this.head; // 更新尾节点的next指向新的头节点,保持循环结构 this.head.prev = this.tail; // 更新头节点的prev指向新的尾节点,保持循环结构}swap方法
// 接收2个参数// 是两个下标值swap(index1, index2) { if (index1 === index2) return false; // 如果索引相等,不需要交换 if ( index1 < 0 || index1 >= this.size || index2 < 0 || index2 >= this.size ) { throw new Error("索引值有错误"); }
let current = this.head; // 记录头节点 let counter = 0; //计数器,用于记录索引的,类似于数组的下标 let node1 = null; let node2 = null;
do{ if (counter === index1) node1 = current; if (counter === index2) node2 = current; if (node1 && node2) break; // 两个节点都找到了,跳出循环 current = current.next; counter++; }while(current !== this.head);
// 找到了两个节点,接下来进行交换操作 if (node1 && node2) { let temp = node1.data; node1.data = node2.data; node2.data = temp; return true; }
return false;}forEach方法
// 接收一个参数// 该参数是一个回调函数forEach(fn) { // 遍历整个链表,将所有的节点扔给这个回调函数 let current = this.head; do{ fn(current); current = current.next; }while(current !== this.head);}find方法
// 接收一个参数// 要查找的节点的数据是什么find(item) { let current = this.head; let counter = 0;
do{ if (current.data === item) { // 说明找到了 return counter; } current = current.next; counter++; }while(current !== this.head);
// 代码走到这儿,说明没有进入过 while 里面的 if // 说明没有找到 return -1;}最后总结一下:
无论是单向循环链表,还是双向循环链表,在重构方法的时候,主要就是 2 个地方:
- 循环:因为循环链表不会有 null 的情况,因此跳出循环的条件不能为
判断当前节点是否null - 头尾节点的处理:要始终保证尾节点的next指向头节点,头节点的prev指向尾节点
-EOF-