循环链表
介绍
在单选链表基础上,下一个节点指向前一个节点,最后一个节点指向起点
封装循环链表
为了让循环链表可以继承自单向链表,对其进行重构
给其增加一个tail属性(尾节点),对各方法进行重写整理
import ILinkedList from "./ILinkedList";
// 1.创建Node节点类
class Node<T> {value:T;next:Node<T> | null = null;constructor(value:T) {this.value = value}
}// 2.创建LinkedList类
class LinkedList<T> implements ILinkedList<T> {protected head:Node<T> | null = null;protected size:number = 0;// 新增属性:尾结点protected tail:Node<T> | null = null;get length() {return this.size}// 封装私有方法// 根据position获取当前的节点protected getNode(position:number): Node<T> | null{let current = this.headlet index = 0while(index++ < position && current) {current = current.next}return current}// 判断是否为最后一个节点private isTail(node:Node<T>): boolean {return node === this.tail}// 1.追加节点append(value:T) {// 1.根据value创建一个新节点const newNode = new Node(value)// 2.判断this.head是否为nulif(this.head === null) {this.head = newNode}else {this.tail!.next = newNode}this.tail = newNodethis.size++}// 2.遍历链表traverse() {const values:T[] = []let current = this.headwhile(current) {values.push(current.value)if(this.isTail(current)){//已经遍历到最后一个节点current = null}else{current = current.next}}if(this.head && this.tail?.next === this.head) {values.push(this.head.value)}console.log(values.join('->'));//aaa->bbb->ccc->ddd}// 3.插入方法insert(value: T, position: number): boolean {// 1. 边界条件判断if (position < 0 || position > this.size) return false;// 2. 根据 value 创建新的节点const newNode = new Node(value);// 3. 判断是否需要插入头部if (position === 0) {newNode.next = this.head;this.head = newNode;} else {// 4.找到插入位置的前一个节点/* let current = this.head;let previous: Node<T> | null = null;let index = 0;while(current && index < position ){previous = currentcurrent = current.next;index++}previous!.next = newNode;newNode.next = current; */// 重构代码const previous = this.getNode(position - 1);newNode.next = previous?.next ?? null;previous!.next = newNode;if(position === this.length) {this.tail = newNode;}}// 6. 更新链表大小this.size++;return true;}// 4.删除方法removeAt(position:number): T | null {// 1.越界判断if(position < 0 || position >= this.size) return null;// 2.判断是否删除第一个节点let current = this.head;if(position === 0) {this.head = current?.next ?? null;if(this.length === 1){this.tail = null;}}else{/* let previous: Node<T> | null = null;let index = 0;while(index++ < position && current) {previous = current;current = current.next}// 找到需要的节点previous!.next = current?.next ?? null; */// 重构为如下代码const previous = this.getNode(position - 1)previous!.next = previous?.next?.next ?? null;current = previous!.next;if(position === this.length - 1) {this.tail = previous;}}this.size--;return current?.value ?? null;}// 5.获取方法get(position:number): T | null {// 5.1越界命题if(position < 0 || position >= this.size) return null;// 5.2查找元素return this.getNode(position)?.value ?? null;}// 7.更新方法update(value:T,position:number): boolean {if(position < 0 || position >= this.size) return false;const currentNode = this.getNode(position)currentNode!.value = value;return true;}// 8.根据值获取对应位置的索引indexOf(value:T): number {// 从第一个节点开始,向后遍历let current = this.head;let index = 0;while(current) {if(current.value === value) {return index}if(this.isTail(current)){current = null}else{index++current = current.next}}return -1;}// 9.根据value删除节点remove(value:T): T | null {const index = this.indexOf(value)return this.removeAt(index)}// 10.判断链表是否为空isEmpty(): boolean {return this.size === 0}
}const linkedList = new LinkedList<string>()export default LinkedList
封装CircularLinkedList
import LinkedList from "./01-实现单选LinkedList"class CircularLinkedList<T> extends LinkedList<T> {// 重新实现的方法: append方法append(value:T) {super.append(value)this.tail!.next = this.head}// traverse方法// insert方法insert(value: T, position: number): boolean {const isSuccess = super.insert(value, position)if(isSuccess && (position === this.length || position === 0)){this.tail!.next = this.head}return isSuccess}// removeAt方法removeAt(position: number): T | null {const value = super.removeAt(position)if(value && this.tail &&(position === 0 || position === this.length-1)){this.tail.next = this.head}return value}// indexOf方法
}const cLinkedList = new CircularLinkedList<string>()
cLinkedList.append('aaa')
cLinkedList.append('bbb')
cLinkedList.append('ccc')
cLinkedList.append('ddd')
cLinkedList.insert('eee', 0)
cLinkedList.insert('fff', 5)cLinkedList.removeAt(5)
cLinkedList.traverse()console.log('--------测试get----------');
console.log(cLinkedList.get(0));
console.log(cLinkedList.get(1));
console.log(cLinkedList.get(4));console.log('--------测试update----------');
cLinkedList.update('hhh', 0)
cLinkedList.update('iii', 2)
cLinkedList.traverse()
双向链表
介绍
既可以从头遍历到尾,也可以从尾遍历到头。
封装双向链表
import LinkedList from "./01-实现单选LinkedList"; class DoublyNode<T> extends LinkedList<T> {
}
2.append方法
情况一:当添加的是第一个节点时→让head和tail都指向新节点
情况二:当添加的是其它节点时,需改变相关节点的指引关系
代码实现:
protected head: DoublyNode<T> | null = null;protected tail: DoublyNode<T> | null = null;
// append方法append(value: T): void {const newNode = new DoublyNode(value)// 情况一:链表为空if(!this.head) {this.head = newNodethis.tail = newNode}else{// 情况二:链表不为空this.tail!.next = newNode// 不能将父类的类型赋值给子类;可以将子类的类型赋值给父类newNode.prev = this.tailthis.tail = newNode}this.size++}
测试代码:
dLinkedList.append("aaa")
dLinkedList.append("bbb")
dLinkedList.append("ccc")
dLinkedList.append("ddd")
dLinkedList.traverse()
3.prepend方法-从头部插入
情况一:链表本身为空
情况二:链表有值
prepend(value:T):void {const newNode = new DoublyNode(value)if(!this.head) {this.head = newNodethis.tail = newNode}else{newNode.next = this.headthis.head.prev = newNodethis.head = newNode}this.size++}
4.反向遍历-从尾到头
postTraverse() {const values:T[] = []let current = this.tailwhile(current) {values.push(current.value)current = current.prev}console.log(values.join('->'));//ddd->ccc->bbb->aaa->zzz
}
5.insert插入节点
情况一:插入位置为0时
情况二:插入位置为末尾时
情况三:链表中间位置时
insert(value: T, position: number): boolean {if(position < 0 || position > this.length) return false;if(position === 0) {this.prepend(value)}else if(position === this.length){this.append(value)}else{const newNode = new DoublyNode(value)const current = this.getNode(position)! as DoublyNode<T>current!.prev!.next = newNodenewNode.next = currentnewNode.prev = current!.prevcurrent!.prev = newNodethis.size++}return true}
6.removeAt(position)
情况一:当删除0位置时:
1.当链表长度为1时:
2.链表长度不为1时:
情况二:删除末尾位置:
情况三:删除中间位置:
// 索引删除removeAt(position: number): T | null {if(position < 0 || position >= this.length) return nulllet current = this.headif(position === 0) {if(this.length === 1){this.head = nullthis.tail = null}else {this.head = this.head!.nextthis.head!.prev = null}}else if(position === this.length - 1){current = this.tailthis.tail= this.tail!.prevthis.tail!.next = null}else {current = this.getNode(position)! as DoublyNode<T>current.prev!.next = current.nextcurrent.next!.prev = current.prev}this.size--;return current?.value?? null;}