学习javascript数据结构——栈

545 阅读7分钟

注:本文章内容源自《学习javascript数据结构与算法(第3版)》,算是自己的一个学习笔记,不喜勿喷。

   导语:我们知道,可以在数组的任意位置上进行删除或者添加元素。然而,强大的数组在有些时候并不能满足咱们人类的需求阿!你比如说有时候就需要一种能在添加或删除元素时进行更多控制的数据结构。所以就有了今天咱们要学的这个数据结构——栈。

0栈数据结构

每天听说哪个哪个大佬是全栈大佬,谁谁谁进阶全栈了,可想而知栈这个字对于广大程序员来说还是非常熟悉的。那么栈到底是个什么玩意儿呢?看看用普通话是怎么解释的吧!

栈是一种遵从后进先出(LIFO)原则的有序集合。新添加或待删除的元素都保存在栈的同一端,称作栈顶,另一端就叫栈底。在栈里,新元素都靠近栈顶,旧元素都接近栈底。

1.创建一个基于数组的栈

首先创建一个类来表示栈:

class Stack {
  constructor() {
    this.items = []; 
  }
}

我们需要一种数据结构来保存栈里的元素,俺们这里选择的是数组。数组允许我们在任何位置添加或删除元素(数组就是这么硬核!)。由于栈遵循的是LIFO原则,需要对元素的插入和删除功能进行限制,为了实现这些限制,接下来要为栈声明一些方法。

  • push(element(s)):向栈顶添加一个或多个元素。
  • pop():移除栈顶的元素,同时返回被移除的元素。
  • peek():返回栈顶的元素(嘛也不干,单纯的返回)。
  • isEmpty():如果栈里边儿没有任何元素了那就返回true,否则它就返回一个false。
  • clear():移除栈里所有的元素。
  • size():返回栈里的元素个数(有点像数组的length属性)。


1.1向栈添加元素

我们要实现的第一个方法就是push。该方法负责往栈里添加元素,但是得注意一点:该方法只添加元素到栈顶,也就是栈的末尾。所以这里当然是用数组的push方法啦~

Push(element) {
  this.items.push(element)
}

1.2从栈移除元素

第二个,咱们来实现pop方法。该方法主要用来移除栈里的元素。栈遵循LIFO的原则,所以移出去的是最后添加进去的元素。因此,可以使用数组的pop方法~


pop() {
  return this.items.pop();
}

1.3查看栈顶元素

如果想知道栈里最后添加的元素是什么,可以用 peek 方法。该方法将返回栈顶的元素。


peek() {
  return this.items[this.items.length - 1]
}

1.4检查栈是否为空

这里要实现的方法是 isEmpty,如果栈为空的话将返回 true,否则就返回 false。


isEmpty() {
  return this.items.length == 0 
}

1.5返回栈的元素个数

类似于数组的 length 属性,我们也能实现栈的 length。对于集合,最好用 size 代替length。因为栈的内部使用数组保存元素,所以能简单地返回栈的长度。


size() {
  return this.items.length
}

1.6清空栈元素

最后,我们来实现 clear 方法。clear 方法用来移除栈里所有的元素,把栈清空。


clear() {
  this.items = []
}

当然啦,你不怕麻烦,也可以用pop方法来清空哦~

1.7使用stack类

经过上面一系列的操作,咱们也算是创建了一个简单的栈啦~那么让咱们来使用一下吧,看看好使不好使~(不是有那么句话嘛,是骡子是马,牵出来溜溜)


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 方法前,我们的栈里有四个元素。调用两次后,现在栈里仅剩下 '二蛋' 和 '日哥' 了。

2.创建一个基于javascript对象的stack类

上面已经实现了一个基于数组的栈,那么为什么还要创建一个基于javascript对象的栈呢?难道是头发太多吗?那肯定不能是因为这个阿!事出皆有因,先看看为什么要这么干。

创建一个stack类最简单的方式就是使用一个数组来存储其元素。但是在现实生活的项目里很常见的一种情况是在处理大量数据的时候,我们同样需要评估如何操作数据才是最高效的。(你看看,你看看,说到底还是为了性能考虑,才有了这个用对象来创建一个栈的想法儿)。

那凭什么说使用数组就不高效了呢?(数组:就是,你说说,凭啥我来处理就不高效了?)

答案就是在使用数组的时候大部分方法的时间复杂度都是O(n)。说到O(n),我已经蒙圈了,这到底是个啥意思?时间复杂度是个啥意思?不清楚不要紧,这一篇文章的目的也不是着重介绍它,就长话短说吧!

O(n)的意思是,有时候我们寻找一个数组中的元素,需要迭代整个数组才能找到我们想要的那个元素,在最坏的情况下那肯定是要迭代数组的所有位置了,这一来不就耗费时间了嘛!这个n就代表数组的长度。试想一下,如果数组里的元素足够多,那需要的时间会更长(这无异于大海捞针阿~就像茫茫人海中你看到这篇文章的几率一样)。

说完了为什么需要创建一个基于javascript对象的栈,那么就让俺们动起手来吧!

2.1先且声明一个Stack类

第一步肯定是新建一个js文件,声明一个Stack类,有了这一步,剩下的路就好走了~

class Stack{
  constructor() {
    this.count = 0
    this.items = {}
  }
}

你看看,你看看,和上面数组的代码一对比 这就发现不同了,多了一个count;items还从一个空数组变成了一个空对象。count就是用来帮助咱们记录栈的大小的,空对象就是用来存放栈中的元素的~

2.2向栈中插入元素

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: '木木'
}

2.3验证一个栈是否为空和它的大小

isEmpty() {
  return this.count  === 0
}

size() {
  return this.count
}

2.4从栈中弹出元素

//从栈中弹出元素
pop() {
  if (this.isEmpty()) {
    return undefined
  }
  this.count--;
  const result = this.items[this.count]
  delete this.items[this.count]
  return result
}

首先,我们需要检验栈是否为空。如果为空,就返回 undefined。如果栈不为空的话,我们会将 count 属性减 1,并保存栈顶的值,以便在删除它之后将它返回。

2.5查看栈顶的值并将栈清空

 // 查看栈顶的值并将栈清空
peek() {
  if (this.isEmpty()) {
    return undefined
  }
  return this.items[this.count - 1]
}
clear() {
  this.items = {}
  this.count = 0
}

2.6创建toString方法

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 方法来像数组一样打印出栈的内容。

如果栈是空的,我们只需返回一个空字符串即可。如果它不是空的,就需要用它底部的第一个元素作为字符串的初始值,然后迭代整个栈的键,一直到栈顶,添加一个逗号(,)以及下一个元素。