JavaScript手写数据结构(链表,BST,堆)

3,513 阅读6分钟

以下代码全部原创手写。

一、链表

非递归实现:

class Node {
  constructor(value, next) {
    this.value = value;
    this.next = next;
  }
}

class LinkedList{
  constructor() {
    this.dummyHead = new Node(null, null);
    this.size = 0;
  }

  //在指定索引处插入一个Node类型的节点
  add(index, newNode) {
    if(index > this.size || index < 0)  
      throw new Error('索引非法');
    let pre = this.dummyHead;
    for(let i = 0; i < index; i++) {
      pre = pre.next;
    }
    newNode.next = pre.next;
    pre.next = newNode;
    this.size++;
  }

  //在链表头增加节点
  addFirst(newNode) {
    this.add(0, newNode);
  }

  //在链表尾增加节点
  addLast(newNode) {
    this.add(size, newNode);
  }

  //删除一个索引为index的节点
  remove(index) {
    if(index >= this.size || index < 0)  
      throw new Error('索引非法');
    let pre = this.dummyHead;
    for (let i = 0; i < index; i++) {
      pre = pre.next;
    }
    let delNode = pre.next;
    pre.next = delNode.next;
    delNode.next = null;
    this.size--;
  }

  removeFirst() {
    this.remove(0);
  }

  removeLast() {
    this.remove(this.size - 1);
  }

  //删除值为value的节点
  removeElement(value) {
    let pre = this.dummyHead;
    //找到待删除节点的前一个节点
    while (pre.next != null) {
      if (pre.next.value == value) 
        break;
      pre = pre.next;
    }
    if (pre.next != null) {
      let cur = pre.next;
      pre.next = cur.next;
      cur.next = null;
      this.size--;
      return cur;
    }
    return null;
  }

  contains(value) {
    let cur = this.dummyHead.next;
    while(cur != null) {
      if(cur.value === value) {
        return true;
      }
      cur = cur.next;
    }
    return false; 
  }

  getSize() {
    return this.size;
  }

  isEmpty() {
    return this.size == 0;
  }

  toString() {
    let cur = this.dummyHead.next;
    let str = "";
    while (cur != null){
      str += cur.value + "->";
      cur = cur.next;
    }
    str += "null";
    return str;
  }
}

function test() {
  let n1 = new Node(1, null);
  let n2 = new Node(4, null);
  let n3 = new Node(3, null);
  let n4 = new Node(2, null);

  let list = new LinkedList();
  list.addFirst(n1);
  list.addFirst(n2);
  list.addFirst(n3);
  list.addFirst(n4);

  console.log("链表初始化:",  list.toString());
  
  list.remove(3);
  console.log("删除第3个元素后为:",  list.toString());


  list.removeElement(3);
  console.log("删除值为3的元素后为:", list.toString());

  console.log("链表元素个数:", list.getSize());
}

test();

测试结果:

下面是递归方法的实现,会有一些绕,但是并不是太难理解,希望大家耐心看下来:

class Node {
  constructor(value, next) {
    this.value = value;
    this.next = next;
  }
}

class LinkedList{
  constructor() {
    this.dummyHead = new Node(null, null);
    this.size = 0;
  }

  //在指定索引处插入一个Node类型的节点,递归写法
  add(index, newNode) {
    if(index > this.size || index < 0)  
      throw new Error('索引非法');
    let pre = this.dummyHead;
    pre.next = this._add(index, pre.next, newNode, 0);
    this.size++;
  }

  _add(index, cur, newNode, depth) {
    //递归深度达到索引值时,即当前的cur指向第index个元素时,我们做一些处理
    //让函数返回插入的节点,即让第index-1个元素的next指向了要插入的节点,以下递归写法均为同理
    if (index == depth) {
      newNode.next = cur;
      return newNode;
    }
    cur.next = this._add(index, cur.next, newNode, depth + 1);
    return cur;
  }

  //在链表头增加节点
  addFirst(newNode) {
    this.add(0, newNode);
  }

  //在链表尾增加节点
  addLast(newNode) {
    this.add(size, newNode);
  }

  //删除一个索引为index的节点,递归写法
  remove(index) {
    if(index >= this.size || index < 0)  
      throw new Error('索引非法');
    let pre = this.dummyHead;
    pre.next = this._remove(index, pre.next, 0);
    this.size--;
  }

  _remove(index, cur, depth) {
    if (index === depth) {
      let nextNode = cur.next;
      cur.next = null;
      return nextNode;
    }
    cur.next = this._remove(index, cur.next, depth + 1);
    return cur;
  }

  removeFirst() {
    this.remove(0);
  }

  removeLast() {
    this.remove(this.size - 1);
  }

  //删除值为value的节点, 递归写法
  removeElement(value) {
    let pre = this.dummyHead;
    pre.next = this._removeElement(pre.next, value);   
  }

  _removeElement(cur, value) {
    if(cur.value === value) {
      let nextNode = cur.next;
      cur.next = null;
      this.size--;
      return nextNode;
    }
    cur.next = this._removeElement(cur.next, value);
    return cur;
  }

  contains(value) {
    let cur = this.dummyHead.next;
    while(cur != null) {
      if(cur.value === value) {
        return true;
      }
      cur = cur.next;
    }
    return false; 
  }

  getSize() {
    return this.size;
  }

  isEmpty() {
    return this.size == 0;
  }

  toString() {
    let cur = this.dummyHead.next;
    let str = "";
    while (cur != null){
      str += cur.value + "->";
      cur = cur.next;
    }
    str += "null";
    return str;
  }
}

function test() {
  let n1 = new Node(1, null);
  let n2 = new Node(4, null);
  let n3 = new Node(3, null);
  let n4 = new Node(2, null);

  let list = new LinkedList();
  list.addFirst(n1);
  list.addFirst(n2);
  list.addFirst(n3);
  list.addFirst(n4);

  console.log("链表初始化:",  list.toString());
  
  list.remove(3);
  console.log("删除第3个元素后为:",  list.toString());


  list.removeElement(3);
  console.log("删除值为3的元素后为:", list.toString());

  console.log("链表元素个数:", list.getSize());
}

test();

测试结果:

可以看到,递归方法的结果和非递归是完全一样的,说明递归的实现没有问题。

二、二分搜索树(Binary Search Tree)

在二分搜索树中,任何的节点的值大于左孩子节点,而且小于右孩子节点。

现在来实现其中所有的方法,其中三种顺序的遍历都有递归和非递归的方式:

class Node{
  constructor(value, left, right) {
    this.value = value;
    this.left = left || null;
    this.right = right || null;
    this.isVisited = false;
  }
}

class BST{
  constructor() {
    this.root = null;
    this.size = 0;
  }

  getSize() {
    return this.size;
  }

  isEmpty() {
    return this.size === 0;
  }

  add(value) {
    this.root = this._add(this.root, value);
  }

  _add(node, value) {
    if(node == null) {
      let newNode = new Node(value);
      this.size++;
      return newNode;
    }
    if(value < node.value)
      node.left = this._add(node.left, value);
    if(value > node.value)
      node.right = this._add(node.right, value);
    //等于的情况不做处理
    return node;
  }

  contains(value) {
    return this._contains(this.root, value);
  }

  _contains(node, value) {
    if(node == null) 
      return false;
    if(value < node.value)
      return this._contains(node.left, value);
    else if(value > node.value)
      return this._contains(node.right, value);
    else if(value === node.value)
      return true;
  }

  //先序遍历 递归方式
  preOrder() {
    this._preOrder(this.root);
  }

  _preOrder(node) {
    if(node == null)
      return;
    console.log(node.value);
    this._preOrder(node.left);
    this._preOrder(node.right);
  }

  //先序遍历 非递归方式
  preOrderNR() {
    //这里需要用到栈,js数组的push和pop就能满足
    let stack = [];
    let p = this.root;
    while(!stack.length || p != null) {
      while(p != null) {
        stack.push(p);
        console.log(p.value);
        p = p.left;
      }
      let q = stack.pop();
      p = q.right;
    }
  }

  //中序遍历 递归方式
  inOrder() {
    this._inOrder(this.root);
  }

  _inOrder(node) {
    if(node == null)
      return;
    this._inOrder(node.left);
    console.log(node.value);
    this._inOrder(node.right);
  }
  
  //中序遍历 非递归方式
  inOrderNR() {
    //这里同样需要用到栈,js数组的push和pop就能满足
    let stack = [];
    let p = this.root;
    while(stack.length  || p != null) {
      while(p != null) {
        stack.push(p);
        p = p.left;
      }
      let q = stack.pop();
      console.log(q.value);
      p = q.right;
    }
  }

  //后序遍历 递归方式
  postOrder() {
    this._postOrder(this.root);
  }
  _postOrder(node) {
    if(node == null)
      return;
    this._inOrder(node.left);
    this._inOrder(node.right);
    console.log(node.value);
  }

  //后序遍历 非递归方式
  postOrderNR() {
    //这里同样也需要用到栈,js数组的push和pop就能满足
    let stack = [];
    let p = this.root;
    while(stack.length  || p != null) {
      while(p != null) {
        stack.push(p);
        p = p.left;
      }
      //拿到栈顶元素
      let peek = stack[stack.length - 1];
      if(peek.right != null && !peek.right.isVisited) 
        p = peek.right;
      else{
        let q = stack.pop();
        console.log(q.value);
        q.isVisited = true;
      }
    }
  }

  //层序遍历
  levelOrder() {
    //需要用到队列,用js数组的shift和push就能满足
    let queue = [];
    queue.push(this.root);
    while(queue.length) {
      let cur = queue.shift();
      console.log(cur.value);
      if(cur.left != null)
        queue.push(cur.left);
      if(cur.right != null)
        queue.push(cur.right);
    }
  }

  //找到BST节点中的最小值
  minimum() {
    return this._minimum(this.root).value;
  }

  _minimum(node) {
    if(node.left == null) {
      return node;
    } 
    return this._minimum(node.left);
  }

  maximum() {
    return this._maximum(this.root).value;
  }

  _maximum(node) {
    if(node.right == null) {
      return node;
    }
    return this._maximum(node.right);
  }

  removeMin() {
    let ret = this.minimum(this.root);
    this.root = this._removeMin(this.root);
    return ret;
  }

  _removeMin(node) {
    if(node.left == null) {
      let right = node.right;
      node.right = null;
      this.size--;
      return right;
    }
    node.left = this._removeMin(node.left);
    return node;
  }

  removeMax() {
    let ret = this.maximum(this.root);
    this.root = this._removeMax(this.root);
    return ret;
  }

  _removeMax(node) {
    if(node.right == null) {
      let left = node.left;
      node.left = null;
      this.size--;
      return left;
    }
    node.right = this._removeMax(node.right);
    return node;
  }

  remove(value) {
    this.root = this._remove(this.root, value);
    return value;
  }

  _remove(node, value) {
    if(value <node.value) {
      node.left = this._remove(node.left);
      return node;
    } else if(value > node.value){
      node.right = this._remove(node.right);
      return node;
    } else {
      //要开始删除了
      if(node.left == null) {
        let right = node.right;
        node.right = null;
        this.size--;
        return right;
      } 
      if(node.right == null) {
        let left = node.left;
        node.left = null;
        this.size--;
        return left;
      }
      //最小的后继节点
      let successor = this._minimum(node.right);
      successor.right = this._removeMin(node.right);
      successor.left = node.left;
      node.left = node.right = null;
      this.size--;
      return successor;
    }
  }

  toString() {
    this._generateBST(this.root, 0);
  }
  //先序遍历打印树的结构
  _generateBST(node, depth){
    if(node == null){
      console.log(this._generateDepthString(depth), "null");
      return;
    }
    console.log(this._generateDepthString(depth), node.value);
    this._generateBST(node.left, depth+1);
    this._generateBST(node.right, depth+1);
  }
  _generateDepthString(depth) {
    let str = "";
    for(let i = 0; i < depth; i++) {
      str += "--"
    }
    return str;
  }
} 

function test() {
  let bst = new BST();
  bst.add(2);

  bst.add(6);
  bst.add(5);
  bst.add(3);
  bst.add(7);

  // console.log("先序遍历");
  // bst.preOrder();
  // bst.preOrderNR();

  // console.log("中序遍历");
  // bst.inOrder();
  // bst.inOrderNR();

  // console.log("后序遍历");
  // bst.postOrder();
  // bst.postOrderNR();

  // console.log("层序遍历");
  // bst.levelOrder();

  // console.log("最大值", bst.maximum());
  // console.log("最小值", bst.minimum());

  // console.log(bst.contains(2));//true

  // console.log(bst.removeMin());

  // console.log(bst.removeMax());

  console.log(bst.remove(6));
  bst.toString();
}
test();

3、基于堆实现优先队列

堆的结构实现如下:

class MaxHeap{
  constructor(arr = [], compare = null) {
    this.data = arr;
    this.size = arr.length;
    this.compare = compare || function(a, b){return a - b > 0};
  }
  getSize() {
    return this.size;
  }
  isEmpty() {
    return this.size === 0;
  }
  _swap(i, j) {
    [this.data[i], this.data[j]] = [this.data[j], this.data[i]];
  }
  _parent(index) {
    return Math.floor((index - 1) / 2); 
  }
  _leftChild(index) {
    return 2 * index + 1;
  }
  _rightChild(index) {
    return 2 * index + 2;
  }
  _siftUp(k) {
    while(k > 0 && this.data[k] > this.data[this._parent(k)]){
      this._swap(k, this._parent(k));
      k = this._parent(k);
    }
  }
  _siftDown(k) {
    while(this._leftChild(k) < this.size) {
      let j = this._leftChild(k);
      debugger;
      if(this._rightChild(k) < this.size && 
        this.compare(this.data[this._rightChild(k)], this.data[j])){
          j++;
      }
      if(this.data[k] >= this.data[j])
        return;
      this._swap(k, j);
      k = j;
    }
  }
  //增加元素
  add(value) {
    this.data.push(value);
    this.size++;
    this._siftUp(this.getSize() - 1);
  }

  findMax() {
    if(this.getSize() === 0)
      return;
    return this.data[0]; 
  }

  extractMax() {
    let ret = this.findMax();
    this._swap(0, this.getSize() - 1);
    this.data.pop();
    this.size--;
    this._siftDown(0);
    return ret;
  }

  toString() {
    console.log(this.data);
  }
}
module.exports = MaxHeap;

所谓优先队列,就是每次出队的时候,总是出队列中权值最高的元素。

现在可以用堆来轻易地实现:

const MaxHeap = require('./maxHeap');

class PriorityQueue {
  constructor() {
    this.maxHeap = new MaxHeap();
  }

  getSize() {
    return this.maxHeap.getSize();
  }

  isEmpty() {
    return this.maxHeap.isEmpty();
  }

  getFront() {
    return this.maxHeap.findMax();
  }

  enqueue(e) {
    return this.maxHeap.add(e);
  }

  dequeue() {
    return this.maxHeap.extractMax();
  }
}

let pq = new PriorityQueue();
pq.enqueue(1);
pq.enqueue(3);
pq.enqueue(6);
pq.enqueue(2);
pq.enqueue(62);
console.log(pq.dequeue());