Skip to content

双向链表

封装思想

implements

默认能打印树结构是因为它有对应属性 leftrightvalue,但是如果别人定义的和对应属性不符合则无法打印

image-20231218145034669

可以使用 implements 实现对应接口,之后通过 get 方法控制其展示内容

typescript
class Node<T> {
  data: T
  constructor(value: T) {
    this.data = value
  }
}
interface PrintableNode {
  left: PrintableNode | null
  right: PrintableNode | null
  value: any
}
class TreeNode<T> extends Node<T> implements PrintableNode {
  left: TreeNode<T> | null = null
  right: TreeNode<T> | null = null
  get value() {
    const data = this.data as Product
    return `${data.name}-${data.price}`
  }
}
class BSTree<T> {
  private root: TreeNode<T> | null = null
  print() {
    btPrint(this.root)
  }
}
class Product {
  constructor(public name: string, public price: number) {}
  valueOf() {
    return this.price
  }
}
const bst = new BSTree<Product>()

大小比较

JS 取巧的方式

typescript
class Product {
  constructor(public name: string, public price: number) {}
  valueOf() {
    return this.price
  }
}
const bst = new BSTree<Product>()

Java 里一般会采取如下方式进行比较

typescript
interface IComparator<T> {
  (c1: T, c2: T): number
}
class BSTree<T> {
  private comparator: IComparator<T>
  constructor(comparator: IComparator<T>) {
    this.comparator = comparator
  }
  private searchNode(value: T): TreeNode<T> | null {
    let current = this.root
    let parent: TreeNode<T> | null = null
    while (current) {
      if (current.data === value) return current
      parent = current
      // 使用比较器进行比较
      if (this.comparator(current.data, value)) {
        current = current.right
      } else {
        current = current.left
      }
      if (current) current.parent = parent
    }
    return null
  }
}
class Product {
  constructor(public name: string, public price: number) {}
}
const bst = new BSTree<Product>((c1, c2) => {
  return c1.price - c2.price
})

认识循环链表

循环链表(Circular LinkedList)是一种特殊的链表数据结构:

  • 在普通链表的基础上,最后一个节点的下一个节点不再是 null,而是指向链表的第一个节点
  • 这样形成了一个环,使得链表能够被无限遍历
  • 这样,我们就可以在单向循环链表中从任意一个节点出发,不断地遍历下一个节点,直到回到起点

重构单向链表

为什么要重构?是为了让循环链表继承自 LinkedList

  • 增加 tail 属性
typescript
class LinkedList<T> implements ILinkedList<T> {
  protected tail: Node<T> | null = null
  private isTail(node: Node<T>) {
    return this.tail === node
  }
  append(value: T) {
    if (this.head) {
      this.tail!.next = newNode
    }
    this.tail = newNode
  }
  insert(value: T, position: number) {
    if (position === this.length) {
      this.tail = newNode
    }
  }
  removeAt(position: number) {
    if (position === 0) {
      if (this.length === 1) {
        this.tail = null
      }
    } else {
      if (position === this.length - 1) {
        this.tail = previous
      }
    }
  }
  traverse() {
    while (current) {
      if (this.isTail(current)) {
        current = null
      } else {
        current = current.next
      }
    }
    if (this.head && this.tail?.next === this.head) {
      values.push(this.head.value)
    }
  }
  indexOf(value: T) {
    while (current) {
      if (this.isTail(current)) {
        current = null
      } else {
        current = current.next
      }
    }
  }
}

循环链表实现

有些方法需要在单向链表基础上重新实现一下

typescript
class CircularLinkedList<T> extends LinkedList<T> {
  append(value: T): void {
    super.append(value)
    this.tail!.next = this.head
  }
  insert(value: T, position: number): boolean {
    const isSuccess = super.insert(value, position)
    if (isSuccess && (position === this.length - 1 || position === 0)) {
      this.tail!.next = this.head
    }
    return isSuccess
  }
  removeAt(position: number): NonNullable<T> | null {
    const value = super.removeAt(position)
    if (value && this.tail && (position === 0 || position === this.length)) {
      this.tail!.next = this.head
    }
    return value
  }
}

认识双向链表

双向链表:

  • 既可以从头遍历到尾,又可以从尾遍历到头
  • 也就是链表相连的过程是双向的
  • 一个节点既有向前连接的引用 previous, 也有一个向后连接的引用 next

双向链表有什么缺点呢?

  • 每次在插入或删除某个节点时,需要处理四个引用,而不是两个.也就是实现起来要困难一些
  • 并且相当于单向链表,必然占用内存空间更大一些
  • 但是这些缺点和我们使用起来的方便程度相比,是微不足道的

image-20231219110119310

节点封装

双向链表的节点,需要进一步添加一个 prev 属性,用于指向前一个节点

typescript
class Node<T> {
  value: T
  next: Node<T> | null = null
  constructor(value: T) {
    this.value = value
  }
}
export class DoublyNode<T> extends Node<T> {
  prev: DoublyNode<T> | null = null
  next: DoublyNode<T> | null = null
}

双向链表实现

双向链表中添加、删除方法的实现和单向链表有较大的区别,所以我们可以对其方法进行重新实现

  • append:在尾部追加元素
  • prepend:在头部添加元素
  • postTraverse:从尾部遍历所有节点
  • insert:根据索引插入元素
  • removeAt:根据索引删除元素
typescript
class DoublyLinkedList<T> extends LinkedList<T> {
  protected head: DoublyNode<T> | null = null
  protected tail: DoublyNode<T> | null = null
  append(value: T): void {
    const newNode = new DoublyNode(value)
    if (!this.head) {
      this.head = newNode
      this.tail = newNode
    } else {
      this.tail!.next = newNode
      // 不能将一个父类的对象,赋值给一个子类的类型
      // 可以将一个子类的对象,赋值给一个父类的类型(多态)
      newNode.prev = this.tail
      this.tail = newNode
    }
    this.length++
  }
  prepend(value: T): void {
    const newNode = new DoublyNode(value)
    if (!this.head) {
      this.head = newNode
      this.tail = newNode
    } else {
      newNode.next = this.head
      this.head.prev = newNode
      this.head = newNode
    }
    this.length++
  }
  postTraverse() {
    const values: T[] = []
    let current = this.tail
    while (current) {
      values.push(current.value)
      current = current.prev
    }
    console.log(values.join('->'))
  }
  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 = newNode
      newNode.next = current
      newNode.prev = current.prev
      current.prev = newNode
      this.length++
    }
    return true
  }
  removeAt(position: number): NonNullable<T> | null {
    if (position < 0 && position >= this.length) return null
    let current = 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
      }
    } else if (position === this.length - 1) {
      current = this.tail
      this.tail = this.tail!.prev
      this.tail!.next = null
    } else {
      current = this.getNode(position) as DoublyNode<T>
      current.next!.prev = current.prev
      current.prev!.next = current.next
    }
    this.length--
    return current?.value ?? null
  }
}

常备不懈,才能有备无患