js数据结构与算法 - 栈

avatar
前端工程师 @豌豆公主

前言

数据结构是一个很宽泛的概念,在整个编程语言中,都是很平常的存在。再js中,我们集合数据有对象,有数组,字典,Map... 他们都各有优缺点。如果需要一个满足一定规则的数据结构,就要我们手动去构建,那今天我们就来构建一个栈

什么是栈

栈是一种数据结构,它符合一个规则,后进先出(LIFO)

创建一个基于数组的栈

我们先创建一个类来标识栈

class Stack {
    constructor{
        this.items = [] // 我们用一个数据结构来保存栈里的数据
    }
}

push()

添加一个 或几个新的元素到栈顶

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

pop()

移除栈顶元素,同时返回被删除的元素

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

peek()

返回栈顶的元素,不对栈数据做任何修改

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

size()

返回栈里的元素个数

size(){
    return this.items.length
}

isEmpty()

如果栈里没有元素,就返回true,否则返回falase

isEmpty() {
   return this.size === 0
}

clear()

移除栈里所有的元素

clear() {
    this.items = []
}

是不是会觉得好简单,确实,数组本身就有长度属性还有poppush, 天然的栈数据结构,但是在处理大量数据的时候,我们需要迭代整个数组,直到找到那个元素,并且数组是有序的集合,为了维护元素的排列顺序,也会占用一定的存储空间。所以,我们尝试用对象结构,来实现栈结构

使用对象来实现栈结构,最主要的是需要手动维护对象的长度

创建一个基于对象的栈

我们先创建一个类来标识栈

class Stack {
    constructor{
        this.items = {} // 我们用一个数据结构来保存栈里的数据
        this.count = 0 // 我们得自己维护长度
    }
}

push()

添加一个 或几个新的元素到栈顶(添加完记得手动count+1)

    push(el) {
        this.items[this.count] = el
        this.count++
}

pop()

移除栈顶元素,同时返回被删除的元素(对象没有pop,需要手动delete栈顶元素,并且在删除之前,需要count - 1。因为count是从0开始的)

    pop() {
        if (this.isEmpty()) {
            return undefined
        }
        this.count--
        const result = this.items[this.count]
        delete this.items[this.count]
        return result
}

peek()

返回栈顶的元素,不对栈数据做任何修改(count是从0开始,和length一样,需要 -1)

peek() {
  if (this.isEmpty()) {
        return undefined
    }
    return this.items[this.count - 1]
}

size()

返回栈里的元素个数

size(){
    return this.count
}

isEmpty()

如果栈里没有元素,就返回true,否则返回falase

isEmpty() {
   return this.size() === 0
}

clear()

移除栈里所有的元素

clear() {
    this.items = {}
    this.count = 0
}

toString 方法

在数组版中,我们不用关心toString方法的实现,因为数组结构可以直接使用数组提供的toString方法,对于对象版本,我们需要实现一个toString方法,来像数组一样打印栈中的内容

toString(){
    if (this.isEmpty()) {
        return ''
     }
    let objString = `${this.items[0]}`
    for (let k = 1; k < this.count; k++) {
        objString = `${objString}, this.items[k]`
    }
    return objString
}

用栈解决实际问题 从十进制转为二进制

10 / 2 = 5 => rem = 0 将余数放入栈中,0101

5 / 2 = 2 => rem = 1 将余数从栈中弹出 1010

2 / 2 = 1 => rem = 0 入栈终止条件是商是0

1 / 2 = 0 => rem = 1 出栈终止条件是 栈为空

  function decimalToBinary(decNumber) {
    let binaryString = '';
    const remStack = new Stack();
    let number = decNumber;

    while (number > 0) {
      let rem = Math.floor(number % 2);
      remStack.push(rem);
      number = Math.floor(number / 2);
    }

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

    return binaryString;
  }
  console.log('decimalToBinary', decimalToBinary(10)); // 1010

十进制转任意(2~36进制)

function baseConverter(decNumber, base) {
    let binaryString = '';
    let digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    const remStack = new Stack();
    let number = decNumber;
    if (!(base >= 2 && base <= 36)) {
      return '';
    }
    while (number > 0) {
      let rem = Math.floor(number % base);
      remStack.push(rem);
      number = Math.floor(number / base);
    }

    while (!remStack.isEmpty()) {
      binaryString += digits[remStack.pop()]; // 此处直接去digits取值,所以不用做toString操作
    }

    return binaryString;
  }
  console.log('baseConverter', baseConverter(10, 2)); // 1010
  console.log('baseConverter', baseConverter(200, 11)); // 172
  console.log('baseConverter', baseConverter(100034, 20)); // CA1E
  console.log('baseConverter', baseConverter(10000, 30)); // B3A
  console.log('baseConverter', baseConverter(100345, 35)); // 2BW0