数据结构是计算机中存储、组织数据的方式。通常情况下,精心选择的数据结构可以带来最优效率的算法,其实算法并不是一个很高级的东西,它充斥在每一种代码组织方式中;而且各种语言关于数据结构方面的内容都是大同小异的,会了一种语言的数据结构方面的内容就可以快速掌握其它语言的的数据结构;有一部分人对JavaScript有偏见,觉得这种语言没有档次,但又有多少人在学完JavaScript敢说精通它。
你没有看错,常见的数组就是一种数据结构,对于数组就是掌握一下它的各种方法,如push(),pop(),shift(),unshift(),indexOf(),sort(),slice(),splice()等,在这里就不赘述了。
遵循的是后进先出
class Stack {
//加#是为了保证其是私有属性,不让外界随意调用
#items = [];
pop() {
return this.#items.pop()
}
push(data) {
return this.#items.push(data)
}
//返回栈顶
peek() {
return this.#items.at(-1)
}
isEmpty() {
return this.#items.length > 0 ? false : true
}
size() {
return this.#items.length
}
clear() {
this.#items = []
}
toString(){
return this.#items.join(' ')
}
}
将十进制数转换为其它进制的数时用到了辗转相除法,刚好是把得到的余数从后到前输出得到对应进制下的数,如果把这些数从前到后推到栈里,再取出来岂不妙哉。
function convert(decNumber, binaryNumber) {
let number = decNumber
let stack = new Stack()
let string = ''
//防止转换成十六进制的时候乱码
let baseString = '0123456789ABCDEF'
while (number > 0) {
stack.push(number % binaryNumber)
number = Math.floor(number / binaryNumber)
}
while (!(stack.isEmpty())) {
string += baseString[stack.pop()]
}
return string
}
console.log(convert(50, 2));
先进先出,后端进,前端出,类似于排队
class Queue {
#items = [];
//一旦元素变多,还用shift会造成运行效率低
deleteQueue() {
return this.#items.shift()
}
enterQueue(data) {
return this.#items.push(data)
}
//返回队头
frontQueue() {
return this.#items.at(0)
}
isEmpty() {
return this.#items.length > 0 ? false : true
}
size() {
return this.#items.length
}
clear() {
this.#items = []
}
toString() {
return this.#items.join(' ')
}
}
class Queue {
#items = {};
#count = 0;
#lowCount = 0;
//出队
deleteQueue() {
if (this.isEmpty()) {
return undefined
}
let res = this.#items[this.#lowCount]
delete this.#items[this.#lowCount]
this.#lowCount++
return res
}
//进队
enterQueue(data) {
this.#items[this.#count] = data
this.#count++
}
//返回队头
frontQueue() {
return this.#items[this.#lowCount]
}
isEmpty() {
return this.size === 0
}
size() {
return this.#count - this.#lowCount
}
clear() {
this.#items = {}
this.#count = 0
this.#lowCount = 0
}
toString() {
let str = ""
for (let i = this.#lowCount; i < this.#count; i++) {
str += `${this.#items[i]} `
}
return str.trim()
}
}
类似于击鼓传花,传指定次数,最终东西在谁手上谁淘汰,一直淘汰到队列里只剩一个人为最终的胜者
function game(list, num) {
let queue = new Queue()
for (let i = 0; i < list.length; i++) {
queue.enterQueue(list[i])
}
while (queue.size() > 1) {
for (i = 0; i < num; i++) {
queue.enterQueue(queue.deleteQueue())
}
console.log(queue.deleteQueue(), '淘汰');
}
console.log(queue.deleteQueue());
//最终获胜者
return queue.deleteQueue()
}
game(['aks', 'uej', 'jsm', 'duj', 'llq'], 7)
与一般的队列有所不同的情况是,可以从队头进队,队尾可以出队,简而言之就是两端都能进出队伍。
class DeQueue {
#items = {};
#count = 0;
#lowCount = 0;
removeFront() {
if (this.isEmpty()) {
return undefined
}
let res = this.#items[this.#lowCount]
delete this.#items[this.#lowCount]
this.#lowCount++
return res
}
addBack(data) {
this.#items[this.#count] = data
this.#count++
}
//在队头添加
addFront(data) {
if (this.isEmpty()) {
this.addBack()
} else {
if (this.#lowCount > 0) {
this.#lowCount--
this.#items[this.#lowCount] = data
} else {
for (let i = this.#count; i > 0; i--) {
this.#items[i] = this.#items[i - 1]
}
this.#items[0] = data
this.#count++
}
}
}
//在队尾删除
removeBack() {
if (this.isEmpty()) {
return
} else {
this.#count--
let res = this.#items[this.#count]
delete this.#items[this.#count]
return res
}
}
//返回队头
peekFront() {
return this.#items[this.#lowCount]
}
//返回队尾
peekBack() {
return this.#items.at(-1)
}
isEmpty() {
return this.size === 0
}
size() {
return this.#count - this.#lowCount
}
clear() {
this.#items = {}
this.#count = 0
this.#lowCount = 0
}
toString() {
let str = ""
for (let i = this.#lowCount; i < this.#count; i++) {
str += `${this.#items[i]} `
}
return str.trim()
}
}
//回文应用
function test(str) {
//将输入有空格的字符串转化为无空格的
const lowStr = str.toLocaleLowerCase().split(' ').join('')
let dequeue = new DeQueue()
for (let i = 0; i < lowStr.length; i++) {
dequeue.addBack(lowStr[i])
}
while (dequeue.size() > 1) {
if (dequeue.removeFront() !== dequeue.removeBack()) {
console.log('不是回文');
break
} else {
console.log('是回文结构');
}
}
}
test('dadadb') //不是回文
可以把next看成一个指针,其能指向表中下一个存储的数据
//根据节点的需要创建相应的元素
class Node {
constructor(element) {
this.element = element
this.next = null
}
}
//单链表
class LinkList {
constructor() {
this.head = null
this.count = 0
}
//从表尾放入新节点
push(element) {
const node = new Node(element)
if (this.head === null) {
this.head = node
} else {
let current = this.head
while (current.next != null) {
current = current.next
}
current.next = node
}
this.count++
}
size() {
return this.count
}
isEmpty() {
return this.size() === 0
}
//指定位置删除
removeAt(index) {
if (index >= 0 && index < this.count) {
let current = this.head
if (index === 0) {
this.head = this.head.next
} else {
let previous
for (let i = 0; i < index; i++) {
previous = current
current = current.next
}
previous.next = current.next
}
this.count--
return current.element
}
return undefined
}
//同样也是指定位置删除,但用到了后面封装的getNodeAt方法
removeAt2(index) {
if (index >= 0 && index < this.count) {
let current = this.head
if (index === 0) {
this.head = this.head.next
} else {
let previous = this.getNodeAt(index - 1)
current = previous.next
previous.next = current.next
}
this.count--
return current.element
}
return undefined
}
//根据索引值找到相应的节点
getNodeAt(index) {
if (index >= 0 && index < this.count) {
let node = this.head
for (let i = 0; i < index; i++) {
node = node.next
}
return node
}
return undefined
}
indexOf(element) {
let current = this.head
for (let i = 0; i < this.count; i++) {
//防止其直接输入一个对象变量
if (JSON.stringify(element) === JSON.stringify(current.element)) {
return i
}
current = current.next
}
return -1
}
//指定元素删除
remove(element) {
let index = this.indexOf(element)
let removeElement = this.removeAt(index)
return removeElement
}
insert(element, index) {
if (index >= 0 && index <= this.count) {
const node = new Node(element)
if (index === 0) {
let current = this.head
node.next = current
this.head = node
this.count++
} else {
let previous = this.getNodeAt(index - 1)
let current = previous.next
previous.next = node
node.next = current
this.count++
}
return true
}
return false
}
}
节点除了存储数据以外,还有两个指针分别指向前一个节点地址(prev)和下一个节点地址(next)
//前人栽树,后人乘凉,继承自之前的Node类
class DoubleNode extends Node {
constructor(element) {
super(element)
this.prev = null
}
}
//继承自LinkList类加方法重写
class DoubleLinkList extends LinkList {
constructor() {
super()
this.tail = null
}
push(element) {
const doubleNode = new DoubleNode(element)
if (this.head === null) {
this.head = doubleNode
this.tail = doubleNode
} else {
this.tail.next = doubleNode
doubleNode.prev = this.tail
this.tail = doubleNode
}
this.count++
}
insert(element, index) {
if (index >= 0 && index <= this.count) {
const doubleNode = new DoubleNode(element)
if (index === 0) {
//分链表是否有元素
if (this.head === null) {
this.head = doubleNode
this.tail = doubleNode
} else {
this.head.prev = doubleNode
doubleNode.next = this.head
this.head = doubleNode
}
} else if (index === this.count) {
this.tail.next = doubleLinkList
doubleLinkList.prev = this.tail
this.tail = doubleLinkList
} else {
let previous
let current = this.head
for (let i = 0; i < index; i++) {
current = current.next
previous = current.prev
}
current.prev = doubleNode
doubleNode.next = current
doubleNode.prev = previous
previous.next = doubleNode
}
this.count++
return true
}
return false
}
removeAt(index) {
if (index >= 0 && index < this.count) {
let current = this.head
if (index === 0) {
this.head = current.next
if (this.count === 1) {
this.tail = null
} else {
this.head.prev = null
}
} else if (index === this.count - 1) {
current = this.tail
this.tail = current.prev
this.tail.next = null
} else {
current = this.getNodeAt(index)
let previous = current.prev
let future = current.next
previous.next = future
future.prev = previous
}
this.count--
return current.element
}
return undefined
}
}
//循环链表类继承自单链表类
class CirculateLinkList extends LinkList {
constructor() {
super()
}
push(element) {
const node = new Node(element)
let current
if (this.head === null) {
this.head = node
} else {
current = this.getNodeAt(this.size() - 1)
current.next = node
}
node.next = this.head
this.count++
}
insert(element, index) {
if (index >= 0 && index <= this.count) {
const node = new Node(element)
let current = this.head
if (index === 0) {
if (this.head === null) {
this.head = node
node.next = this.head
} else {
node.next = this.head
current = this.getNodeAt(this.size() - 1)
this.head = node
current.next = node
}
} else {
if (index === this.count) {
current = this.getNodeAt(index - 1)
current.next = node
node.next = this.head
} else {
let previous = this.getNodeAt(index - 1)
previous.next = node
node.next = previous.next
}
}
this.count++
return true
}
return false
}
removeAt(index) {
if (index >= 0 && index < this.count) {
let current = this.head
if (index === 0) {
if (this.count === 1) {
this.head = null
} else {
let last = this.getNodeAt(this.size() - 1)
this.head = current.next
last.next = this.head
}
} else {
let previous = this.getNodeAt(index - 1)
current = previous.next
previous.next = current.next
}
this.count--
return current.element
}
return undefined
}
}
Set结构,其实已经是JavaScript里封装好的,它是以值:值进行存储,且存入的值不能够重复
class Set {
items = {}
add(element) {
if (!this.has(element)) {
this.items[element] = element
return true
}
return false
}
delete(element) {
if (this.has(element)) {
delete this.items[element]
return true
}
return false
}
has(element) {
return element in this.items
}
clear() {
this.items = {}
}
size() {
return Object.keys(this.items).length
}
values() {
return Object.values(this.items)
}
}
const mySet = new Set()
let arr = [2, 1, 3, 3, 4, 5, 2, 1]
arr.forEach((item) => {
mySet.add(item)
})
console.log(mySet.values());//[ 1, 2, 3, 4, 5 ]
Set结构生成的是一个迭代器的结构,并且由于是集合,可以进行相关的并集交集差集等运算,可以进行相关的,详细的相关使用请查看下方代码
let mySet = new Set()
mySet.add(1)
mySet.add(2)
mySet.add(3)
let item = mySet.values() //生成的是一个迭代器
console.log(item); //[Set Iterator] { 1, 2, 3 }
console.log(item.next()); //{ value: 1, done: false }
console.log(item.next()); //{ value: 2, done: false }
console.log(item.next()); //{ value: 3, done: false }
// for (let i of item) {
// console.log(i);
// } //1 2 3
// console.log([...item]); //[ 1, 2, 3 ]
// const A = new Set([1, 2, 3, 4])
// const B = new Set([2, 3, 4, 5])
// //取并集
// const C = new Set([...A, ...B])
// console.log(C); //Set(5) { 1, 2, 3, 4, 5 }
// //取交集
// const D = new Set([...A].filter(item => B.has(item)))
// console.log(D); //Set(3) { 2, 3, 4 }
// //取差集
// const E = new Set([...A].filter(item => !B.has(item)))
// console.log(E); //Set(1) { 1 }
字典和集合很相似,集合以[值,值]的形式存储元素,字 典则是以[键,值]的形式来存储元素。字典也称作映射、符号表或关联数组。
class Dictionary {
table = {}
//保证当存入的键是对象的时候,强制它转换为json字符串的形式
toStrFn(item) {
if (item === null) {
return "null"
} else if (item === undefined) {
return "undefined"
} else if (typeof item === 'string' || item instanceof String) {
return item
}
return JSON.stringify(item)
}
hasKey(key) {
return this.table[this.toStrFn(key)] != null
}
set(key, value) {
if (key != null && value != null) {
const tableKey = this.toStrFn(key)
// this.table[tableKey] = value
this.table[tableKey] = new ValuePair(key, value)
return true
}
return false
}
get(key) {
let result = this.table[this.toStrFn(key)]
return result == null ? undefined : result.value
}
remove(key) {
if (this.hasKey(key)) {
delete this.table[this.toStrFn(key)]
return true
}
return false
}
keys() {
return this.keyValues().map(item => item.key)
}
values() {
return this.keyValues().map(item => item.value)
}
keyValues() {
return Object.values(this.table)
}
size() {
return Object.keys(this.table).length
}
isEmpty() {
return this.size() === 0
}
clear() {
this.table = {}
}
forEach(ch) {
const valuePair = this.keyValues()
for (let i = 0; i < valuePair; i++) {
ch(valuePair[i].key, valuePair[i].value)
}
}
}
class ValuePair {
constructor(key, value) {
this.key = key
this.value = value
}
}
HashMap 类,它是 Dictionary 类的一种散列表 实现方式。.散列算法的作用是尽可能快地在数据结构中找到一个值。这样在面对海量数据的时候可以加快查询的速度
class HashTable {
table = {}
toStrFn(item) {
if (item === null) {
return "null"
} else if (item === undefined) {
return "undefined"
} else if (typeof item === 'string' || item instanceof String) {
return item
}
return JSON.stringify(item)
}
// hashCode(key) {
// if (typeof key === 'number') {
// return key
// } else {
// const tableKey = this.toStrFn(key)
// let hash = 0
// for (let i = 0; i < tableKey.length; i++) {
// hash += tableKey.charCodeAt(i)
// }
// return hash % 35
// }
// }
//将对应字符串或对象转成的字符串的ASCII值进行转换生成相应的数值
hashCode(key) {
const tableKey = this.toStrFn(key)
let hash = 5381
for (let i = 0; i < tableKey.length; i++) {
hash = (hash * 33) + tableKey.charCodeAt(i)
}
return hash % 1013
}
set(key, value) {
if (key != null && value != null) {
let position = this.hashCode(key)
this.table[position] = new ValuePair(key, value)
}
}
get(key) {
let result = this.table[this.hashCode(key)]
return result == null ? undefined : result.value
}
remove(key) {
if (this.table[this.hashCode(key)] != null) {
delete this.table[this.hashCode(key)]
return true
}
return false
}
}
class ValuePair {
constructor(key, value) {
this.key = key
this.value = value
}
}
var mymap = new Map()
mymap.set("name","kerwin")
mymap.set({a:1},"aaaaaa")
mymap.get("name")
mymap.delete("name")
树是一种分层数据的抽象模型。
二叉树中的节点最多只能有两个子节点:一个是左侧子节点,另一个是右侧子节点。
二叉搜索树(BST)是二叉树的一种,但是只允许你在左侧节点存储(比父节点)小的值, 在右侧节点存储(比父节点)大的值。
>>关于遍历
中序遍历是一种以上行顺序访问 BST 所有节点的遍历方式,也就是以从最小到最大的顺序 访问所有节点。 中序遍历的一种应用就是对树进行排序操作。
先序遍历是以优先于后代节点的顺序访问每个节点的。先序遍历的一种应用是打印一个结构 化的文档。(访问根节点,遍历左子树,遍历右子树)
后序遍历则是先访问节点的后代节点,再访问节点本身。后序遍历的一种应用是计算一个目 录及其子目录中所有文件所占空间的大小。(简而言之就是在树中从最末尾的那一代开始从左到右打印,一直到整个树都被遍历完成)
>>关于移除要考虑的情况
class Node {
constructor(key) {
this.key = key
this.left = null
this.right = null
}
}
class BST {
constructor() {
this.root = null
}
insert(key) {
if (this.root == null) {
this.root = new Node(key)
} else {
this.insertNode(this.root, key)
}
}
insertNode(node, key) {
if (this.compareFn(key, node.key) === -1) {
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)
}
}
}
compareFn(a, b) {
if (a === b) {
return 0
}
return a < b ? -1 : 1
}
//中序遍历,注意递归调用的顺序
middleMap(callback) {
this.middleMapNode(this.root, callback)
}
middleMapNode(node, callback) {
if (node != null) {
this.middleMapNode(node.left, callback)
callback(node.key)
this.middleMapNode(node.right, callback)
}
}
//先序遍历
prevMap(callback) {
this.prevMapNode(this.root, callback)
}
prevMapNode(node, callback) {
if (node != null) {
callback(node.key)
this.prevMapNode(node.left, callback)
this.prevMapNode(node.right, callback)
}
}
//后序遍历
behindMap(callback) {
this.behindMapNode(this.root, callback)
}
behindMapNode(node, callback) {
if (node != null) {
this.behindMapNode(node.left, callback)
this.behindMapNode(node.right, callback)
callback(node.key)
}
}
min() {
return this.minNode(this.root)
}
minNode(node) {
let current = node
while (current != null && current.left != null) {
current = current.left
}
return current.key
}
max() {
return this.maxNode(this.root)
}
maxNode(node) {
let current = node
while (current != null && current.right != null) {
current = current.right
}
return current.key
}
search(key) {
return this.searchNode(this.root, key)
}
searchNode(node, key) {
if (node === null) {
return false
}
if (this.compareFn(key, node.key) === -1) {
return this.searchNode(node.left, key)
} else if (this.compareFn(key, node.key) === 1) {
return this.searchNode(node.right, key)
} else {
return true
}
}
remove(key) {
this.removeNode(this.root, key)
}
removeNode(node, key) {
if (node == null) {
return null
}
if (this.compareFn(key, node.key) === -1) {
node.left = this.removeNode(node.left, key)
console.log(node);
return node
} else if (this.compareFn(key, node.key) === 1) {
node.right = this.removeNode(node.right, key)
console.log(node);
return node
} else {
if (node.left == null && node.right == null) {
node = null
return node
}
if (node.left == null) {
node = node.right
return node
} else if (node.right == null) {
node = node.left
return node
}
const target = this.minNode(node.right)
node.key = target.key
node.right = this.removeNode(node.right, target.key)
return node
}
}
}
const tree = new BST()
tree.insert(6)
tree.insert(4)
tree.insert(8)
tree.insert(3)
tree.insert(5)
tree.middleMap((value) => console.log(value)) //3 4 5 6 8
tree.prevMap((value) => console.log(value)) //6 4 3 5 8
tree.behindMap((value) => console.log(value)) //3 5 4 8 6
二叉堆是一种特殊的二叉树,有两种特性
它是一棵完全二叉树,表示树的每一层都有左侧和右侧子节点(除了最后一层的叶节点), 并且最后一层的叶节点尽可能都是左侧子节点,这叫作结构特性。
二叉堆不是最小堆就是最大堆。最小堆允许你快速导出树的最小值,最大堆允许你快速 导出树的最大值。所有的节点都大于等于(最大堆)或小于等于(最小堆)每个它的子 节点。这叫作堆特性。
//把二叉堆的数据按数组的格式存储起来
class minHeap {
heap = []
//根据现有节点计算左子节点
getLeftSonIndex(index) {
return 2 * index + 1
}
getRightSonIndex(index) {
return 2 * index + 2
}
getParentIndex(index) {
if (index === 0) {
return undefined
} else {
return Math.floor((index - 1) / 2)
}
}
insert(value) {
if (value != null) {
this.heap.push(value)
this.shiftUp(this.heap.length - 1)
return true
}
return false
}
shiftUp(index) {
let parent = this.getParentIndex(index)
while (index > 0 && this.compareFn(this.heap[parent], this.heap[index]) === 1) {
this.swap(this.heap, parent, index)
index = parent
parent = this.getParentIndex(index)
}
}
compareFn(a, b) {
if (a === b) {
return 0
}
return a < b ? -1 : 1
}
swap(arr, a, b) {
let temp = arr[a]
arr[a] = arr[b]
arr[b] = temp
}
size() {
return this.heap.length
}
isEmpty() {
return this.size() === 0
}
findMinimun() {
return this.heap[0]
}
//去除根节点及后面的操作
extractFirst() {
if (this.isEmpty()) {
return undefined
}
if (this.size() === 1) {
return this.heap.shift()
}
const removedValue = this.heap[0]
this.heap[0] = this.heap.pop()
this.shiftDown(0)
return removedValue
}
shiftDown(index) {
let current = index
let left = this.getLeftSonIndex(index)
let right = this.getRightSonIndex(index)
if (left < this.size() && this.compareFn(this.heap[current], this.heap[left]) === 1) {
current = left
}
if (right < this.size() && this.compareFn(this.heap[current], this.heap[right]) === 1) {
current = right
}
if (index !== current) {
this.swap(this.heap, index, current)
this.shiftDown(current)
}
}
}
可以偷个懒,直接继承并随便改写一下比较两数大小的方法
class maxHeap extends minHeap {
constructor() {
super()
}
compareFn(a, b) {
if (a === b) {
return 0
}
return a > b ? -1 : 1
}
}
数据结构是个神奇的东西,它充斥在代码中,却仿佛离我们那么遥远,学习数据结构不仅是在面对不同的数据时要施加不同的策略,而且可以提高我们的代码阅读能力。当然了,对于数据结构的学习依然任重而道远,各位同仁加油吧,希望可以点赞收藏加关注嘿嘿。