Skip to content

单链表

LinkedListNode

  • linked-list/LinkedListNode.js
export default class LinkedListNode {
  constructor(value, next = null) {
    this.value = value;
    this.next = next;
  }

  toString(callback) {
    return callback ? callback(this.value) : `${this.value}`;
  }
}

LinkedList

  • linked-list/LinkedList.js
import LinkedListNode from './LinkedListNode.js';
import Comparator from '../utils/Comparator.js';

export default class LinkedList {
  constructor(comparatorFunction) {
    /** @var LinkedListNode */
    this.head = null;

    /** @var LinkedListNode */
    this.tail = null;

    this.compare = new Comparator(comparatorFunction);
  }

  append(value) {
    const newNode = new LinkedListNode(value);

    if (!this.head) {
      this.head = newNode;
      this.tail = newNode;

      return this
    }

    this.tail.next = newNode;
    this.tail = newNode;

    return this;
  }

  prepend(value) {
    const newNode = new LinkedListNode(value);
    newNode.next = this.head;
    this.head = newNode;

    if (!this.head) {
      this.tail = newNode;
    }

    return this;
  }

  delete(value) {
    if (!this.head) {
      return null;
    }

    let deletedNode = null;

    // If next node must be deleted then make next node to be a next next one.
    while(this.head && this.compare.equal(this.head.value, value)) {
      deletedNode = this.head;
      this.head = this.head.next;
    }

    let currentNode = this.head;
    // If the head must be deleted then make next node that is different
    // from the head to be a new head.
    while(currentNode && currentNode.next) {
      if (this.compare.equal(currentNode.next.value, value)) {
        deletedNode = currentNode.next;
        currentNode.next = currentNode.next.next;
      } else {
        currentNode = currentNode.next;
      }
    }

    // Check if tail must be deleted.
    if (this.compare.equal(this.tail.value, value)) {
      this.tail = currentNode;
    }

    return deletedNode;
  }

  deleteHead() {
    if (!this.head) {
      return null;
    }

    const deletedHead = this.head;

    if (this.head.next) {
      this.head = this.head.next;
    } else {
      this.head = null;
      this.tail = null;
    }

    return deletedHead;
  }

  deleteTail() {
    if (!this.tail) {
      return null;
    }

    const deletedTail = this.tail;

    if (deletedTail === this.head) {
      this.head = null;
      this.tail = null;
      return deletedTail;
    }

    let curr = this.head;
    while(curr) {
      if (curr.next === this.tail) {
        break;
      }
      curr = curr.next;
    }
    this.tail = curr;
    this.tail.next = null;

    return deletedTail;
  }

  find({ value = undefined, callback = undefined }) {
    if (!this.head) {
      return null;
    }

    let currentNode = this.head;

    while(currentNode) {
      if (callback && callback(currentNode.value)) {
        return currentNode;
      }

      if (value !== undefined && this.compare.equal(currentNode.value, value)) {
        return currentNode;
      }

      currentNode = currentNode.next;
    }

    return null;
  }

  reverse() {
    let current = this.head;
    let prev = null;
    let next = null;

    while (current) {
      // Store next node.
      next = current.next;

      // Change next node of the current node so it would link to previous node.
      current.next = prev;

      // Move prevNode and currNode nodes one step forward.
      prev = current;
      current = next;
    }

    this.tail = this.head;
    this.head = prev;
  }

  fromArray(values) {
    values.forEach((value) => this.append(value));

    return this;
  }

  toArray() {
    const nodes = [];

    let currentNode = this.head;
    while (currentNode) {
      nodes.push(currentNode);
      currentNode = currentNode.next;
    }

    return nodes;
  }

  toString(callback) {
    return this.toArray().map(node => node.toString(callback));
  }
}