JavaScript 中的数据结构

Run

每个js文件需要命名为xxx.mjs

node --experimental-modules ./demo.mjs

栈是一种遵从先进后出 (LIFO) 原则的有序集合;类似于一叠书,想要拿到最下面的一本,必须移开上面的全部。

class Stack{
  constructor () {
    this.stacks = [];
  }
  push (element) {
    this.stacks.push(element);
  }
  pop () {
    return this.stacks.pop();
  }
  get peek () {
    return this.stacks[this.stacks.length -1];
  }
  get isEmpty () {
    return !this.stacks.length
  }
  get size () {
    return this.stacks.length
  }
  clear () {
    this.stacks = [];
  }
  print () {
    return this.stacks.toString()
  }
};

const stack = new Stack();
stack.push(1);
console.log(stack.isEmpty);
console.log(stack.print());

队列

与栈相反,队列是一种遵循先进先出 (FIFO / First In First Out) 原则的一组有序的项;队列在尾部添加元素,在头部移除元素,新添加的元素必须在队列的尾部。

  • 普通队列
    用javascript实现一个简单队列
class Queen {
  constructor (queens) {
    this.queens = queens || [];
  }
  enquene (element) {
    this.queens.push(element);
  }
  dequeue () {
    return this.queens.shift();
  }
  front () {
    return this.queens[0]
  }
  clear () {
    this.queens = [];
  }
  get size () {
    return this.queens.length;
  }
  get isEmpty () {
    return !this.queens.length;
  }
  print () {
    return this.queens.toString();
  }
};

const quene = new Queen();
quene.enquene(1);
console.log(quene.size);
console.log(quene.isEmpty);
console.log(quene.print());

export default Queen;
  • 优先队列
    用javascript实现一个具有优先性质的队列
import Queen from './queen.mjs';

class PriorityQueue extends Queen {
  constructor () {
    super();
  }
  enquene (element, priority) {
    const queueElement = { element, priority };
    if (this.isEmpty) {
      this.queens.push(queueElement);
    } else {
      const preIndex = this.queens.findIndex((item) => {
        queueElement.priority < item.priority;
      });
      if (preIndex === -1) {
        this.queens.splice(preIndex, 0, queueElement);
      } else {
        this.queens.push(queueElement);
      }
    }
  }
};

const priorityQueue = new PriorityQueue();
priorityQueue.enquene('name', 1);
priorityQueue.enquene('age', 2);

console.log(priorityQueue.front());
  • 循环队列
import Queen from './queen.mjs';

class LoopQueen extends Queen {
  constructor (queens) {
    super(queens);
  }
  getIndex (index) {
    const length = this.queens.length;
    return index >= length ? (index % length) : index;
  }
  find (index) {
    return !this.isEmpty ? this.queens[this.getIndex(index)] : null;
  }
};

const loopQueen = new LoopQueen();
loopQueen.enquene(5);
loopQueen.enquene(6);
loopQueen.enquene(7);
console.log(loopQueen.find(3));
console.log(loopQueen.size);
链表

链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个 元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。

  • 单向链表
class Node {
  constructor (element) {
    this.element = element;
    this.next = null;
  }
};
class SingleList {
  constructor () {
    this.head = null;
    this.length = 0;
  }
  append (element) {
    const node = new Node(element);
    let current = null;
    if (this.head === null) {
      this.head = node;
    } else {
      current = this.head;
      while (current.next) {
        current = current.next;
      }
      current.next = node;
    }
    this.length ++;
  }
  insert (position, element) {
    if (position >= 0 && position <= this.length -1) {
      const node = new Node(element);
      let current = this.head;
      let previous = null;
      let index = 0;
      if (position === 0) {
        node.next = this.head;
        this.head = node;
      } else {
        while (index ++ < position) {
          previous = current;
          current = current.next;
        }
        node.next = current;
        previous.next = node;
      }
      this.length ++;
      return true;
    }
    return false;
  }
  removeAt (position) {
    if (position > -1 && position < this.length) {
      let current = this.head;
      let previous = null;
      let index = -1;
      if (position === 0) {
        this.head = current.next;
      } else {
        while (index ++ < position) {
          previous = current;
          current = current.next;
        }
        previous.next = current.next;
      }
      this.length --;
      return current.element;
    }
    return null;
  }
  findIndex (element) {
    let current = this.head;
    let index = -1;
    while (current) {
      if (element === current.element) {
        return index + 1;
      }
      index ++;
      current = current.next;
    }
    return -1;
  }
  remove (element) {
    const index = this.findIndex(element);
    return this.removeAt(index);
  }
  get size () {
    return this.length;
  }
  toString () {
    let current = this.head;
    let string = '';
    while (current) {
      string += current.element;
      current = current.next;
    }
    return string;
  }
};
// need use index 
const singleList = new SingleList();
singleList.append(1);
singleList.append(2);
singleList.append(3);
console.log(singleList.findIndex(3));
console.log(singleList.toString());
  • 双向链表
class Node {
  constructor (element) {
    this.element = this.element;
    this.prev = null;
    this.next = null;
  }
};

class DoubleList {
  constructor () {
    this.head = null; 
    this.tail = null;
    this.length = 0
  }
  insert (element, position) {
    if (position >= 0 && position <= this.length) {
      const node = new Node();
      let current = this.head;
      let previous = null;
      let index = 0;

      if (position === 0) {
        if (this.head === null) {
          this.head = node;
          this.tail = node;
        } else {
          node.next = current;
          this.head = node;
          current.prev = node;
        }
      } else if (position === this.length) {
        current = this.tail;
        current.next = node;
        node.prev = current;
        this.tail = node;
      } else {
        while (index ++ < position) {
          previous = current;
          current = current.next;
        }
        node.next = current;
        previous.next = node
        current.prev = node
        node.prev = previous
      }
      this.length ++;
      return true;
    }
    return false;
  }
};

const doubleList = new DoubleList();
doubleList.insert(0, 1);

/**
 * @class SingleList // 单向链表: A (head) -> B -> C -> D
 *
 * @method append append E 节点,只需要把D的next指向E,E默认的next是null
 * @method insert insert E 节点,需要从头开始遍历,next是遍历推动者(需要找到index位置的前后两个节点),只要把前置位节点的next设置为节点E,节点E的next设置为后置位节点,且需要维护head的状态
 * 
 * @class DoubleList // 双向链表: A (head) <- -> B <- -> C <- -> D (tail)
 * 
 * @method insert insert E 节点,需要从头开始遍历,next是遍历推动者(需要找到index位置的前后两个节点),只要把前置位节点的next设置为E,E的prev设置为前置位节点,E的next设置为后置位节点,E的后置位节点的prev为前置位节点,且需要维护head和tail的状态
 * 
 * @class CirvcleList // 单向循环链表:     A (head) -> B -> C -> D (tail) -> A
 * 
 * @method insert insert E 节点,需要从head开始遍历, next是遍历推动者(需要找到index位置的前后两个节点),只要把前置位节点的next设置为E,E的next设置为后置位节点,需要考虑next后如果是head的情况,且需要维护head和tail的状态
 */
  • 循环链表
import Queen from './queen.mjs';

class LoopQueen extends Queen {
  constructor (queens) {
    super(queens);
  }
  getIndex (index) {
    const length = this.queens.length;
    return index >= length ? (index % length) : index;
  }
  find (index) {
    return !this.isEmpty ? this.queens[this.getIndex(index)] : null;
  }
};

const loopQueen = new LoopQueen();
loopQueen.enquene(5);
loopQueen.enquene(6);
loopQueen.enquene(7);
console.log(loopQueen.find(3));
console.log(loopQueen.size);
集合
  • Set集合

集合是由一组无序且唯一(不能重复)的项组成的,是以 { value: value }的形式存储数据。

class Set {
  constructor () {
    this.items = {}
  }

  has (value) {
    return this.items.hasOwnProperty(value)
  }

  add (value) {
    if (!this.has(value)) {
      this.items[value] = value
      return true
    }     
    return false
  }

  remove (value) {
    if (this.has(value)) {
      delete this.items[value]
      return true
    }
    return false
  }

  get size() {
    return Object.keys(this.items).length
  }

  get values() {
    return Object.keys(this.items)
  }
};

const set = new Set();

set.add(1);
console.log(set.values);
console.log(set.has(1));
console.log(set.size);

/**
 * @class Set 集合是以 { value: value }的形式存储数据
 * @class Dictionary 字典是以{ key: value }的形式存储数据 
 */
字典
  • Dictionary字典

以 [键,值] 对为数据形态的数据结构,其中键名用来查询特定元素,类似于 Javascript 中的Object。

class Dictionary {
  constructor () {
    this.items = {}
  }

  set (key, value) {
    this.items[key] = value
  }

  get (key) {
    return this.items[key]
  }

  remove (key) {
    delete this.items[key]
  }

  get keys () {
    return Object.keys(this.items)
  }

  get values() {
    return Object.values(this.items)
  }
};

const dictionary = new Dictionary()
dictionary.set('zhangsan', 'zhangsan@163.com')
dictionary.set('lisi', 'lisi@163.com')
dictionary.set('wangwu', 'wangwu@163.com')

console.log(dictionary);
console.log(dictionary.keys);
console.log(dictionary.values);
console.log(dictionary.items);

散列(hash)
  • HashTable 哈希

根据关键码值(Key value)直接进行访问的数据结构;它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度;这个映射函数叫做散列函数,存放记录的数组叫做散列表。

class HashTable {
  constructor() {
    this.table = [];
  }
  static loseloseHashCode(key) {
    let hash = 0;
    for (let codePoint of key) {
      hash += codePoint.charCodeAt();
    }
    return hash % 37;
  }
  static djb2HashCode(key) { // 使用djb2 函数后,散列函数将不再会有冲突
    let hash = 5381
    for (let codePoint of key) {
      hash = hash * 33 + codePoint.charCodeAt();
    }
    return hash % 1013
  }
  put(key, value) {
    const position = HashTable.loseloseHashCode(key);
    this.table[position] = value;
  }

  get(key) {
    return this.table[HashTable.loseloseHashCode(key)];
  }

  remove(key) {
    this.table[HashTable.loseloseHashCode(key)] = undefined;
  }
};

const hash = new HashTable()
hash.put('Surmon', 'surmon.me@email.com');
hash.put('John', 'johnsnow@email.com');
hash.put('Tyrion', 'tyrion@email.com');
  • Tree 树

由 n(n>=1)个有限节点组成一个具有层次关系的集合;把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的,基本呈一对多关系,树也可以看做是图的特殊形式。

class Node {
  constructor (key) {
    this.key = key;
    this.left = null;
    this.right = null;
  }
}
class BinarySearchTree  {
  constructor () {
    this.root = null;
  }
  insert (key) {
    const newNode = new Node(key);
    if (this.root === null) {
      this.root = newNode;
    } else {
      this.insertNode(this.root, newNode);
    }
  }
  insertNode (parentNode, newNode) {
    if (newNode.key <  parentNode.key) {
      if (parentNode.left === null) {
        parentNode.left = newNode;
      } else {
        this.insertNode(parentNode.left, newNode);
      }
    } else {
      if (parentNode.right === null) {
        parentNode.right = newNode
      } else {
        this.insertNode(parentNode.right, newNode);
      }
    }
  }
  inOrderTraverse (callback) { // 中序遍历(是对树进行遍历的一种排序操作)
    const inOrderTraverseNode = (node, callback) => {
      if (node !== null) {
        inOrderTraverseNode(node.left, callback);
        callback(node.key);
        inOrderTraverseNode(node.right, callback);
      }
    };
    inOrderTraverseNode(this.root, callback)
  }
  preOrderTraverse(callback) { // 先序遍历(优先于后代节点的顺序访问每个节点)
    const preOrderTraverseNode = (node, callback) => {
      if (node !== null) {
        callback(node.key);
        preOrderTraverseNode(node.left, callback);
        preOrderTraverseNode(node.right, callback);
      }
    }
    preOrderTraverseNode(this.root, callback);
  }
  postOrderTraverse(callback) { // 后序遍历(优先访问后代节点,再访问自身,应用场景如统计folder的占用空间大小)
    const postOrderTraverseNode = (node, callback) => {
      if (node !== null) {
        postOrderTraverseNode(node.left, callback);
        postOrderTraverseNode(node.right, callback);
        callback(node.key);
      }
    }
    postOrderTraverseNode(this.root, callback);
  }
  search (key) {
    const searchNode = (node, key) => {
      if (node === null) return false;
      if (node.key === key) return node;
      return searchNode((node.key > key) ? node.left : node.right, key);
    };
    return searchNode(this.root, key);
  }
  min (key) { 
    let currentNode = null;
    if (key) {
      currentNode = this.search(key);
    }
    const minNode = (node) => {
      return node ? (node.left ? minNode(node.left) : node) : null;
    };
    return minNode(currentNode || this.root);
  }
  max (key) {
    let currentNode = null;
    if (key) {
      currentNode = this.search(key);
    }
    const minNode = (node) => {
      return node ? (node.right ? minNode(node.right) : node) : null;
    };
    return minNode(currentNode || this.root);
  }
};

const tree = new BinarySearchTree();
tree.insert(10);
tree.insert(9);
tree.insert(11);
tree.insert(8);
tree.insert(7);
//tree.inOrderTraverse(value => { console.log(value) });
//tree.preOrderTraverse(value => { console.log(value) });
//tree.postOrderTraverse(value => { console.log(value) });
//console.log(tree.search(8));
//console.log(tree.min());
//console.log(tree.max());
  • Graph 图

图是网络结构的抽象模型;图是一组由边连接的节点(顶点);任何二元关系都可以用图来表示,常见的比如:道路图、关系图,呈多对多关系。

import Dictionary from './dictionary.mjs';
class Graph {
  constructor() {
    this.vertices = [];
    this.adjList = new Dictionary(); // 相邻边的集合设置为字典结构
  }
  addVertex(v) {
    this.vertices.push(v);
    this.adjList.set(v, []);
  }
  addEdge(v, w) {
    this.adjList.get(v).push(w);
    this.adjList.get(w).push(v);
  }
  toString() {
    return this.vertices.reduce((r, v, i) => {
        return this.adjList.get(v).reduce((r, w, i) => {
            return r + `${w} `;
        }, `${r}\n${v} => `);
    }, '');
  }
  bfs (vertice, callback) {  // 广度优先搜索算法 (Breadth-First Search,BFS) 
    const read = [];
    const pending = [vertice || this.vertices[0]];
    const adjList = this.adjList;
    const readVertices = (vertices) => {
      vertices.forEach(key => {
        read.push(key);
        pending.shift();
        adjList.get(key).forEach(v => {
          if (!pending.includes(v) && !read.includes(v)) {
            pending.push(v);
          }
        });
        if (callback) callback(key);
        if (pending.length > 0) readVertices(pending);
      });
    };
    readVertices(pending);
  }
  bfs_input_dis_pre (vertice, callback) {
    const read = [];
    const pending = [vertice || this.vertices[0]];
    const distance = [];
    const predecessors = [];
    const adjList = this.adjList;
    const readVertices = (vertices) => {
      vertices.forEach(key => {
        read.push(key);
        pending.shift();
        distance[key] = distance[key] || 0;
        predecessors[key] = predecessors[key] || null;
        adjList.get(key).forEach(k => {
          if (!pending.includes(k) && !read.includes(k)) {
            pending.push(k);
            distance[k] = distance[key] + 1;
            predecessors[k] = key;
          }
        });
        if (callback) callback(key);
        if (pending.length > 0) readVertices(pending);
      });
    };
    readVertices(pending);
    return { distance, predecessors }
  }
  dfs(callback) {  // 深度优先搜索(Depth-First Search,DFS) 区分于广度优先搜索,不需要指定一个源顶点,核心判断条件是所有顶点是否已读
    const read = [];
    const adjList = this.adjList;
    const readVertices = (vertices) => {
      vertices.forEach(key => {
        if (read.includes(key)) return;
        read.push(key);
        if (callback) callback(key);
        if (read.length !== this.vertices.length) {
          readVertices(adjList.get(key));
        }
      });
    };
    readVertices(adjList.keys);
  }
  distance(fromVertex) {
    const vertices = this.vertices;
    const { distances, predecessors } = this.bfs2(fromVertex);
    vertices.forEach(toVertex => {
      if (!!distances[toVertex]) {
        let preVertex = predecessors[toVertex];
        let slug = '';
        while (fromVertex !== preVertex) {
          slug = `${preVertex} - ${slug}`;
          preVertex = predecessors[preVertex];
        }
        slug = `${fromVertex} - ${slug}${toVertex}`;
      }
    })
  }
  dfs_explore(callback) {
    let readTimer = 0;
    const read = [];
    const readTimes = [];
    const finishedTimes = [];
    const predecessors = [];
    const adjList = this.adjList;
    const readVertices = (vertices, predecessor) => {
      vertices.forEach(key => {
        readTimer++;
        if (adjList.get(key).every(v => read.includes(v)) && !finishedTimes[key]) {
            finishedTimes[key] = readTimer;
        }
        if (read.includes(key)) return false;
        readTimes[key] = readTimer;
        read.push(key);
        if (callback) callback(key);
        predecessors[key] = predecessors[key] || predecessor || null;
        if (read.length !== this.vertices.length) {
            readVertices(adjList.get(key), key);
        }
      })
    }
    readVertices(adjList.keys);
    return { readTimes, finishedTimes, predecessors };
  }
}

const graph = new Graph();
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'].forEach(n => 
  graph.addVertex(n)
);
graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.addEdge('A', 'D');
graph.addEdge('C', 'D');
graph.addEdge('C', 'G');
graph.addEdge('D', 'G');
graph.addEdge('D', 'H');
graph.addEdge('B', 'E');
graph.addEdge('B', 'F');
graph.addEdge('E', 'I');
//graph.bfs(graph.vertices[0], (v) => {console.log('Visited vertex: ' + v)});
//console.log(graph.bfs_input_dis_pre(graph.vertices[0]));
//graph.dfs((v) => {console.log('Visited vertex: ' + v)});
console.log(graph.dfs_explore());



<

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342