4. 集合
特点
集合是由一组无序且唯一(即不能重复)的项组成的。可以将其想象成一个既没有重复元素,也没有顺序概念的数组。
实现
class Set {
constructor() {
this.items = {}
}
// 添加一个元素
add(element) {
if (this.has(element)) {
return false
}
// 重点注意,这里是和栈/队列等数据结构区别最为明显的地方
// 另外两种数据结构使用下标作为key,Set使用element本身作为key
// 对象不允许同一个key指向不同的value,确保了集合里元素的唯一性
this.items[element] = element
return true
}
// 删除元素
delete(element) {
if (!this.has(element)) {
return undefined
}
const result = this.items[element]
delete this.items[element]
return result
}
// set中是否有element元素
has(element) {
return this.items.hasOwnProperty(element)
}
// 获取集合长度
size() {
return Object.keys(this.items).length
}
// 获取集合所有element值
values() {
return Object.values(this.items)
}
// 清空集合
clear() {
this.items = {}
}
}
实际使用场景
并集
function union(setA, setB) {
const unionSet = new Set()
setA.values().forEach(value => unionSet.add(value))
setB.values().forEach(value => unionSet.add(value))
return unionSet
}
交集
function intersection(setA, setB) {
const intersectionSet = new Set()
// 找到size更小的集合,减少循环次数
const sizeRelation = setA.size() > setB.size()
const [smallerSet, biggerSet] = [
sizeRelation ? setB : setA,
sizeRelation ? setA : setB,
]
smallerSet.values().forEach(value => {
if (biggerSet.has(value)) {
intersectionSet.add(value)
}
})
return intersectionSet
}
差集
function difference(setA, setB) {
const differenceSet = new Set()
setA.values().forEach(value => {
if (!setB.has(value)) {
differenceSet.add(value)
}
})
return differenceSet
}
是否是子集
function isSubsetOf(setA, setB) {
if (setA.size() > setB.size()) {
return false
}
let isSubset = setA.values.every(value => {
return setB.has(value)
})
return isSubset
}
通过ES6 Set数据结构实现
let a = new Set([1, 2, 3])
let b = new Set([4, 3, 2])
// 并集
let union = new Set([...a, ...b])
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)))
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)))
5. 字典和散列表
特点
- 字典和集合很相似,都是由一组无序且唯一的项组成的;之间的区别是,集合以
[value, value]的形式存储元素,字典则是以[key, value]的形式进行存储。 - 散列表是字典的一种实现,通过散列算法尽可能快地在数据结构中找到一个值。
实现
字典
class ValuePair {
constructor(key, value) {
this.key = key
this.value = value
}
toString() {
return `${this.key}: ${this.value}`
}
}
class Dictionary {
constructor() {
this.table = {}
}
set(key, value) {
if (!key || !value) {
return false
}
// 通过ValuePair保存key value
this.table[key] = new ValuePair(key, value)
}
get(key) {
if (!this.hasKey(key)) {
return undefined
}
return this.table[key]
}
remove(key) {
if (!this.hasKey(key)) {
return undefined
}
const result = this.get(key)
delete this.table[key]
return result
}
// 是否已有这个键
hasKey(key) {
return !!this.table[key]
}
// keys和values取出的ValuePair实例,需要map获取具体的值
keys() {
return Object.keys(this.table)
}
values() {
return Object.values(this.table).map(valuePair => valuePair.value)
}
size() {
return Object.keys(this.table).length
}
isEmpty() {
return this.size() === 0
}
clear() {
this.table = {}
}
forEach(callbackFn) {
const valuePairs = Object.values(this.table)
for (let i = 0; i < valuePairs.length; i++) {
// 执行cb函数
const result = callbackFn(valuePairs[i].key, valuePairs[i].value)
if (result === false) {
break // 如果cb函数返回false,终止循环
}
}
}
toString() {
if (this.isEmpty()) {
return ''
}
const values = this.values()
const result = ''
for (let i = 0; i < values.length; i++) {
result += `${values[i]}${i === values.length - 1 ? '' : ','}`
}
return result
}
}
散列表
定义散列函数,常见的有三种方法是lose lose,djb2,sdbm,具体实现可以参考这篇文章
// 定义djb2散列函数
function djb2HashCode(key) {
// 初始化一个hash值并赋值为一个质数(目前大多数都是用5381)
let hash = 5381
for (let i = 0; i < key.length; i++) {
hash = hash * 33 + key.charCodeAt(i)
}
// 将使用相加的和与另一个质数相除
return hash % 1013
}
即使通过散列函数djb2对key进行处理,也有可能造成返回值相同,后一个相同的key覆盖前面value的情况,这里采用分离链接的方式来解决,既为散列表的每一个位置创建一个链表并将元素存储在里面
class HashTable {
constructor() {
this.table = {}
}
put(key, value) {
if (!key || !value) {
return false
}
// 通过散列函数获取当前key在table中的位置
const position = djb2HashCode(key)
if (!this.table[position]) {
// 如果当前位置没有值,则生成一个链表实例
this.table[position] = new LinkedList()
}
// 为当前位置的链表添加一个元素
this.table[position].push(new ValuePair(key, value))
}
get(key) {
const position = djb2HashCode(key)
const linkedList = this.table[position]
// 判断table当前位置是否有值,该位置的链表是否为空
if (!this.table[position] || linkedList.isEmpty()) {
return undefined
}
// 遍历链表,对比key
let current = linkedList.getElementAt(0)
while (current) {
if (current.element.key === key) {
return current.element.value
}
current = current.next
}
}
remove(key) {
const position = djb2HashCode(key)
const linkedList = this.table[position]
if (!this.table[position] || linkedList.isEmpty()) {
return undefined
}
let current = linkedList.getElementAt(0)
while (current) {
if (current.element.key === key) {
const result = current.element.value
linkedList.remove(current.element)
return result
}
current = current.next
}
}
}
6. 树
特点
- 树是包含一系列存在父子关系的节点的数据结构;位于树顶部的节点叫作根节点,至少有一个子节点的节点称为内部节点,没有子元素的节点称为外部节点或叶子节点。每个节点都包含一个属性,深度,节点的深度取决于它的祖先节点的数量;树的高度取决于所有节点深度的最大值。
- 二叉树中的节点最多只能有两个子节点:一个是左侧子节点,另一个是右侧子节点。
- 二叉搜索树(
Binary Search Tree)是二叉树的一种,但是只允许你在左侧节点存储(比父节点)小的值, 在右侧节点存储(比父节点)大的值。 - 自平衡树(
Adelson-Velskii-Land)是在BST的基础上增加了平衡因子概念,每次在添加或者移除节点后,都会实现自平衡(即任意节点下左右子树高度差不超过±1)
实现
二叉搜索树BST
const Compare = {
LESS_THAN: -1,
BIGGER_THAN: 1,
}
function defaultCompare(a, b) {
if (a === b) {
return 0
}
return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN
}
class Node {
constructor(key) {
this.key = key
this.left = null
this.right = null
}
}
class BinarySearchTree {
constructor(compareFn = defaultCompare) {
this.root = null
this.compareFn = compareFn
}
insert(key) {
if (this.root === null) {
this.root = new Node(key)
} else {
// 从root开始向下查找合适的位置并添加key
this.insertNode(this.root, key)
}
}
insertNode(node, key) {
if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
// 由于BST左侧子节点小于父节点的特点,如果比较结果是小于的话,递归朝左侧寻找
if (node.left === null) {
node.left = new Node(key)
} else {
this.insertNode(node.left, key)
}
} else {
// 反之向右侧寻找
if (node.right === null) {
node.right = new Node(key)
} else {
this.insertNode(node.right, key)
}
}
}
// 中序遍历,以上行顺序(从小到大)访问BST所有节点
inOrderTraverse(callback) {
this.inOrderTraverseNode(this.root, callback)
}
inOrderTraverse(node, callback) {
if (node !== null) {
// 递归调用leftNode rightNode
this.inOrderTraverseNode(node.left, callback)
callback(node.key)
this.inOrderTraverseNode(node.right, callback)
}
}
// 先序遍历,以父级优先于后代的顺序访问
preOrderTraverse(callback) {
this.preOrderTraverseNode(this.node, callback)
}
preOrderTraverseNode(node, callback) {
callback(node.key)
this.preOrderTraverseNode(node.left, callback)
this.preOrderTraverseNode(node.right, callback)
}
// 后序遍历,和先序遍历相反,后代优先于父级的顺序
postOrderTraverse(callback) {
this.postOrderTraverseNode(this.node, callback)
}
postOrderTraverseNode(node, callback) {
this.postOrderTraverseNode(node.left, callback)
this.postOrderTraverseNode(node.right, callback)
callback(node.key)
}
// 搜索最小值,访问最左侧节点
min() {
return this.minNode(this.root)
}
// 可以根据node取当前树的最小值
minNode(node) {
let current = node
while (current !== null && current.left !== null) {
current = current.left
}
return current
}
// 最大值,访问最右侧节点
max() {
return this.maxNode(this.root)
}
maxNode(node) {
let current = node
while (current !== null && current.right !== null) {
current = current.right
}
return current
}
// 搜索一个特定的值
search(key) {
return this.searchNode(this.root, key)
}
searchNode(node, key) {
if (node === null) {
return undefined
}
// 比较key和node.key 向左或者右递归查找
if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
return this.searchNode(node.left, key)
} else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
return this.searchNode(node.rigth, key)
} else {
return node
}
}
// 移除节点
remove(key) {
// removeNode返回值是一个node,需要重新为整棵树赋值
this.root = this.removeNode(this.root, key)
}
removeNode(node, key) {
if (node === null) {
return node
}
// 这里注意,对比后需要重新为需要改变的一边赋值,这也是为什么remove函数要给root重新赋值的原因
// 重新赋值后返回node节点
if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
node.left = this.removeNode(node.left, key)
return node
} else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
node.right = this.removeNode(node.rigth, key)
return node
} else {
// 第一种情况,节点本身就是叶子节点
if (node.left === null && node.right === null) {
// 将node清空,即为删除,直接返回即可
node = null
return node
} else {
// 第二种情况,有left或者right节点,返回该节点
if (node.left !== null) {
node = node.left
return node
} else if (node.right !== null) {
node = node.right
return node
}
}
// 第三种情况,node是内部节点,且left right都有子节点
// 可以将left子树中的最大值取出来(或者right子树最小值),作为node节点的值
const rightMinNode = this.minNode(node.right)
node.key = rightMinNode.key
// 删除掉叶子节点(重新调用removeNode函数)
node.right = this.removeNode(node.right, rightMinNode.key)
/**
* const leftMaxNode = this.maxNode(node.left)
* node.key = leftMaxNode.key
* node.left = this.removeNode(node.left, leftMaxNode.key)
*/
return node
}
}
}
自平衡树AVL
// 定义平衡因子常量
const BalanceFactor = {
UNBALANCED_RIGHT: 1,
SLIGHTLY_UNBALANCED_RIGHT: 2,
BALANCED: 3,
SLIGHTLY_UNBALANCED_LEFT: 4,
UNBALANCED_LEFT: 5,
}
class AVL extends BinarySearchTree {
constructor() {
super()
}
// 计算node节点高度
getNodeHeight(node) {
if (node === null) {
return -1
}
// 递归查找左右子树的高度,因为node是null的时候返回-1,因此结果+1
return (
Math.max(this.getNodeHeight(node.left), this.getNodeHeight(node.right)) +
1
)
}
/**
* 计算平衡因子
* -1 0 1 即为平衡
* -2 2 则需要调整
*/
getBalanceFactor(node) {
const heightDifference =
this.getNodeHeight(node.left) - this.getNodeHeight(node.right)
switch (heightDifference) {
case -2:
return BalanceFactor.UNBALANCED_RIGHT
case -1:
return BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT
case 1:
return BalanceFactor.SLIGHTLY_UNBALANCED_LEFT
case 2:
return BalanceFactor.UNBALANCED_LEFT
default:
return BalanceFactor.BALANCED
}
}
/**
* 向右单旋转
* 节点的左侧子节点的高度大于右侧子节点的高度,且左侧子节点也是平衡或者左侧较重的
*/
rotateLL(node) {
const temp = node.left // 左子树节点
node.left = temp.right // 将node的左子树节点设置为左子树节点的右节点(即左子树的右树作为node节点的左树)
temp.right = node // 将node作为左子树节点的右数
return temp // 返回原来的左子树节点
}
// 向左单旋转,和rotateLL相反
rotateRR(node) {
const temp = node.right
node.right = temp.left
temp.left = node
return temp
}
/**
* 向右双旋转
* 节点的左侧子节点的高度大于右侧子节点的高度,且左侧子节点也是平衡或者右侧较重的
* 1. 先对node的左子树进行左旋转
* 2. 再对整棵树进行右旋转
*/
rotateLR(node) {
node.left = this.rotateRR(node.left)
return this.rotateLL(node)
}
// 向左双旋转
rotateRL(node) {
node.right = this.rotateLL(node.right)
return this.rotateRR(node)
}
// 重写insert方法,主要是判断平衡因子
insert(key) {
this.root = this.insertNode(this.root, key)
}
insertNode(node, key) {
if (node === null) {
return new Node(key)
} else if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
node.left = this.insertNode(node.left, key) // 由于一开始对node进行了!null判断,这里不需要像BST树一样再判断一次
} else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
node.right = this.insertNode(node.right, key)
} else {
return node // 重复的值
}
// 判断是否平衡
const balanceFactor = this.getBalanceFactor(node)
if (balanceFactor === BalanceFactor.UNBALANCED_LEFT) {
// 左侧高度比右侧高,且左子树左边较重
if (this.compareFn(key, node.left.key) === Compare.LESS_THAN) {
return this.rotateLL(node)
} else {
return this.rotateLR(node)
}
}
if (balanceFactor === BalanceFactor.UNBALANCED_RIGHT) {
if (this.compareFn(key, node.right.key) === Compare.BIGGER_THAN) {
return this.rotationRR(node)
} else {
return this.rotationRL(node)
}
}
}
removeNode(node, key) {
node = super.removeNode(node, key) // 删除方法沿用BST
if (node === null) {
return node
}
// 和insertNode方法一致,操作后需要检查平衡因子
const balanceFactor = this.getBalanceFactor(node.left)
if (balanceFactor === BalanceFactor.UNBALANCED_LEFT) {
const balanceFactorLeft = this.getBalanceFactor(node) // 左子树的平衡因子
if (balanceFactorLeft === BalanceFactor.SLIGHTLY_UNBALANCED_LEFT) {
return this.rotationLL(node)
} else if (
balanceFactorLeft === BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT
) {
return this.rotationLR(node.left)
}
} else if (balanceFactor === BalanceFactor.UNBALANCED_RIGHT) {
const balanceFactorRight = this.getBalanceFactor(node.right) // 右子树的平衡因子
if (balanceFactorRight === BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT) {
return this.rotationRR(node)
} else if (
balanceFactorRight === BalanceFactor.SLIGHTLY_UNBALANCED_LEFT
) {
return this.rotationRL(node.right)
}
}
return node
}
}