1.栈数据结构
栈是一种遵从后进先出(LIFO)原则的有序集合。新添加或待删除的元素都保存在栈的同一端,称作栈顶,另一端就叫栈底。在栈里,新元素都靠近栈顶,旧元素都接近栈底。
在现实生活中也能发现许多栈的例子。例如,下图中的一摞书。
栈也被用在编程语言的编译器和内存中保存变量、方法调用等,浏览器历史记录。
1.1 创建一个基于数组的栈
我们需要一种数据结构来保存栈里的元素。可以选择数组。数组可以在任何位置添加或删除元素。由于栈遵循LIFO原则,需要对元素的插入和删除功能进行限制。也要为栈声明一些方法。
创建一个类来表示栈。
class Stack {
constructor() {
this.items = []
}
// 添加新元素到栈顶
push(element) {
return this.items.push(element)
}
// 移除栈顶的元素,同时返回被移除的元素
pop() {
return this.items.pop()
}
// 返回栈顶的元素,不对栈做任何修改(该方法不会移除栈顶的元素,仅仅返回它)
peek () {
return this.items[this.items.length - 1]
}
// 如果栈里没有任何元素就返回true,否则返回false
isEmpty () {
return this.items.length === 0
}
// 清空栈里的所有元素
clear() {
return this.items = []
}
// 返回栈里的元素个数。该方法和数组的length属性很类似
size() {
return this.items.length
}
}
1.2 使用Stack 类
首先需要初始化类,然后验证下栈是否为空。
const stack = new Stack()
console.log(stack.isEmpty()) // true
接下来,往栈里添加一些元素
stack.push(5)
stack.push(8)
console.log(stack.peek()) // 输出 8
再添加一个元素
stack.push(11)
console.log(stack.size()) // 输出 3
console.log(stack.isEmpty) // 输出false
我们往栈里添加了11,如果调用了size方法,输出3,因为栈里有三个元素(5, 8,11),如果我们调用isEmpty方法,会看到false。最后,我们再添加一个元素。
stack.push(15)
下图描述了栈的当前状态
结果,连续两次调用pop方法,从栈里移除两个元素
stack.pop()
stack.pop()
console.log(stack.size()) // 输出 2
在两次调用pop方法前,我们的栈里有四个元素。调用两次后,现在栈里仅剩下5和8了。如下图所示:
1.3 使用JavaScript对象创建Stack类
使用数组时,大部分的时间复杂度是O(n)。O(n)的意思是,我们需要迭代整个数组直到找到的那个元素。另外数组时元素的一个有序集合,为了保证元素的排列有序,会占用更多空间。
创建Stack类如下:
class Stack {
constructor() {
this.count = 0
this.items = {}
}
// 添加新元素到栈顶
push (element) {
this.items[this.count] = element
this.count++
}
// 移除栈顶的元素,同时返回被移除的元素
pop () {
if (this.isEmpty()) {
return undefined
}
this.count --
const result = this.items[this.count]
delete this.items[this.count]
return result
}
// 返回栈顶的元素,不对栈做任何修改(该方法不会移除栈顶的元素,仅仅返回它)
peek () {
if (this.isEmpty()) {
return undefined
}
return this.items[this.count - 1]
}
// 如果栈里没有任何元素就返回true,否则返回false
isEmpty () {
return this.count === 0
}
// 清空栈里的所有元素
clear () {
this.items = {}
this.count = 0
}
// 返回栈里的元素个数。该方法和数组的length属性很类似
size () {
return this.count
}
// 方法类似于数组的toString()
toString() {
if (this.isEmpty()) {
return ''
}
let str = ''
for (let i = 0; i < this.count; i++) {
str += this.items[i]
}
return str
}
}
2. 保护数据结构内部元素
对于Stack类来说,要确保元素只会被添加到栈顶。而不是栈底或其他位置。
2.1 下划线命名
下划线命名约定就是在属性名称之前加上一个(_)。不过这种方式只是一种约定,并不能保护数据,而且只能依赖于使用我们开发者具备的常识。
class Stack {
constructor() {
this._count = 0;
this._items = {};
}
}