堆栈的基础知识

  • 什么是栈
  • 栈的操做
  • 实现一个栈类
  • 栈的应用
  • 栈属性私有化

什么是栈

image
堆栈是只能从列表的一端(称为顶部)访问的元素列表. 一个常见的,现实生活中的例子就是自助餐厅的托盘堆. 托盘老是从顶部取走, 当托盘在清洗后放回堆栈时,它们被放置在堆栈的顶部. 堆栈被称为后进先出(LIFO)数据结构.git

因为堆栈的后进先出特性, 当前不在栈顶都不能被访问的. 假如想访问栈底的元素必须把上面的元素移除(托盘取走)github

栈是一种类队列的数据结构, 能够用解决许多计算问题. 同时栈是很是高效的一种数据结构,因数据新增、删除都只能从栈顶完成, 而且容易以及快速实现.数组

栈操做

因为栈后入先出的特性,任何非栈顶元素不能被访问. 要想访问栈底的元素必须把上面的元素所有出栈才能访问到.安全

下面栈的经常使用的两个操做,元素入栈、出栈. 使用push操做将元素添加到堆栈中, 使用pop操做从堆栈中取出元素. 下图所示
image数据结构

堆栈另外一个常见操做是查看堆栈顶部的元素. pop操做访问堆栈的顶部元素,可是它将永久地从堆栈中删除该元素. peek操做返回存储在堆栈顶部的值,而不从堆栈中删除它. 除pop()push()peek() 主要方法外, 栈应该包含其它的如判断栈是否是空 isEmpty()、获取栈的大小 size()、清空栈 clear().
下面经过代码来实现一个栈.测试

栈的实现

经过数组来实现一个栈, 代码以下:this

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

    push(ele) {
        this.items.push(ele)
    }

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

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

    isEmpty() {
        return !!this.items.length
    }
    size() {
        return this.items.length
    }
    clear() {
        this.items = []
    }
    print(){
        return this.items.toString()
    }
}

下面经过一个案例来测试下定义栈类.spa

const stack = new Stack()
console.log(stack.isEmpty()) //=> true
stack.push(1) //=> 入栈 1
stack.push(2) //=> 入栈 2
stack.push(3) //=> 入栈 3

stack.pop()   //=> 出栈 3
stack.pop()   //=> 出栈 2

stack.push(4) //=> 入栈 4
console.log(stack.isEmpty()) //=> false
console.log(stack.size());   //=> 2
stack.clear();
console.log(stack.isEmpty()); //=> true

上面代码只是简单操做,这里就不一一介绍了.code

栈的应用

下面经过几个实际的例子来增长咱们对栈的理解:blog

进制转换

把一个数字从一个基数转为另一个基数. 给定一个数字 n, 要把它转为基数b, 下面大概的步骤:

  1. n % b 取余推入栈中
  2. n / b 取模后的值替换 n 值
  3. 重复步骤一、2,直到 n = 0
  4. 经过出栈操做获取转换后字符串

进制转换,经过栈很是容易实现,下面大概实现方式

function baseConverter(num, base) {
    const stack = new Stack()
    const digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    if(!(base >= 2 && base <= 32)){
       return '';
    }
    do {
        stack.push(num % base)
        num = Math.floor(num / base)
    } while (num > 0)
    let converted = ''
    while (!stack.isEmpty()) {
        converted += digits[stack.pop()]
    }
    return converted;
}

下面咱们来测试下: 把一个数字转为二进制、八进制

let num = 32
let base = 2
let converted = baseConverter(num, base)
console.log(`${num} converted to base ${base} is ${converted}`)

num = 125
base = 8
converted = baseConverter(num, base)
console.log(`${num} converted to base ${base} is ${converted}`)

运行输出后结果为:

32 converted to base 2 is 100000
125 converted to base 8 is 175

回文

什么是回文:

回文是先后拼写相同的单词、短语或数字。例如,“dad”是一个回文;“racecar”是一个回文; 1001是一个数字回文;

咱们可使用堆栈来肯定给定的字符串是不是回文. 拿到

  • 把字符串中的每一个字符压入到栈中
  • 经过 pop() 方法生成新的字符串
  • 拿原字符串与新生成的字符串比较,相同则为“回文”

image

下面经过代码实现判断回文的方法:

function isPalindrome(word) {
    const stack = new Stack()
    for (let i = 0; i < word.length; i++) {
        stack.push(word[i])
    }
    let rword = ''
    while (!stack.isEmpty()) {
        rword += stack.pop()
    }
    return word === rword
}
console.log(isPalindrome('racecar')) // true
console.log(isPalindrome('hello'))   // false

注意
实现判断回文方式不少, 这里只是列举经过栈如何实现

用堆栈模拟递归过程

演示如何用堆栈实现递归,就以阶乘为例.

5!= 5 * 4 * 3 * 2 * 1 = 120

先用递归方法实现, 具体以下:

function factorial(n) {
    if (n === 1) {
        return 1
    } else {
        return n * factorial(n-1)
    }
}

👇使用堆栈模拟递归过程:

function fact(n) {
    const stack = new Stack()
    while (n > 1) {
        stack.push(n--)
    }

    let product = 1
    while (!stack.isEmpty()) {
        product *= stack.pop()
    }
    return product
}

栈属性私有化

私有化实际就是隐藏内部细节,只对外提供操做方法,这样能保证代码安全性. 下面经常使用方式:

  • 命名约定方式
class Stack {
    constructor() {
        this._items = []
    }
}

这种方式只是一种约定并不能起到保护做用,并且只能依赖使用咱们代码的开发者所具有的常识.

  • 借助ES模块做用域和 Symbol 属性
const _items = Symbol('stackItems')
class Stack {
    constructor() {
        this[_items] = []
    }
}
//...

}
这种方式其实建立一个假的私有属性, ES2015新增的Object.getOwnPropertySymbols方法能取到类里面声明的全部 Symbol 属性, 下面是破坏Stack类的例子

const stack = new Stack()\
stack.push(1)
stack.push(2)

let objectSymbols = Object.getOwnPropertySymbols(stack)
console.log(objectSymbols.length) // 1
console.log(objectSymbols) // [ Symbol(stackItems) ]
console.log(stack[objectSymbols[0]].push(1))
console.log(stack.print()) // 1,2,1
  • 利用 WeakMap 来实现是私有化
const items = new WeakMap()
class Stack {
    constructor() {
        items.set(this, [])
    }
    push(ele) {
        const s = items.get(this)
        s.push(ele)
    }
    pop() {
        const s = items.get(this)
        const r = s.pop()
        return r
    }
}

items 在 Stack 类里是真正的私有属性. 采用这种方式带来问题代码可读性不强,并且扩展该类时没法继承私有属性.

  • ECMAScript 类属性提案

虽然在 TypeScrpit 中存在经过 private 修饰符来定义私有变量, 可是这只能编译时才有效,在编译后仍是能够被外部访问.

在ES草案中提供以下方式进行声明私有化属性

class Stack {
    #items = [];
    push(ele) {
        // 访问
        this.#items.push(ele)
    }
}

详细

相关文章
相关标签/搜索