JS数据结构学习:栈

william

栈的定义

什么是栈?栈是一种遵循后进先出原则的有序集合,新添加的或者待删除的元素都保存在栈的同一端,称为栈顶,另一端称为栈底,在栈里,新元素靠近栈顶,旧元素靠近栈底,用个图来看大概这样式的:

用一个更形象的例子来说明:上网的时候,每点击一个超链接,浏览器都会打开一个新的页面,并且压入到一个访问历史栈中,你可以不断的点击打开新的页面,但总是可以通过回退重新访问以前的页面,从浏览器的访问历史栈中弹出历史网页地址,从栈顶弹出,总是最新的最先弹出

栈的创建

首先创建一个类用来表示栈,接着声明一个数组用来保存栈里的元素:

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

总结

这篇文章主要对栈做了简单介绍,动手实践了栈的实现,以及用栈实现了一个简单进制转换的功能。如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞。

阅读 4.1k

前端进阶之路
love and share

love and share

2.6k 声望
824 粉丝
0 条评论

love and share

2.6k 声望
824 粉丝
文章目录
宣传栏