- 双向链表
- 实现双向链表
- 定义节点类
- 定义双向链表类
- append 向双向链表尾部添加一个新元素
- insert 向双向链表的任意位置插入新元素
- getElementAt() 返回双向链表中特定位置的元素
- remove 从双向链表中移除一个元素
- removeAt 从双向链表的特定位置移除一个元素
- removeHead 移除首节点
- removeTail 移除尾结点
- indexOf 返回指定元素在双向链表中的索引
- head 返回首节点
- tail 返回尾结点
- size 返回双向链表包含的元素个数
- isEmpty 判断双向链表是否为空
- clear 清空双向链表
- toString() 返回表示整个双向链表的字符串
- inverseToString() 返回双向链表从尾到头的字符串
- 完整代码
- 双向链表的应用
双向链表
双向链表是链表的一种,它的每个节点中都有两个指针,分别指向上一个节点和下一个节点,所以,从双向链表中的任意一个节点开始,都可以很方便的访问他的前驱节点和后继节点。如下图为双向链表的结构图:
双向链表与普通链表的区别在于,在普通链表中,一个节点只有一个指向下一个节点的指针域,而在双向链表中,有两个指针域,一个指向下一个节点,另一个指向上一个节点,如上图所示。
实现双向链表
定义节点类
双向链表中的节点只是比普通链表中的节点多了一个 prev 指针,我们的双向链表中的节点类可以继承自普通链表的节点类:
class DoublyNode {constructor(element) {this.element = element;this.prev = null;this.next = null;}}
定义双向链表类
DoublyLinkList 类是一种特殊的 LInkList 类,我们只需要扩展
import { defaultEquals } from './utils';class DoublyLinkList {constructor(equalsFn = defaultEquals) {// 链表内部的函数,用于比较链表中的元素是否相等this.equalsFn = equalsFn;// 存储双向链表中的元素个数this.count = 0;// 保存头节点(注意:head 永远指向头节点)this.head = null;// 保存尾结点(注意:tail 永远指向尾结点)this.tail = null;}}
接下来,我们为双向链表定义一些方法:
append(element) 向双向链表尾部添加一个新元素
insert(element, index) 向双向链表的任意位置插入新元素
getElementAt(index) 返回双向链表中特定位置的元素,若元素不存在,返回 undefined
remove(element) 从双向链表中移除一个元素
removeAt(index) 从双向链表的特定位置移除一个元素
removeHead() 移除首节点
removeTail() 移除尾结点
indexOf(element) 返回指定元素在双向链表中的索引,若没有该元素则返回 -1
head() 返回首节点
tail() 返回尾结点
size() 返回双向链表包含的元素个数,即返回链表的长度
isEmpty() 判断双向链表是否为空
clear() 清空双向链表
toString() 返回表示整个双向链表的字符串
inverseToString() 返回双向链表从头到尾的字符串
append 向双向链表尾部添加一个新元素
向双向链表尾部添加一个新元素时,和向普通链表尾部添加新元素一样,也可能有两中情景:
- 双向链表为空,添加的是第一个元素
双向链表不为空,向其追加元素
append(element) {const node = new DoublyNode();// 双向链表为空,添加的是第一个元素if (this.head == null) {// 让 head 指向新创建的节点this.head = node;// 让 tail 属性指向新创建的节点 tail 属性永远指向双向链表的最后一个元素this.tail = node;} else {// 双向链表不为空,向双向链表尾部添加元素// 将尾部元素的 next 指针指向新节点,新节点就变成了新的尾结点this.tail.next = node;// 将新创建的节点的 prev 指针指向旧的尾结点node.prev = this.tail;// tail 属性永远是链表最后一个元素的引用this.tail = node;}this.count++;}
我们来分析一下这两种情景:
第一种情景:向空的双向链表添加一个元素。当我们创建一个双向链表实例时,head 默认会指向 null,也就意味着此时的双向链表是一个空链表,我们是在向双向链表添加第一个元素,因此我们要做的就是让 head 指向这个新建的节点 node,同时也要把 tail 指向 node,即此时的 node 节点既是首节点也是尾结点。
// 双向链表为空,添加的是第一个元素if (this.head == null) {// 让 head 指向新创建的节点this.head = node;// 让 tail 属性指向新创建的节点 tail 属性永远指向双向链表的最后一个元素this.tail = node;}
第二种情景:向一个不为空的双向链表添加新元素。要向双向链表的尾部添加一个新元素,首先需要找到最后一个元素。在双向链表中,tail 属性永远都是对双向链表最后一个元素的引用,因此我们可以很方便的找到双向链表中的最后一个元素。然后将 tail 的 next 指针指向要添加到双向链表的新节点,再将 tail 属性指向新节点,因为 tail 属性永远都是指向双向链表中的最后一个元素。
else {// 双向链表不为空,向双向链表尾部添加元素// 将尾部元素的 next 指针指向新节点,新节点就变成了新的尾结点this.tail.next = node;// 将新创建的节点的 prev 指针指向旧的尾结点node.prev = this.tail;// tail 属性永远是链表最后一个元素的引用this.tail = node;}
insert 向双向链表的任意位置插入新元素
insert 方法向双向链表的任意位置插入一个新元素。向双向链表中插入新元素时,需要考虑四种情景:
- 插入的位置不合法,元素插入失败
- 在双向链表的起点位置插入新元素
- 在双向链表的尾部插入新元素
在双向链表的中间插入新元素 ```javascript insert(element, index) { // 插入的位置合法,向双向链表中插入新元素 if (index >=0 && index <= this.count) {
const node = new DoublyNode(element); let current = this.head;
if (index === 0) {
// 在双向链表的头部插入元素if (this.head == null) { // 双向链表为空链表// 此时添加的 node 节点既是头节点也是尾节点this.head = node;this.tail = node;} else { // 双向链表不为空,此时添加的 node 将会成为 头节点// 将 node 节点的 next 指针指向当前的头节点node.next = this.head;// 将当前的头节点的 prev 指针指向 node 节点this.head.prev = node;// 将 head 指向 node,node成为新的头节点this.head = node;}
} else if (index === this.count) {
// 在双向链表的尾部插入元素,此时添加的 node 将会成为 尾节点current = this.tail;// 将当前的尾节点的 next 指针指向 node 节点current.next = node;// 将 node 节点的 prev 指针指向当前的尾节点node.prev = current;// 将 tail 属性指向 node,node成为新的尾节点this.tail = node;
} else {
// 在双向链表的其他位置插入元素// 首先获取插入位置的前一个节点const previous = this.getElementAt(index - 1);// 插入位置的后一个节点current = previous.next;// 将 node 节点插入 previous 和 current 之间// 则将 node 的 next 指针指向 current,prev 指针指向 previousnode.next = current;node.prev = previous;// node 节点插入后,previous 的 next 指针指向 node 节点previous.next = node;// current(插入位置的后一个节点) 的 prev 指针指向新插入的 node 节点current.prev = node;
}
// 新节点插入后,双向链表的长度增加 1 this.count++; // 返回 true,表示元素成功插入到双向链表中 return true; }
// 插入的位置不合法,返回false,表示元素没有添加到双向链表中 return false;
}
下面我们逐一分析这四种情景:第一种情景:插入的位置不合法。由于我们是根据位置(索引)来插入元素,因此在插入前需要先检查越界值,如果越界了,就返回 false,表示元素没有插入到双向链表中。```javascriptif (index >=0 && index <= this.count) {}// 插入的位置不合法,返回false,表示元素没有添加到双向链表中return false;
第二种情景:在双向链表的第一个位置(起点) 插入一个新元素。如果双向链表为空,我们只需要把 head 和 tail 属性都指向这个新节点即可,此时的 node 节点既是头节点也是尾结点。如果双向链表不为空,我们就将新加节点 node 的 next 指针指向双向链表中当前的头节点 head,然后将 head 的 prev 指针指向新加节点 node,最后将 head 属性指向 node 节点,新插入的 node 节点就成为了新的头节点。
if (index === 0) {// 在双向链表的头部插入元素if (this.head == null) { // 双向链表为空链表// 此时添加的 node 节点既是头节点也是尾节点this.head = node;this.tail = node;} else { // 双向链表不为空,此时添加的 node 将会成为 头节点// 将 node 节点的 next 指针指向当前的头节点node.next = this.head;// 将当前的头节点的 prev 指针指向 node 节点this.head.prev = node;// 将 head 指向 node,node成为新的头节点this.head = node;}}
第三种情景:在双向链表的尾部添加一个新元素。在这种情景下,我们需要把最后一个节点的 next 指针指向 node 节点,然后把 node 节点的 prev 指针指向双向链表中当前的尾结点,最后将 tail 属性指向 node 节点,这样新添加的node节点就成为了新的尾结点了。
else if (index === this.count) {// 在双向链表的尾部插入元素,此时添加的 node 将会成为 尾节点current = this.tail;// 将当前的尾节点的 next 指针指向 node 节点current.next = node;// 将 node 节点的 prev 指针指向当前的尾节点node.prev = current;// 将 tail 属性指向 node,node成为新的尾节点this.tail = node;}
第四种情景:在链表的中间插入一个新元素。在这种情景下,我们需要迭代双向链表找到要插入的位置。调用 getElementAt 方法找到插入位置的前一个节点 previous,然后将 插入位置的下一个节点 previous.next 赋值给 current 变量。我们现在要做的就是在 previous 和 current 之间插入新节点,首先将新插入的节点 node 的next 指针指向 current ,previous 的 next 指针指向 node 节点,然后再将 current 的 prev 指针指向 node 节点,node 节点的 prev 指针指向 previous ,这样,新节点 node 就插入了 previous 和 current 之间。
else {// 在双向链表的其他位置插入元素// 首先获取插入位置的前一个节点const previous = this.getElementAt(index - 1);// 插入位置的后一个节点current = previous.next;// 将 node 节点插入 previous 和 current 之间// 则将 node 的 next 指针指向 current,prev 指针指向 previousnode.next = current;node.prev = previous;// node 节点插入后,previous 的 next 指针指向 node 节点previous.next = node;// current(插入位置的后一个节点) 的 prev 指针指向新插入的 node 节点current.prev = node;}
getElementAt() 返回双向链表中特定位置的元素
getElementAt 方法返回双向链表中特定位置的元素,若元素不存在,返回 undefined
getElementAt(index) {// 传入的位置合法,迭代双向链表查找目标位置的元素if (index >= 0 && index <= this.count) {// 从第一个元素迭代双向链表let node = this.head;for (let i = 0; i < index && node != null; i++) {// 迭代整个双向链表直到目标 index,结束循环时,此时的 node 元素是目标index 的前一个元素// 因此需要使用 node.next 来获取目标 index 的元素node = node.next;}return node;}// 传入的位置不合法,即在双向链表中找不到该位置的节点,返回 undefinedreturn undefined;}
代码分析:
我们首先会对传入的 index 参数做合法性检查,如果传入的位置是不合法的参数,我们会返回 undefined,因为这个位置在双向链表中不存在。
然后从双向链表的第一个节点开始,迭代整个链表直到目标index,当结束循环时,node 就是我们要查找的目标节点。
remove 从双向链表中移除一个元素
remove(element) {const index = this.indexOf(element);return this.removeAt(index)}
removeAt 从双向链表的特定位置移除一个元素
removeAt 方法从双向链表的特定位置移除一个元素。我们需要处理四种情景:
- 移除的位置不合法,元素移除失败
- 移除双向链表中的第一个元素
- 移除双向链表的最后一个元素
- 移除双向链表中间的元素
removeAt(index) {if (index >= 0 && index < this.count) {// current 变量是对双向链表中第一个节点的引用let current = this.head;if (index === 0) {// 删除双向链表的头部元素// 删除头部元素,把 head 指向第二个节点即可this.head = this.head.next;if (this.count === 1) {// 链表只有一个节点时,该节点的 prev 指针和 next 指针都为 null// 如果链表中只有一个节点,删除了头部节点,就变成了空的双向链表// 因此需要将 tail 设为 nullthis.tail = null;} else {// 将新的头部节点的 prev 指针设为 nullthis.head.prev = null;}} else if (index === this.count - 1) {// 删除双向链表的尾部元素current = this.tail;// tail 是对最后一个元素的引用,删除最后一个元素,直接将 tail 的引用更新为倒数第二个元素即可this.tail = current.prev;// 将新的尾部元素的 next 指针设为 nullthis.tail.next = null;} else {// 删除双向链表的中间元素// current 变量就是要删除的元素current = this.getElementAt(index);// 要删除元素的前一个元素const previous = current.prev;// 将 previous 的 next 指针指向要删除元素的下一个元素previous.next = current.next;// 将 要删除元素的下一个元素的 prev 指针指向 previouscurrent.next.prev = previous;}this.count--;return current.element;}return undefined;}
接下来,我们分析一下这四种情景:
第一种情景:移除的位置不合法。由于 removeAt 方法是根据位置 (index) 移除双向链表中的元素,因此我们首先需要对 index 进行有效性检查。从 0(包括 0 )到双向链表的长度(count - 1)都是有效的位置。如果 index 不是有效的位置,就返回 undefined,即没有从链表中移除元素。
第二种情景:移除双向链表中的第一个元素。要移除双向链表中的第一个元素,我们把 head 的引用改为双向链表中的第二个元素(current.next)即可,同时还要将链表中的第二个元素的 prev 指针设为null,因为双向链表的第一个元素的 prev 指针永远都为 null(或 undefined)。如果双向链表中只有一个元素,移除了双向链表的第一个元素(也是最后一个元素),那么双向链表就为空了,此时我们还需要将 tail 设为 null。
if (index === 0) {// 删除双向链表的头部元素// 删除头部元素,把 head 指向第二个节点即可this.head = this.head.next;if (this.count === 1) {// 链表只有一个节点时,该节点的 prev 指针和 next 指针都为 null// 如果链表中只有一个节点,删除了头部节点,就变成了空的双向链表// 因此需要将 tail 设为 nullthis.tail = null;} else {// 将新的头部节点的 prev 指针设为 nullthis.head.prev = null;}}
第三种情景:移除双向链表的最后一个元素。因为我们已经有对双向链表最后一个元素的引用,因此我们可以很方便地移除最后一个元素。我们只需将 tail 的引用指向双向链表中倒数第二个元素即可,既然 tail 指向了倒数第二个元素,我们只需要将 next 指针设为 null 就可以了。这样,双向链表中的最后一个元素自然就被移除了。
else if (index === this.count - 1) {// 删除双向链表的尾部元素current = this.tail;// tail 是对最后一个元素的引用,删除最后一个元素,直接将 tail 的引用更新为倒数第二个元素即可this.tail = current.prev;// 将新的尾部元素的 next 指针设为 nullthis.tail.next = null;}
第四种情景:从双向链表中间移除一个元素。首先需要迭代双向链表,直到找到要移除的位置。我们将要移除的元素赋值给 current 变量,那么要移除元素的上一个元素就是 previous = current.prev,要移除元素的下一个元素就是
current.next,我们只需要将 previous 的 next 指针指向 current.next,current.next 的 prev 指针指向 previous ,就可以断开与要移除元素的连接,从而将要移除元素移除掉。
else {// 删除双向链表的中间元素// current 变量就是要删除的元素current = this.getElementAt(index);// 要删除元素的前一个元素const previous = current.prev;// 将 previous 的 next 指针指向要删除元素的下一个元素previous.next = current.next;// 将 要删除元素的下一个元素的 prev 指针指向 previouscurrent.next.prev = previous;}
removeHead 移除首节点
removeHead() {if (this.head == null) {return undefined;}const current = this.head;// 删除头部元素,把 head 指向第二个节点即可this.head = this.head.next;if (this.count === 1) {// 链表只有一个节点时,该节点的 prev 指针和 next 指针都为 null// 如果链表中只有一个节点,删除了头部节点,就变成了空的双向链表// 因此需要将 tail 设为 nullthis.tail = null;} else {// 将新的头部节点的 prev 指针设为 nullthis.head.prev = null;}return current.element;}
如果双向链表只有一个节点,移除首节点,双向链表就为空了,此时除了要将 head 设为 null,还需要将 tail 也设为 null;
如果双向链表中不止一个节点,移除首节点,只需将 head 指向双向链表的第二个节点,然后将第二个节点的 prev 指针设为 null 就可以了。
removeTail 移除尾结点
removeTail() {// 空链表if (this.head == null) {return undefined;}// current 变量是对尾结点的引用const current = this.tail;// tail 是对最后一个元素的引用,删除最后一个元素,直接将 tail 的引用更新为倒数第二个元素即可this.tail = current.prev;// 将新的尾部元素的 next 指针设为 nullthis.tail.next = null;return current.element;}
因为 tail 属性是对双向链表的最后一个节点的引用,要移除尾节点,直接将 tail 的引用更新为双向链表的倒数第二个节点,并将倒数第二个节点的 next 指针设为 null 即可。
indexOf 返回指定元素在双向链表中的索引
indexOf 方法接收一个元素的值,如果在双向链表中找到了它,就返回该元素的位置,否则就返回 -1,表示在双向链表中没有找到该元素
indexOf(element) {// current 变量是对双向链表中第一个元素(即首节点)的引用let current = this.head;// 迭代链表,从 head(索引为 0)开始let index = 0;while(current != null) {// 比较当前节点的元素与目标元素是否相等,若相等,当前节点就是我们要找的节点,返回当前节点的位置if (this.equalsFn(element, current.element)) {return index;}// 不是我们要找的节点,继续迭代下一个节点index++;current = current.next;}// 双向链表为空或者迭代到双向链表尾部,没有找到目标元素,返回 -1return -1}
代码分析:
从双向链表中查找某个节点的位置,需要对双向链表进行迭代。我们从第一个元素 head (索引 0) 开始,一直到 current 为 null为止,也就是迭代到双向链表的尾部。
在每次迭代时,我们都会验证 current 节点的元素和目标元素是否相等,如果相等,当前位置的元素就是我们要找的元素,返回该元素的位置。如果不是我们要找的元素,则继续迭代下一个节点。
如果双向链表为空或者迭代到了双向链表的尾部,都没有找到我们想要的元素,就返回 -1。
head 返回首节点
head() {return this.head;}
tail 返回尾结点
tail() {return this.tail;}
size 返回双向链表包含的元素个数
size 方法返回双向链表的节点个数,即返回双向链表的长度。DoublyLinkList 是我们自己构建的类,其长度是我们使用 count 变量来控制的,因此返回双向链表的长度,就是返回 count 变量。
size() {return this.count;}
isEmpty 判断双向链表是否为空
isEmpty 方法判断双向链表是否为空。如果双向链表中没有元素,isEmpty 就会返回 true,否则返回 false
isEmpty() {return this.size() === 0}
clear 清空双向链表
clear() {this.head = null;this.tail = null;this.count = 0;}
toString() 返回表示整个双向链表的字符串
toString 方法将 DoublyLinkList 对象转换成一个字符串,然后返回双向链表内容的字符串。
toString() {if (this.head == null) {return '';}let objString = `${this.head.element}`;let current = this.head.next;while(current != null) {objString = `${objString}, ${current.element}`;current = current.next;}return objString;}
代码分析:
首先,如果双向链表为空,我们就返回一个空字符串;
如果双向链表不为空,我们首先用双向链表第一个元素的值来初始化方法返回的字符串objString。然后迭双向代链表的所有其它元素,将元素值添加到字符串 objString 上;
当迭代完双向链表后,返回双向链表内容的字符串。
inverseToString() 返回双向链表从尾到头的字符串
inverseToString 方法将 DoublyLinkList 对象转换成一个字符串,然后返回双向链表从尾到头的字符串。
inverseToString() {if (this.tail != null) {return ''}let objString = `${this.tail.element}`;let previous = this.tail.prev;while(previous != null) {objString = `${objString}, ${previous.element}`;previous = previous.prev;}return}
代码分析:
首先,如果双向链表为空,我们就返回一个空字符串;
如果双向链表不为空,我们首先用双向链表的尾部元素的值来初始化方法返回的字符串objString。然后从尾部开始往前迭双向代链表的所有其它元素,将元素值添加到字符串 objString 上;
当迭代完双向链表后,返回双向链表内容的字符串。
完整代码
function defaultEquals(a, b) {return a === b;}class DoublyNode {constructor(element) {this.element = element;this.prev = null;this.next = null;}}class DoublyLinkList {constructor(equalsFn = defaultEquals) {this.equalsFn = equalsFn;this.count = 0;this.head = null;this.tail = null;}append(element) {const node = new DoublyNode(element);if (this.head == null) {this.head = node;this.tail = node;} else {this.tail.next = node;node.prev = this.tail;this.tail = node;}this.count++;}insert(element, index) {if (index >=0 && index <= this.count) {const node = new DoublyNode(element);let current = this.head;if (index === 0) {if (this.head == null) {this.head = node;this.tail = node;} else {node.next = this.head;this.head.prev = node;this.head = node;}} else if (index === this.count) {current = this.tail;current.next = node;node.prev = current;this.tail = node} else {const previous = this.getElementAt(index);current = previous.next;node.next = current;node.prev = previous;current.prev = node;previous.next = node;}this.count++;return true;}return false;}getElementAt(index) {if (index >= 0 && index <= this.count) {let node = this.head;for (let i = 0; i < index && node != null; i++) {node = node.next;}return node;}return undefined;}remove(element) {const index = this.indexOf(element);return this.removeAt(index);}removeAt(index) {if (index >= 0 && index < this.count) {let current = this.head;if (index === 0) {this.head = this.head.next;if (this.count === 1) {this.tail = null;} else {this.head.prev = null;}} else if (index === this.count - 1) {current = this.tail;this.tail = current.prev;this.tail.next = null;} else {current = this.getElementAt(index);const previous = current.prev;previous.next = current.next;current.next.prev = previous;}this.count--;return current.element;}return undefined;}removeHead() {if (this.head == null) {return undefined;}const current = this.head;this.head = this.head.next;if (this.count === 1) {this.tail = null;} else {this.head.prev = null;}return current.element;}removeTail() {if (this.head = null) {return undefined;}const current = this.tail;this.tail = current.prev;this.tail.next = null;return current.element;}indexOf(element) {let current = this.head;let index = 0;while(current != null) {if (this.equalsFn(element, current.element)) {return index;}index++;current = current.next;}return -1;}head() {return this.head;}tail() {return this.tail;}size() {return this.count;}isEmpty() {return this.size() === 0;}clear() {this.head = null;this.tail = null;this.count = 0;}toString() {if (this.head == null) {return '';}let objString = `${this.head.element}`;let current = this.head.next;while(current != null) {objString = `${objString}, ${current.element}`;current = current.next;}return objString;}inverseToString() {if (this.head == null) {return '';}let objString = `${this.tail.element}`;let previous = this.tail.prev;while(previous != null) {objString = `${objString}, ${previous.element}`;previous = previous.prev;}return objString;}}
双向链表的应用
基于双向链表实现栈
使用双向链表来实现栈,而不是链表,是因为相对于栈来说,我们会向链表尾部添加元素,也会从链表尾部移除元素,我们实现的双向链表有最后一个元素的引用,无需迭代就能获取它。双向链表可以直接获取尾部的元素,减少过程消耗,它的时间复杂度和原始的 Stack 实现相同,为 O(1)。
class Stack {constructor() {this.items = new DoublyList();}// 添加元素到栈push(element) {this.items.append(element);}// 从栈顶移除元素pop() {if (this.isEmpty()) {return undefined;}return this.items.removeAt(this.size() - 1);}// 查看栈顶元素peek() {if (this.isEmpty()) {return undefined'}return this.items.tail();}// 检查栈是否为空isEmtpy() {return this.items.isEmpty();}// 清空栈clear() {this.items.clear()}// 返回栈里的元素个数size() {return this.items.size()}// 打印栈print() {return this.items.inverseToString()}}
基于双向链表实现队列
class Queue {constructor() {this.items = new DoublyList();}// 入队列enqueue(element) {this.items.append(element);}// 出队列dequeue() {if (this.isEmpty()) {return undefined;}return this.items.removeHead();}// 返回队首head() {if (this.isEmpty()) {return undefined;}return this.items.head();}// 返回队尾tail() {if(this.isEmpty()) {return undefined;}return this.items.tail();}// 判断队列是否为空isEmpty() {return this.items.isEmpty();}// 清空队列clear() {this.items.clear();}// 返回队列长度size() {this.items.size()}// 打印队列print() {this.items.toString()}}
基于双向链表实现双端队列
class Deque {constructor() {this.items = new DoublyList()}// 在双端队列的前端添加元素addFront(element) {this.items.append(element);}// 在双端队列的后端添加元素addBack(element) {this.items.insert(element, this.size() - 1);}// 从双端队列的前端移除第一个元素removeFront() {if (this.isEmpty()) {return undefined;}return this.items.removeHead();}// 从双端队列的后端移除一个元素removeBack() {if (this.isEmpty()) {return undefined;}return this.items.removeTail();}// 查看双端队列的前端的第一个元素peekFront() {if (this.isEmpty()) {return undefined;}return this.items.head();}// 查看双端队列的后端的第一个元素peekBack() {if (this.isEmpty()) {return this.items.tail()}}// 检查双端队列是否为空isEmpty() {return this.items.isEmpty();}// 清空双端队列clear() {return this.items.clear();}// 返回双端队列的长度size() {return this.items.size();}// 输出双端队列从队列前端到队列后端的字符串toString() {return this.items.toString();}}
