双向链表
认识双向链表
-
双向链表(Doubly LinkedList)是一种特殊的链表数据结构
- 它的每个数据结点中都 有两个指针,分别指向直接后继和直接前驱节点
-
从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点
- 双向链表既可以从头遍历到尾,又可以从尾遍历到头
封装双向链表
| 属性/方法 | 描述 |
|---|---|
| append(value) | 向链表尾部添加一个节点 |
| prepend(value) | 向链表头部添加一个节点 |
| insert(position, value) | 向链表指定位置插入一个节点 |
| get(position) | 查看指定位置的节点 |
| update(position, value) | 修改指定位置节点值 |
| removeAt(position) | 删除指定位置的节点 |
| traverse() | 从头部向后遍历链表,返回遍历结果 |
| postTraverse() | 从尾部向前遍历链表,返回遍历结果 |
| isEmpty | 链表是否为空 |
| size | 链表内元素个数 |
初始化双向链表结构
-
由于双向链表增加了前驱节点指针,因此需要对 节点类 增加属性
prev/**
* @description 双向链表节点类
*/
class DoublyLinkedNode<T> {
/**
* @description 节点的值
*/
value: T;
/**
* @description 上一个节点的指针,初始为 null
*/
prev: DoublyLinkedNode<T> | null = null;
/**
* @description 下一个节点的指针,初始为 null
*/
next: DoublyLinkedNode<T> | null = null;
constructor(value: T) {
this.value = value;
}
} -
同时链表类初始化头尾指针时,也需要修改为对应的节点类型
/**
* @description 双向链表
*/
class DoublyLinkedList<T> {
/**
* @description 链表长度
*/
private length = 0;
/**
* @description 头指针
*/
private head: DoublyLinkedNode<T> | null = null;
/**
* @description 尾指针
*/
private tail: DoublyLinkedNode<T> | null = null;
}
插入方法 — insert
-
情况一:链表为空,直接让头尾指针都指向这个新增的节点
-
情况二:在头部插入节点
- 先让【插入节点】的 next 指向【头部节点】
- 再让【头部节点】的 prev 指向【插入节点】
- 最后让头指针指向这个【插入节点】,使其称为新的【头部节点】
-
情况三:在尾部插入节点
- 先根据尾指针找到【尾部节点】,让其 next 指向【插入节点】
- 再让【插入节点】的 prev 指向【尾部节点】
- 最后让尾指针重新指向这个【插入节点】
-
情况四:在中间位置插入

- 先查询插入位置的节点
- 再让插入位置【前一个节点】和【当前插入的节点】互为相邻节点
- 最后让【插入节点】和【当前插入位置节点】互为相邻节点
/**
* @description 双向链表
*/
class DoublyLinkedList<T> {
/**
* 向指定位置插入一个节点
* @param position 指定位置
* @param value 插入节点值
* @returns 是否插入成功
*/
insert(position: number, value: T): boolean {
// 位置越界,插入失败
if (position < 0 || position > this.length) return false;
// 创建新节点
const insertNode = new DoublyLinkedNode<T>(value);
if (this.length === 0) {
/**
* 情况一:链表为空,直接让头尾指针都指向【新增的节点】
*/
this.head = insertNode;
this.tail = insertNode;
} else {
if (position === 0) {
/**
* 情况二:在头部插入节点
*/
// 1.【插入节点】的 next 指向【头部节点】
insertNode.next = this.head;
// 2.【头部节点】的 prev 指向【插入节点】
this.head!.prev = insertNode;
// 3. 让头指针指向这个【插入节点】,使其称为新的【头部节点】
this.head = insertNode;
} else if (position === this.length) {
/**
* 情况三:在尾部 插入节点
*/
// 1.根据尾指针找到【尾部节点】,让其 next 指向【插入节点】
this.tail!.next = insertNode;
// 2.【插入节点】的 prev 指向【尾部节点】
insertNode.prev = this.tail;
// 3.尾指针重新指向这个【插入节点】
this.tail = insertNode;
} else {
/**
* 情况四:在中间位置插入
*/
// 查询插入位置的节点
const target = this.findNodeByPosition(position)!;
// 获取插入位置的前一个节点
const prev = target.prev!;
// 1.让插入位置【前一个节点】和【当前插入的节点】互为相邻节点
prev!.next = insertNode;
insertNode.prev = prev;
// 2.让【插入节点】和【当前插入位置节点】互为相邻节点
insertNode.next = target;
target.prev = insertNode;
}
}
// 插入完成,更新链表长度
this.length++;
return true;
}
}
删除方法 — removeAt
-
情况一:链表为空,直接终止删除操作
-
情况二:非空链表中只有一个节点,直接让头尾指针都指向 null 即可
-
情况三:在非空链表头部删除节点