通过数组的学习,我们复习了数组的一些API,这些在JS中是很基础的。我会从这一节开始,将一些函数整理到自己的github库中,以便大家查看及学习。

本次内容主要包括

  • 栈数据结构
  • 向栈中增加元素
  • 删除栈中的元素
  • 如何使用Stack类
  • 十进制转二进制

1. 栈数据结构

栈是一种遵循后进先出(LIFO = last in first out)原则,是一种用来表示元素的集合,主要操作有两种,pushpop

push 是向栈的顶部(尾部)中增加一个元素
pop 是移除栈中最顶端(尾部)的元素

如图所示

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

根据栈遵循的LIFO的原则,我们需要对元素的插入和删除功能做一些限制,我们要为栈声明以下几种方法

  • push(elements):向栈顶添加一个或几个元素到栈顶
  • pop() : 移除栈顶的元素,返回值为移除的元素
  • peek() : 返回栈顶的元素,不对它做任何处理(只是返回这一值)
  • isEmpty(): 判断栈是否为空
  • clear() : 清空栈中的元素
  • size() : 返回栈中元素的个数,类似于数组的length
class Stack {
    constructor(){
        this.items = [] // 我们可以用数组来保存栈中的数据
    }
    push(elem){
        this.items.push(elem) // 通过上面的图,我们可以使用数组中的push方法将元素推到栈顶
    }
    pop(){
       return this.items.pop()
    }
    peek(){
      return this.items[this.items.length - 1]  
    }
    isEmpty(){
        return this.items.length === 0
    }
    clear(){
        this.items = []
    }
    size(){
        return this.items.length 
    }
}

接下来我们使用一下我们封装的Stack

var stack = new Stack()
console.log(stack.isEmpty()) // true
stack.push(9)
stack.push(8)
console.log(stack.size()) // 2
console.log(stack) // Stack { items: [ 9, 8 ] }
stack.push(7)
stack.push(6)
console.log(stack) // Stack { items: [ 9, 8, 7, 6] }

console.log(stack.peek()) // 6

console.log(stack.pop()) // 6

console.log(stack) // Stack { items: [ 9, 8, 7 ] }

stack.clear()
console.log(stack) // Stack { items: [] }

3. 如何使用Stack类

在我们使用数组中的方法是,大部分的API的时间复杂度是O(n)

上述的代码中我们只是在栈顶去推入一个元素。我们现在需要改进一下我们的例子,在Stack的构造函数中增加count的属性,这样可以在栈的任何位置插入元素,下面的代码只拿出来修改的部分

constructor(){
    this.items = [] // 我们可以用数组来保存栈中的数据
    this.count = 0
}
push(elem){
   // this.items.push(elem) // 通过上面的图,我们可以使用数组中的push方法将元素推到栈顶
   this.items[this.count] = elem
   this.count ++
}

我们测试之后

var stack = new Stack()
console.log(stack) // Stack { items: [], count: 0 }
stack.push(9)
stack.push(8)
console.log(stack) // Stack { items: [ 9, 8 ], count: 2 }

同样的思路,我们将其他方法也修改一下,升级后的Stack类如下

class Stack {
    constructor(){
        this.items = [] // 我们可以用数组来保存栈中的数据
        this.count = 0
    }
    push(elem){
        this.items[this.count] = elem
        this.count++
        // this.items.push(elem)
    }
    pop(){
        if (this.isEmpty()) {
            return undefined 
        }
        const item = this.items[this.count-1]
        this.count--
        return item
    //    return this.items.pop()
    }
    peek(){
        if (this.isEmpty()) {
           return undefined 
        }
        return this.items[this.count - 1]  
    }
    isEmpty(){
        return this.count === 0
    }
    clear(){
        this.items = []
        this.count = 0
    }
    size(){
        return this.count
    }
    
}

接下来,我们在Stack类中增加一个toString()的方法,将数组结构转化为字符串

toString(){
    if (this.isEmpty()) {
        return ''
    }
    let objString = `${this.items[0]}`
    for (let i = 1; i < this.items.length; i++) {
        objString = `${objString},${this.items[i]}`
    }
    return objString
}
var stack = new Stack()
stack.push(9)
stack.push(8)
stack.push(7)
stack.push(6)
console.log(stack.toString()) // 9,8,7,6

4. 用栈解决问题

我们在回溯算法问题中,栈可以用来存储访问过的任务或者路径、撤销操作。又或者在前端访问页面时的历史记录就是一个栈,我们这里介绍的是十进制转化为二进制,以及任意进制转换的算法

进制转换.png

通过上述的图我们可以看到十进制转化为二进制的过程

//  进制转换函数
function decimalToBinary(decNumber) {
    const remStack = new Stack()
    let number = decNumber
    let rem;
    let binaryString = ''
    while (number > 0) {
        rem = Math.floor(number % 2)
        remStack.push(rem)
        number = Math.floor(number / 2)
    }
    console.log(remStack,'remStack',remStack.isEmpty())
    while (!remStack.isEmpty()) {
        binaryString += remStack.pop().toString()
    }
    return binaryString
}

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

接下来我们可以对这个函数进行改造一下,让它可以实现任意进制转换

function baseConvert(decNumber,base) {
    const remStack = new Stack()
    const digits = '0123456789ABCDEFGHIGKLMNOPQRSTUVWXYZ'
    let number = decNumber
    let rem ;
    let binaryString = ''

    if (!(base >= 2 && base <= 36)) {
        return ''
    }
    while (number > 0) {
        rem = Math.floor(number % base)
        remStack.push(rem)
        number = Math.floor(number / base)
    }

    while (!remStack.isEmpty()) {
        binaryString += digits[remStack.pop()]
    }
    
    return binaryString
}
console.log(baseConvert(100345,3)) // 12002122111

下一篇文章是关于队列的,队列则和我们生活中的排队是一样的,遵循先进先出,后进后出的原则。这也是和栈最重要的区别