Javascript数据结构与算法(三)栈

栈是一种遵循后进先出(ILFO)原则的有序集合,新添加或待删除的元素都保存在栈的同一段,称为栈顶,另一端就叫栈底。现实中很多例子采用了这种数据结构,比如一摞书,叠放的盘子。栈通常用来保存变量、方法调用,当然也可以用于浏览器历史记录。

基于数组的栈

创建类

class StackArray {
  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;
  }

清空栈元素

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

转化为数组

toArray() {
    return this.items;
  }

转化为字符串

toString() {
    return this.items.toString();
  }

基于Javascript对象的stack类

创建stack类

class Stack {
  constructor() {
    this.count = 0;//记录栈的大小,帮助我们进行添加和删除操作
    this.items = {};
  }

添加元素

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

删除元素

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

验证一个栈是否为空和它的大小

isEmpty() {
    return this.count === 0;
  }

  size() {
    return this.count;
  }

查看栈顶的值并将栈清空

peek() {
    if (this.isEmpty()) {
      return undefined;
    }
    return this.items[this.count - 1];
  };
clear() {
    /* while (!this.isEmpty()) {
        this.pop();
      } */
    this.items = {};
    this.count = 0;
  };

转为字符串

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

总结

除了toString方法,我们创建的其他方法的时间复杂度均是O(1),代表我们可以直接扎到目标元素并对其进行操作。

注意

目前为止我们使用的是ES6语法类,由于JavaScript语言的特点,类并不能跟其他语言一样具有私有属性,也就是在外部可以引用私有属性,对其进行赋值等操作。看以下的代码了解一下:

const stack = new Stack();
console.log(Object.getOwnPropertyNames(stack));//["count","items"]
console.log(Object.keys(stack));//["count","items"]
console.log(stack.items);//直接访问私有属性

那么问题来了,我们只想让用户使用我们暴露出来的属性或者方法,我们该怎么实现私有属性呢。笔者看了前辈总结的,直接把它写上来。

保护数据结构内部元素

下划线命名约定

class stack{
    constructor(){
        this._count = 0;
        this._items = {};
    }
};

这只是一种约定,只能依赖于开发人员具备的常识

用E2015的限定作用于Symbol实现类

const _items = Symbol('stackItems');
class Stack{
    constructor(){
        this[_items] = [];
    }
};

实现了假的私有属性,虽然Symbol基本类型不可变,但由于ES2015新增的Object.getOwnPropertySymbols方法仍然能取到所有Symbol属性,而且是数组的形式,但我们操作的是栈,不应该出现这种行为。别急,还有第三个方案

用ES2015的WeakMap实现类

const items = new WeakMap();
class Stack{
    constructor(){
        items.set(this,[]);
    },
    push(element){
        const s = items.get(this);
        s.push(element);
    }
}

这样我们就实现了一个实现了一个完全的私有属性 ,但是代码可读性对于笔者真的太难受了,不知道你们怎么觉得呢。

栈的实践

进制转换算法

function baseConverter(decNumber, base) {
  const remStack = new Stack();
  const digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  let number = decNumber;
  let rem;
  let baseString = '';

  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()) {
    baseString += digits[remStack.pop()];
  }

平衡圆括号算法

function parenthesesChecker(symbols) {
  
const stack = new Stack();
  const opens = '([{';
  const closers = ')]}';
  let balanced = true;
  let index = 0;
  let symbol;
  let top;

  while (index < symbols.length && balanced) {
    symbol = symbols[index];
    if (opens.indexOf(symbol) >= 0) {
      stack.push(symbol);
    } else if (stack.isEmpty()) {
      balanced = false;
    } else {
      top = stack.pop();
      if (!(opens.indexOf(top) === closers.indexOf(symbol))) {
        balanced = false;
      }
    }
    index++;
  }
  return balanced && stack.isEmpty();
}