栈数据结构
栈数据结构是一种遵从后进先出(LIFO) 原则的有序集合, 新添加或需要被删除的元素都是在栈顶,另一端叫栈底。在生活中的例子有汉诺塔,这是完全符合栈进出原则的例子。在撤回中也是基本符合这个原则的。
使用对象来实现栈
这里就不使用JS的数组来实现栈,因为数组是元素的有序的集合,为了保证元素排列有序,它会占用更多的空间,而且数组的时间复杂度大多数都是O(n)。我们直接使用对象来模拟数组。
声明一个Stack类
// stack.js
class Stack {
constructor () {
this.items = {}
this.count = 0
}
//方法
}
在这个类中,count用来帮助记录栈的长度,增加、删除元素
栈元素的插入
在数组中,需要向尾部插入的话,限制只可以插入一个。
push (element) {
this.items[this.count] =element
this.count ++ // 记录栈的长度、为下一个插入\弹出做准备
}
现在插入两个元素检查方法是否正确
const stack = new Stack()
stack.push('Jack')
stack.push('Mimi')
console.log(stack) // Stack { items: { '0': 'Jack', '1': 'Mimi' }, count: 2 }
可以看到元素以键值对的格式出现,符合数组的基本格式,并同时返回了栈的长度。
栈元素的弹出
由于count的值是栈的长度和下一个插入元素的键。所以在弹出时,需要count-1。
pop () {
this.count --
const result = this.items[this.count]
delete this.items[this.count]
return result
}
但实际这个代码是存在一定问题的,栈是空的时候会报错,所以还需要检查栈是否为空。
栈的大小和是否为空
大小我们可以返回count的值,是否为空直接利用count的值计算
size () {
return this.count
}
isEmpty () {
return this.count === 0
}
查看栈顶值和栈清空
peek () {
if ( this.isEmpty() ){
return undefined
}
return this.count[this.count-1]
}
clear () {
this.items = {}
this.count = 0
}
当然清空也可以保证LIFO原则
while( !this.isEmpty() )
{
this.pop()
}
实现 toString 方法
可以利用字符串拼接和循环来实现
toString() {
if (this.isEmpty()) {
return ''
}
let objString = `${this.items[0]}`
for (let i = 1; i < this.count; i++) {
objString = `${objString},${this.items[i]}`
}
return objString
}
最后,回顾以下我们实现的方法,除了toString方法,其他方法的复杂度均为O(1),表示我们一次就可以直接找到目标元素和对其操作。
队列数据结构
队列数据结构遵循的先进先出(FIFO) 原则的集合。在队尾插入新元素,在队头弹出元素。目前最熟悉的场景莫过于排队做核酸/(ㄒoㄒ)/~~ 。
现在利用对象来模拟队列。
class Queue{
constructor () {
this.count = 0 // 辅助确认队尾
this.lowestCount = 0 // 确认队头
this.items = {}
}
// 方法
}
仍然需要count,但这里面的count跟栈的有一定的区别,单靠count不再能计算队列的长度,但可以指向队尾的下一个元素。还需要loestCount它前指向的队列头元素。所以队列的长度是count - lowerCount。
添加元素
跟栈的相同,都是从尾部添加, 所以只有count有变化,lowerCount不需要变化。
enqueue (item) {
this.items[this.count] = item
this.count ++
}
检查队列长度和是否为空
队列长度计算count - lowerCount。
size () {
return this.count - this.lowestCount
}
isEmpty () {
return this.size() === 0
}
弹出队列头元素
因为当队头弹出时,lowestCount不再指向弹出的队头,而是弹出队头的下一位,所以需要lowestCount需要+1
dequeue () {
if (this.isEmpty()){
return undefined
}
const result = this.items[this.lowestCount]
delete this.items[this.lowestCount]
this.lowestCount ++
return result
}
实现 toString 方法
toString () {
if (this.isEmpty()){
return undefined
}
let word = this.items[this.lowestCount]
for (let i = this.lowestCount+1 ; i<this.count; i++ ){
word = `${word},${this.items[i]}`
}
return word
}
从代码来看,队列因为比栈多了lowestCount属性,所以栈可以做到的事情,队列都是做到的。下一篇将讨论双端队列,一种可以允许我们同时从前端和后端添加和移除元素的特殊队列。