注:本文章内容源自《学习javascript数据结构与算法(第3版)》,算是本身的一个学习笔记,不喜勿喷。javascript
导语:咱们知道,能够在数组的任意位置上进行删除或者添加元素。然而,强大的数组在有些时候并不能知足我们人类的需求阿!你好比说有时候就须要一种能在添加或删除元素时进行更多控制的数据结构。因此就有了今天我们要学的这个数据结构——栈。
java
天天据说哪一个哪一个大佬是全栈大佬,谁谁谁进阶全栈了,可想而知栈这个字对于广大程序员来讲仍是很是熟悉的。那么栈究竟是个什么玩意儿呢?看看用普通话是怎么解释的吧!程序员
栈是一种听从后进先出(LIFO)原则的有序集合。新添加或待删除的元素都保存在栈的同一端,称做栈顶,另外一端就叫栈底。在栈里,新元素都靠近栈顶,旧元素都接近栈底。算法
首先建立一个类来表示栈:数组
class Stack {
constructor() {
this.items = [];
}
}复制代码
咱们须要一种数据结构来保存栈里的元素,俺们这里选择的是数组。数组容许咱们在任何位置添加或删除元素(数组就是这么硬核!)。因为栈遵循的是LIFO原则,须要对元素的插入和删除功能进行限制,为了实现这些限制,接下来要为栈声明一些方法。bash
咱们要实现的第一个方法就是push。该方法负责往栈里添加元素,可是得注意一点:该方法只添加元素到栈顶,也就是栈的末尾。因此这里固然是用数组的push方法啦~数据结构
Push(element) {
this.items.push(element)
}复制代码
第二个,我们来实现pop方法。该方法主要用来移除栈里的元素。栈遵循LIFO的原则,因此移出去的是最后添加进去的元素。所以,可使用数组的pop方法~性能
pop() {
return this.items.pop();
}复制代码
若是想知道栈里最后添加的元素是什么,能够用 peek 方法。该方法将返回栈顶的元素。学习
peek() {
return this.items[this.items.length - 1]
}复制代码
这里要实现的方法是 isEmpty,若是栈为空的话将返回 true,不然就返回 false。ui
isEmpty() {
return this.items.length == 0
}复制代码
相似于数组的 length 属性,咱们也能实现栈的 length。对于集合,最好用 size 代替length。由于栈的内部使用数组保存元素,因此能简单地返回栈的长度。
size() {
return this.items.length
}复制代码
最后,咱们来实现 clear 方法。clear 方法用来移除栈里全部的元素,把栈清空。
clear() {
this.items = []
}复制代码
固然啦,你不怕麻烦,也能够用pop方法来清空哦~
通过上面一系列的操做,我们也算是建立了一个简单的栈啦~那么让我们来使用一下吧,看看好使很差使~(不是有那么句话嘛,是骡子是马,牵出来溜溜)
const stack = new Stack()
console.log(stack.isEmpty()) // true 这个时候里面还没东西呢 确定是空啦复制代码
接下来,往里面添加一些东西吧,否则显得太冷清啦~
stack.push('二蛋');
stack.push('日哥');复制代码
若是调用 peek 方法,将输出 '日哥',由于它是往栈里添加的最后一个元素。
console.log(stack.peek()); // 日哥复制代码
再添加一个元素。
stack.push('木木');
console.log(stack.size()); // 3
console.log(stack.isEmpty()) // false复制代码
咱们往栈里添加了 '木木'。若是调用 size 方法,输出为 3,由于栈里有三个元素('二蛋'、'日哥' 和 '木木')。若是咱们调用 isEmpty 方法,会看到输出了 false(由于栈里有三个元素,不是空栈)。最后,咱们再添加一个元素。
stack.push('唐唐');复制代码
而后,调用两次 pop 方法从栈里移除两个元素。
stack.pop()
stack.pop()
console.log(stack.size()) // 2复制代码
在两次调用 pop 方法前,咱们的栈里有四个元素。调用两次后,如今栈里仅剩下 '二蛋' 和 '日哥' 了。
上面已经实现了一个基于数组的栈,那么为何还要建立一个基于javascript对象的栈呢?难道是头发太多吗?那确定不能是由于这个阿!事出皆有因,先看看为何要这么干。
建立一个stack类最简单的方式就是使用一个数组来存储其元素。可是在现实生活的项目里很常见的一种状况是在处理大量数据的时候,咱们一样须要评估如何操做数据才是最高效的。(你看看,你看看,说到底仍是为了性能考虑,才有了这个用对象来建立一个栈的想法儿)。
那凭什么说使用数组就不高效了呢?(数组:就是,你说说,凭啥我来处理就不高效了?)
答案就是在使用数组的时候大部分方法的时间复杂度都是O(n)。说到O(n),我已经蒙圈了,这究竟是个啥意思?时间复杂度是个啥意思?不清楚没关系,这一篇文章的目的也不是着重介绍它,就长话短说吧!
O(n)的意思是,有时候咱们寻找一个数组中的元素,须要迭代整个数组才能找到咱们想要的那个元素,在最坏的状况下那确定是要迭代数组的全部位置了,这一来不就耗费时间了嘛!这个n就表明数组的长度。试想一下,若是数组里的元素足够多,那须要的时间会更长(这无异于大海捞针阿~就像茫茫人海中你看到这篇文章的概率同样)。
说完了为何须要建立一个基于javascript对象的栈,那么就让俺们动起手来吧!
第一步确定是新建一个js文件,声明一个Stack类,有了这一步,剩下的路就好走了~
class Stack{
constructor() {
this.count = 0
this.items = {}
}
}复制代码
你看看,你看看,和上面数组的代码一对比 这就发现不一样了,多了一个count;items还从一个空数组变成了一个空对象。count就是用来帮助我们记录栈的大小的,空对象就是用来存放栈中的元素的~
push(element) {
this.items[this.count] = element
this.count++
}复制代码
在js中对象是一系列键值对的集合。要向栈中添加元素,咱们将使用 count 变量做为 items 对象的键名,插入的元素则是它的值。在向栈插入元素后,咱们递增 count 变量。
能够往里面插入两个元素试试看。
const stack = new Stack();
stack.push('二蛋');
stack.push('木木');复制代码
通过上面的操做之后,在内部items和count属性以下所示
count: 0,
items: {
0: '二蛋',
1: '木木'
}复制代码
isEmpty() {
return this.count === 0
}
size() {
return this.count
}复制代码
//从栈中弹出元素
pop() {
if (this.isEmpty()) {
return undefined
}
this.count--;
const result = this.items[this.count]
delete this.items[this.count]
return result
}复制代码
首先,咱们须要检验栈是否为空。若是为空,就返回 undefined。若是栈不为空的话,咱们会将 count 属性减 1,并保存栈顶的值,以便在删除它以后将它返回。
// 查看栈顶的值并将栈清空
peek() {
if (this.isEmpty()) {
return undefined
}
return this.items[this.count - 1]
}
clear() {
this.items = {}
this.count = 0
}复制代码
toString() {
if (this.isEmpty()) {
return undefined
}
let objString = `${this.items[0]}`
for (let i = 1; i < this.count; i++) {
objString = `${objString}, ${this.items[i]}`
}
return objString
}复制代码
在数组版本中,咱们不须要关心 toString 方法的实现,由于数据结构能够直接使用数组已经提供的 toString 方法。对于使用对象的版本,咱们将建立一个 toString 方法来像数组同样打印出栈的内容。
若是栈是空的,咱们只需返回一个空字符串便可。若是它不是空的,就须要用它底部的第一个元素做为字符串的初始值,而后迭代整个栈的键,一直到栈顶,添加一个逗号(,)以及下一个元素。