栈数据结构

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

生活中栈的例子: 一摞书, 堆放的盘子.

栈也被用在编程语言的编译器和内存中保存变量,方法调用等.

创建栈

创建一个类来表示栈. 先声明这个类:

function Stack() {
  // 各种属性和方法的声明
}

使用数组数据结构来保存栈里的元素

let items = []

声明栈的方法

push(element(s)): 添加一个(或几个)新元素到栈顶

pop(): 移除栈顶的元素, 同时返回被移除的元素

peek(): 返回栈顶的元素, 不对栈做任何修改 (这个方法不会移除栈顶元素, 仅仅返回它)

isEmpty(): 如果战力没有任何元素就返回 true, 否则返回 false

clear(): 移除栈里所有元素

size(): 返回栈里元素个数. 和数组 length 属性类似

向栈添加元素

push 方法向栈里添加新元素, 只添加元素到栈顶, 也就是栈的末尾

this.push = function (element) {
  items.push(element)
}

从栈移除元素

pop 方法用来移除栈里的元素. 栈遵从 LIFO 原则, 因此移出的是最后添加进去的元素

  this.pop = function () {
    return item.pop()
  }

只能用 push 和 pop 方法添加和删除栈中的元素, 栈自然就遵从了 LIFO 原则

查看栈顶元素

peek 方法返回栈顶元素

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

检查栈是否为空

isEmpty 如果栈为空返回 true, 否则返回 false. 因为栈内部使用数组保存元素, 所以能简单地返回栈的长度

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

清空栈元素

clear 方法用来移除栈里所有元素. 也可以多次调用 pop 方法, 把数组中的元素全部移除, 这样也能实现 clear 方法

  this.clear = function () {
    items = []
  }

打印栈元素

print 方法把栈里的所有元素都输出到控制台

  this.print = function () {
    console.log(items.toString())
  }

使用 Stack 类

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)
    // console.log(items.toString()) 
  }

}

let stack = new Stack()
console.log(stack.isEmpty()) // true

stack.push(5)
stack.push(8)

// 8 是栈里最后添加的一个元素
console.log(stack.peek()) // 8

stack.push(11)
console.log(stack.size())  // 3
console.log(stack.isEmpty()) // false

stack.push(15)

// 调用两次 pop 移除两个元素
stack.pop()
stack.pop()
console.log(stack.size()) // 2
stack.print() // [5,8]

ECMAScript 和 Stack 类

用 ES6 语法声明 Stack 类

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

在类的其他函数里使用 this.nameofVariable 就可以引用这个变量

1. 用 ES6 的限定作用域 Symbol 实现类

ES6 新增了一种叫做 Symbol 的基本类型, 它是不可变的, 可以用作对象的属性.

let _items= Symbol()

class Stack {
  constructor () {
    this[_items] = []
  }
  push (element) {
    this[_items].push(element)
  }

  pop () {
    return this[_items].pop()
  }

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

  isEmpty () {
    return this[_items].length == 0
  }

  size () {
    return this[_items].length
  }

  clear () {
    this[_items] = []
  }

  print () {
    console.log(this[_items])
    // console.log(this[_items].toString()) 
  }
}

上面代码中声明了 Symbol 类型的变量 _items , 在类的 constructor 函数中初始化它的值. 要访问 _items, 只需把所有的 this.items 都换成 this[_items]

这种方法创建了一个假的私有属性, 因为 ES6 新增的 Object.getOwnPropertySymbols 方法能够取到类里面声明的所有 Symbols 属性. 下面是一个破坏 Stack 类的例子

let stack = new Stack()
stack.push(5)
stack.push(8)
let objectSymbols = Object.getOwnPropertySymbols(stack)
console.log(objectSymbols.length) // 1
console.log(objectSymbols) // [ Symbol() ]
console.log(objectSymbols[0]) // Symbol()
stack[objectSymbols[0]].push(1) 
stack.print() // [ 5, 8, 1 ]
2.用ES6的WeakMap实现类

WeakMap 数据类型可以确保属性私有, WeakMap 可以存储键值对,其中键是对象,值可以是任意数据类型.

用 WeakMap 来存储 items 变量, Stack 类如下

const items = new WeakMap() // {1}

class Stack{
  constructor() {
    items.set(this, []) // {2}
  }
  push(element) {
    let s = items.get(this) // {3}
    s.push(element)
  }
  pop() {
    let s = items.get(this)
    let r = s.pop()
    return r
  }
  // 其他方法
}

行 {1}, 声明一个 WeakMap 类型的变量 items.

行 {2}, 在 constructor 中, 以 this (Stack 类自己的引用) 为键, 把代表栈的数组存入 items.

行 {3}, 从 WeakMap 中取值, 即以 this 为键 (行{2}设置的)从 items 中取值.

现在 items 在 Stack 类里是真正的私有属性了, 还有一件事情要做, items 现在仍然是在 Stack 类以外声明的, 因此谁都可以改动它. 我们要用一个闭包 (外层函数) 把 Stack 类包起来, 这样就只能在这个函数里访问 WeakMap:

let Stack = (function () {
  const items = new WeakMap() // {1}

  class Stack{
    constructor() {
      items.set(this, []) // {2}
    }
    push(element) {
      let s = items.get(this) // {3}
      s.push(element)
    }
    pop() {
      let s = items.get(this)
      let r = s.pop()
      return r
    }
    // 其他方法
  }
  return Stack // {5}
})()

当 Stack 函数里面的构造函数被调用时, 会返回 Stack 类的一个实例 (行{5})

3.3 用栈解决问题
从十进制到二进制

把十进制转化成二进制, 可以将十进制数字和2整除(二进制是满二进一), 知道结果为0为止. 把十进制的数字10转化成二进制的数字,过程如下

算法描述


function divideBy2(decNumber) {
  let remStack = new Stack()
  let rem = null
  let binaryString = ''

  while (decNumber > 0) { // {1}
    rem = Math.floor(decNumber % 2)  // {2}
    remStack.push(rem)  // {3}
    decNumber = Math.floor(decNumber / 2)  // {4}
  }

  while (!remStack.isEmpty()) {  // {5}
    binaryString += remStack.pop().toString()
  }

  return binaryString
}

console.log(divideBy2(10)) // 1010

当结果满足和2做整除的条件时 (行{1}) , 会获得当前结果和2的余数,放到栈里(行{2}, {3}). 让结果和2做整除(行 {4}). 注意: JavaScript 有数字类型, 但是它不会区分究竟是整数还是浮点数. 因此, 要使用 Math.floor 函数让除法的操作仅返回整数部分. 最后, 用 pop 方法把栈中的元素都移除, 把出栈的元素变成连接字符串 (行{5})

进制转化算法

很容易修改之前的算法, 使之能把十进制转换成任何进制.除了让十进制数字和2整除转成二进制数,还可以传入其他任意进制的基数为参数, 算法如下:

function baseConverter(decNumber, base) {
  let remStack = new Stack()
  let rem = null
  let baseString = ''
  let digits = '0123456789ABCDEF' // {6}

  while (decNumber > 0) {
    rem = Math.floor(decNumber % base)
    remStack.push(rem)
    decNumber = Math.floor(decNumber / base)
  }

  while (!remStack.isEmpty()) {
    baseString += digits[remStack.pop()] // {7}
  }

  return baseString
}

console.log(baseConverter(10, 2)) // 1010
console.log(baseConverter(11, 2)) // 1011

只需要修改一个地方. 在将十进制转换成二进制时, 余数是0和1;在将十进制转成八进制时,余数是0和7之间的数; 但是将十进制转换成16进制时, 余数是0到9之间的数字加上 A, B, C, D, E 和 F (对应10, 11, 12, 13, 14 和 15). 因此, 对栈中的数字做个转化即可(行 {6} 和 行 {7}) .

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 栈数据结构 栈是一种遵从后进先出(LIFO)原则的有序集合。新增加的或删除的元素都保存在栈的同一端,称作栈顶,另外...
    kim_jin阅读 5,566评论 0 0
  • 如需转载, 请咨询作者, 并且注明出处.有任何问题, 可以关注我的微博: coderwhy, 或者添加我的微信: ...
    coderwhy阅读 19,993评论 5 49
  • 栈结构 栈也是一种非常常见的数据结构, 并且在程序中的应用非常广泛. 一. 认识栈结构 我们先来简单认识一下栈结构...
    小码哥教育520it阅读 5,902评论 0 1
  • 栈数据结构 栈是一种遵从后进先出(LIFO)原则的有序集合。新添加的或待删除的元素都保存在栈的同一端,称作栈顶,另...
    会飞小超人阅读 5,330评论 1 4
  • 栈 栈的英文单词是Stack,它代表一种特殊的线性表,这种线性表只能在固定一端(通常认为是线性表的尾端)进行插入,...
    Jack921阅读 5,446评论 0 5

友情链接更多精彩内容