乐趣区

JS数据结构学习:栈

栈的定义
什么是栈?栈是一种遵循后进先出原则的有序集合,新添加的或者待删除的元素都保存在栈的同一端,称为栈顶,另一端称为栈底,在栈里,新元素靠近栈顶,旧元素靠近栈底,用个图来看大概这样式的:用一个更形象的例子来说明:上网的时候,每点击一个超链接,浏览器都会打开一个新的页面,并且压入到一个访问历史栈中,你可以不断的点击打开新的页面,但总是可以通过回退重新访问以前的页面,从浏览器的访问历史栈中弹出历史网页地址,从栈顶弹出,总是最新的最先弹出
栈的创建
首先创建一个类用来表示栈,接着声明一个数组用来保存栈里的元素:
function Stack() {
let items = []
// 方法声明
}
创建好栈之后,需要为栈声明一些方法,栈一般会包含以下几个方法:

push(): 添加新元素到栈顶
pop(): 移除栈顶的元素,同时返回被移除的元素
peek(): 返回栈顶的元素,不对栈做任何修改
isEmpty(): 如果栈里没有任何元素就返回 true,否则返回 false
clear(): 移除栈里的所有元素
size(): 返回栈里的元素个数

具体实现:
function Stack() {
let items = []
// 添加元素到栈顶,也就是栈的末尾
this.push = function (element) {
items.push(element)
}
// 栈的后进先出原则,从栈顶出栈
this.pop = function () {
return items.pop()
}
// 查看栈顶的元素,访问数组最后一个元素
this.peek = function () {
return items[items.length – 1]
}
// 检查栈是否为空
this.isEmpty = function () {
return items.length == 0
}
// 返回栈的长度,栈的长度就是数组的长度
this.size = function () {
return items.length
}
// 清空栈
this.clear = function () {
items = []
}
// 打印栈元素
this.print = function () {
console.log(items.toString())
}
}
栈的使用
现在我们来看如何使用栈:
let stack = new Stack()
stack.push(1)
stack.push(2)
stack.push(3)
console.log(stack.peek()) // 3
console.log(stack.isEmpty()) // false
console.log(stack.size()) // 3
先向栈中加入三个元素 1,2,3,接下来用一张图来看一下入栈的过程:入栈过程都是在栈顶依次入栈,接着调用 peek 方法返回栈顶元素 3,isEmpty 返回 false,size 返回栈的长度 3,然后在继续调用 pop 来看看出栈的过程:
stack.pop()
stack.print() // 1,2
出栈也是和入栈相同,都是从栈顶开始出栈,保证栈的后入先出原则
es6 声明 Stack 类
上面创建了一个 Stack 函数来充当类,并且在里面声明了一个私有变量,但是在 es6 里面是有类的语法的,可以直接用 es6 新语法来声明,代码大概是这样事的:
class Stack {
constructor () {
this.items = []
}
push (element) {
this.items.push(element)
}
pop () {
return this.items.pop()
}
peek () {
return this.items[items.length – 1]
}
isEmpty () {
return this.items.length == 0
}
size () {
return this.items.length
}
clear () {
this.items = []
}
print () {
console.log(this.items.toString())
}
}
let stack = new Stack()
stack.push(1)
console.log(stack.isEmpty())
stack.print()
看起来似乎不错,比之前要简单,关键是看起来更加合理,使用的是类的语法,调用也没有什么问题,但是如果这么执行呢?
console.log(stack.items)
会发现一个问题,在 stack 类外面可以直接访问类里面的属性,按照设计 items 应该是 Stack 类的私有属性才对,就像之前 Stack 函数里面的私有变量,是不能在外部访问的,如何才能将 items 变成私有属性呢?应该会有和我一样想法的人,使用闭包,在闭包里面里面定义私有属性,然后再将 stack 类返回回来,代码实现大概是这样的:
let Stack = (function () {
let items = new WeakMap()
class Stack {
constructor () {
items.set(this, [])
}
push (element) {
let s = items.get(this)
s.push(element)
}
pop () {
let s = items.get(this)
return s.pop()
}
peek () {
let s = items.get(this)
return s[s.length – 1]
}
isEmpty () {
let s = items.get(this)
return s.length == 0
}
size () {
let s = items.get(this)
return s.length
}
clear () {
let s = items.get(this)
s = []
}
print () {
let s = items.get(this)
console.log(s.toString())
}
}
return Stack
})()
可能有人会问为什么这里要把 items 定义成一个 WeakMap 对象,先简单介绍一下 WeakMap 对象的用法,WeakMap 对象是一对键 / 值对的集合,其键必须是对象,值可以是任意的,定义成 WeakMap 就是为了将 items 属性变成私有属性,在外部调用 Stack 对象的时候无法访问到 items 属性。
栈的应用
前面介绍了那么多栈相关的知识,最后也是介绍栈的应用场景的时候了,栈的实际应用非常广泛,例如用来存储访问过的任务或路径、撤销的操作。为了有一个更直观的应用了解,下面会介绍如何用来解决计算机中的进制转换问题,将 10 进制转换为其他进制数,可能不少人已经忘了如何进行进制转换了,下面先来看一个简单的 10 进制转 2 进制的过程:
上面的图中展示将一个 10 进制 8 转化为 2 进制数的过程,接下来看看用栈是如何实现的:
function conver(num, radix) {
let stack = new Stack()
let binaryString = ”
let digits = ‘0123456789ABCDEF’
while (num > 0) {
stack.push(num % radix)
num = parseInt(num / radix)
}
while (!stack.isEmpty()) {
binaryString += digits[stack.pop()]
}
console.log(binaryString)
}
conver(8, 2) // 1000
总结
这篇文章主要对栈做了简单介绍,动手实践了栈的实现,以及用栈实现了一个简单进制转换的功能。如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞。

退出移动版