一种听从先进后出原则的有序集合javascript
听从先进先出原则的有序项java
修改版的队列,设置优先级node
基于队列,克服‘假溢出’想象的队列。例如队列长度为5,取第6个数据时,会取第一个数据git
要存储多个元素,数组(或列表)多是最经常使用的数据结构github
每种语言都实现了数组。这种数据结构很是方便,提供了一个便利的[]语法来访问它的元素。数组
而后,这种数据结构有一个缺点:在大多数语言中,数组的大小是固定的,从数组的起点或中间插入或移除项的成本很高,因须要移动元素。数据结构
尽管javascript中的Array类方法能够帮咱们作这些事,但背后的处理机制一样如此。闭包
链表储存有序的元素集合,但不一样于数组,链表中的元素在内存中不是连续放置的。每一个元素由一个储存元素自己的节点和一个指向下一个元素的引用(也称指针或连接)组成app
相对于传统的数组,链表的一个好处在于,添加或移除元素的时候不须要移动其余元素。然而,链表须要使用指针,所以实现链表时须要额外注意。函数
数组的另外一个细节是能够直接访问任意位置的任何元素,而要想访问链表中间的一个元素,须要从起点(表头)开始迭代列表直到找到所需的元素
// 链表节点 class Node { constructor(element) { this.element = element this.next = null } } // 链表 class LinkedList { 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) { const node = new Node(element) let current = this.head let previous = null let index = 0 if (position === 0) { 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 < length) { let current = this.head let previous = null let index = 0 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.indexOf(element) return this.removeAt(index) } isEmpty() { return !this.length } size() { return this.length } // 转为字符串 toString() { let current = this.head let string = '' while (current) { string += ` ${current.element}` current = current.next } return string } }
与普通链表的区别在于,双向链表的连接是双向的,一个链向下一个元素,一个链向上一个元素。
双向链表提供了两种迭代列表的方法,从头至尾或反过来。在单向链表中,若是迭代列表时错过了要找的元素,就须要回到列表起点,从新开始迭代。
循环链表能够是单向链表同样只有单向引用,也能够向双向链表有双向引用。循环链表和链表之间惟一的区别在于,最后元素指向下一个元素的指针(tail.next)不是引用null,而是指向第一个元素(head)
链表相比数组最重要的优势,就是无需移动链表中的元素,就能轻松地添加移除元素。所以,当你须要添加移除不少元素时,最好的选择就是链表,而不是数组
集合是由一组无序且惟一(不能重复)的项组成的。这个数据结构使用了与优先集合相同的数学概念,但应用在计算机科学的数据结构中
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) } }
集合,字典,散列表均可以存储不重复的数据。字典和集合很像,集合是以{ value: value }的形式存储数据,而字典是以{ key: value}的形式存储数据,字典也称为映射。
object对象即是字典在javascript中的实现
一个树结构包含一系列存在父子关系的节点。每一个节点都有一个父节点(除顶部的第一个节点)以及零个或者多个子节点
二叉树中的节点最多只能有两个子节点:一个是左侧子节点,另外一个是右侧子节点。二叉树在计算机科学中应用很是普遍
二叉搜索树(BST)是二叉树的一种,可是它只容许你在左侧节点存储比父节点小的值,右侧节点存储比父节点大的值
下图展现二叉搜索树的组织结构方式
代码实现二叉搜索树
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) const insertNode = (node, newNode) => { if (newNode.key < node.key) { if (node.left === null) { node.left = newNode } else { insertNode(node.left, newNode) } } else { if (node.right === null) { node.right = newNode } else { insertNode(node.right, newNode) } } } if (!this.root) { this.root = newNode } else { insertNode(this.root, newNode) } } }
使用二叉搜索树
const tree = new BinarySearchTree() tree.insert(11) tree.insert(7) tree.insert(5) tree.insert(3) tree.insert(9) tree.insert(8) tree.insert(10) tree.insert(13) tree.insert(12) tree.insert(14) tree.insert(20) tree.insert(18) tree.insert(25)
构建的树以下图:
遍历一颗树是指访问树的每个节点并对它们进行某种操做的过程。
访问树的全部节点有三种方式:中序、先序、后序
中序遍历是一种以上行顺序访问BST全部节点的遍历方式,也就是以从小到最大的顺序访问全部的节点。中序遍历的一种应用就是对树进行排序操做。实现以下:
inOrderTraverse(callback) { const inOrderTraverseNode = (node, callback) => { if (node !== null) { inOrderTraverseNode(node.left, callback) callback(node.key) inOrderTraverseNode(node.right, callback) } } inOrderTraverseNode(this.root, callback) }
inOrderTraverse方法接受一个回调函数做为参数,回掉函数用来定义咱们对便利到的每一个节点进行的操做,这也叫作访问者模式
在以前展现的树上执行下面的方法:
tree.inOrderTraverse(value => { console.log(value) })
控制台将会按照顺序输出:3 5 6 7 8 9 10 11 12 13 14 15 18 20 25
下图描述了inOrderTraverse方法的访问路径
先序遍历是以优先于后代节点的顺序访问每一个节点的,先序遍历的一种应用是打印一个结构化的文档。
preOrderTraverse(callback) { const preOrderTraverseNode = (node, callback) => { if (node !== null) { callback(node.key) preOrderTraverseNode(node.left, callback) preOrderTraverseNode(node.right, callback) } } preOrderTraverseNode(this.root, callback) }
下面的图描绘了 preOrderTraverse 方法的访问路径:
后序遍历是先访问节点的后代节点,再访问节点自己。后续遍历的一种应用是计算一个目录和它的子目录全部文件所占空间的大小
postOrderTraverse(callback) { const postOrderTraverseNode = (node, callback) => { if (node !== null) { postOrderTraverseNode(node.left, callback) postOrderTraverseNode(node.right, callback) callback(node.key) } } postOrderTraverseNode(this.root, callback) }
这个例子中,后续遍历会先访问左侧节点,而后是右侧节点,最后是父节点自己。
中序遍历、先序遍历、后续遍历的实现方式类似的,惟一不一样是三行代码的执行顺序。
下图描绘postOrderTraverse方法的访问路径
在树中,有三种常常执行的搜索顺序:
用下图的树做为示例
实现方法:
min(node) { const minNode = node => { return node ? (node.left ? minNode(node.left) : node) : null } return minNode(node || this.root) } max(node) { const maxNode = node => { return node ? (node.right ? maxNode(node.right) : node) : null } return maxNode(node || this.root) }
搜索一个特定的值
search(key) { const searchNode = (node, key) => { if (node === null) return false if (node.key === key) return node return searchNode((key < node.key) ? node.left : node.right, key) } return searchNode(root, key) }
移除一个节点
remove(key) { const removeNode = (node, key) => { if (node === null) return false if (node.key === key) { console.log(node) if (node.left === null && node.right === null) { let _node = node node = null return _node } else { console.log('key', key) } } else if (node.left !== null && node.key > key) { if (node.left.key === key) { node.left.key = this.min(node.left.right).key removeNode(node.left.right, node.left.key) return node.left } else { return removeNode(node.left, key) } } else if (node.right !== null && node.key < key) { if (node.right.key === key) { node.right.key = this.min(node.right.right).key removeNode(node.right.right, node.right.key) return node.right } else { return removeNode(node.right, key) } } else { return false } return removeNode((key < node.key) ? node.left : node.right, key) } return removeNode(this.root, key) }
完整写法:
var removeNode = function(node, key){ if (node === null){ //{2} return null; } if (key < node.key){ //{3} node.left = removeNode(node.left, key); //{4} return node; //{5} } else if (key > node.key){ //{6} node.right = removeNode(node.right, key); //{7} return node; //{8} } else { //键等于node.key //第一种状况——一个叶节点 if (node.left === null && node.right === null){ //{9} node = null; //{10} return node; //{11} } //第二种状况——一个只有一个子节点的节点 if (node.left === null){ //{12} node = node.right; //{13} return node; //{14} } else if (node.right === null){ //{15} node = node.left; //{16} return node; //{17} } //第三种状况——一个有两个子节点的节点 var aux = findMinNode(node.right); //{18} node.key = aux.key; //{19} node.right = removeNode(node.right, aux.key); //{20} return node; //{21} } }; var findMinNode = function(node){ while (node && node.left !== null) { node = node.left; } return node; };
TODO