作者: 草帽lufei | 来源:发表于2019-07-03 21:14 被阅读0次

栈数据结构

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

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

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

创建栈

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

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}) .

相关文章

  • Java实现栈

    数组栈:压栈、出栈、返回栈顶元素 链式栈:压栈、出栈、返回栈顶元素

  • 数据结构之 栈

    栈结构 链式栈 一.栈结构体 1构建空栈 2栈置空 3判断栈空 4获取栈顶 5入栈 6出栈 7便利栈 二.链式栈 ...

  • 栈和队列

    1、栈 栈是一种先进先出的数据结构。栈顶进栈,栈顶出栈。 数据结构 栈的初始化 进栈 出栈 栈的最小值 2、队列 ...

  • 递归累加数组

    入栈 5入栈 4入栈 3入栈 2入栈 1出栈 [1 0]出栈 [2 1 0]出栈 [3 2 1 0]出栈 [4 3...

  • 栈的逻辑结构和存储结构

    main()进栈s(1)进栈s(0)进栈 s(0)出栈s(1)出栈main()出栈 顺序栈 一个数组 + 指向栈顶...

  • 单调栈 2020-06-12(未经允许,禁止转载)

    1.单调栈 指栈内元素保持单调性的栈结构,分为单调增栈(栈底到栈顶元素递增)和单调减栈(栈底到栈顶元素递减) 2....

  • 链栈的操作

    链栈的定义 链栈的操作 初始化 判断栈空 入栈 出栈

  • 函数调用栈平衡

    栈平衡 栈平衡:函数调用前后的栈顶指针指向的位置不变 内平栈 外平栈 内平栈: 指的是在函数调用返回之前使栈保持...

  • 栈的简单Java实现

    栈栈的特点是先进后出,出栈、入栈都是在栈顶操作。

  • 汇编学习-入栈和出栈

    栈有两个基本的操作:入栈和出栈。入栈就是将一个新的元素放到栈顶,出栈就是从栈顶取出一个元素。栈顶的元素总是最后入栈...

网友评论

    本文标题:

    本文链接:https://www.haomeiwen.com/subject/selhhctx.html