js数据结构之栈

1.栈数据结构

栈是一种遵从后进先出LIFO)原则的有序集合。新添加或待删除的元素都保存在栈的同一端,称作栈顶,另一端就叫栈底。在栈里,新元素都靠近栈顶,旧元素都接近栈底。
在现实生活中也能发现许多栈的例子。例如,下图中的一摞书。
在这里插入图片描述

栈也被用在编程语言的编译器和内存中保存变量、方法调用等,浏览器历史记录。

1.1 创建一个基于数组的栈

我们需要一种数据结构来保存栈里的元素。可以选择数组。数组可以在任何位置添加或删除元素。由于栈遵循LIFO原则,需要对元素的插入和删除功能进行限制。也要为栈声明一些方法。

创建一个类来表示栈。

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

    // 添加新元素到栈顶
    push(element) {
        return this.items.push(element)
    }

    // 移除栈顶的元素,同时返回被移除的元素
    pop() {
        return this.items.pop()
    }

    // 返回栈顶的元素,不对栈做任何修改(该方法不会移除栈顶的元素,仅仅返回它)
    peek () {
        return this.items[this.items.length - 1]
    }

    // 如果栈里没有任何元素就返回true,否则返回false
    isEmpty () {
        return this.items.length === 0
    }

    // 清空栈里的所有元素
    clear() {
        return this.items = []
    }

    // 返回栈里的元素个数。该方法和数组的length属性很类似
    size() {
        return this.items.length
    }
}

1.2 使用Stack 类

首先需要初始化类,然后验证下栈是否为空。

const stack = new Stack()
console.log(stack.isEmpty())  // true

接下来,往栈里添加一些元素

stack.push(5)
stack.push(8)
console.log(stack.peek()) // 输出 8

再添加一个元素

stack.push(11)
console.log(stack.size()) // 输出 3
console.log(stack.isEmpty) // 输出false

我们往栈里添加了11,如果调用了size方法,输出3,因为栈里有三个元素(5, 8,11),如果我们调用isEmpty方法,会看到false。最后,我们再添加一个元素。

stack.push(15)

下图描述了栈的当前状态
在这里插入图片描述
结果,连续两次调用pop方法,从栈里移除两个元素

stack.pop()
stack.pop()
console.log(stack.size()) // 输出 2

在两次调用pop方法前,我们的栈里有四个元素。调用两次后,现在栈里仅剩下5和8了。如下图所示:
在这里插入图片描述

1.3 使用JavaScript对象创建Stack类

使用数组时,大部分的时间复杂度是O(n)。O(n)的意思是,我们需要迭代整个数组直到找到的那个元素。另外数组时元素的一个有序集合,为了保证元素的排列有序,会占用更多空间。
创建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
    }

    // 返回栈顶的元素,不对栈做任何修改(该方法不会移除栈顶的元素,仅仅返回它)
    peek () {
        if (this.isEmpty()) {
            return undefined
        }
        return this.items[this.count - 1]
    }

    // 如果栈里没有任何元素就返回true,否则返回false
    isEmpty () {
        return this.count === 0
    }

    // 清空栈里的所有元素
    clear () {
        this.items = {}
        this.count = 0
    }

    // 返回栈里的元素个数。该方法和数组的length属性很类似
    size () {
        return this.count
    }

    // 方法类似于数组的toString()
    toString() {
        if (this.isEmpty()) {
            return ''
        }
        let str = ''
        for (let i = 0; i < this.count; i++) {
            str += this.items[i]
        }
        return str
    }
}

2. 保护数据结构内部元素

对于Stack类来说,要确保元素只会被添加到栈顶。而不是栈底或其他位置。

2.1 下划线命名

下划线命名约定就是在属性名称之前加上一个(_)。不过这种方式只是一种约定,并不能保护数据,而且只能依赖于使用我们开发者具备的常识。

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

2.2 用symbol实现类

es2015新增了一种叫做Symbol的基本类型,它是不可变的,可以用作对象的属性。

const _items = Symbol('stackItems'); // {1} 
class Stack { 
 constructor () { 
 this[_items] = []; // {2} 
 } 
 // 栈的方法
}

这种方法创建了一个假的私有属性,因为 ES2015 新增的 Object.getOwnPropertySymbols 方法能够取到类里面声明的所有 Symbols 属性。

const 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

从以上代码可以看到,访问 stack[objectSymbols[0]]是可以得到_items 的。并且,
_items 属性是一个数组,可以进行任意的数组操作,比如从中间删除或添加元素(使用对象进
行存储也是一样的)。但我们操作的是栈,不应该出现这种行为

2.3 用weakMap实现类

有一种数据类型可以确保属性是私有的,这就是 WeakMap。

const items = new WeakMap(); // {1} 
class Stack { 
 constructor () { 
 items.set(this, []); // {2} 
 } 
 push(element){ 
 const s = items.get(this); // {3} 
 s.push(element); 
 } 
 pop(){ 
 const s = items.get(this); 
 const r = s.pop(); 
 return r; 
 } 
 // 其他方法
}

items 在 Stack 类里是真正的私有属性。采用这种方法,代码的可读性
不强,而且在扩展该类时无法继承私有属性。

3. 用栈解决问题

编写算法,使之能把十进制转换成基数为 2~36 的任意进制。

function baseConverter(decNumber, base) {
    const remStack = new Stack();
    const digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; // {6}
    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()]; // {7}
    }
    return baseString;
}

使用算法,输出结果如下

console.log(baseConverter(100345, 2)); // 11000011111111001
console.log(baseConverter(100345, 8)); // 303771
console.log(baseConverter(100345, 10)); // 100345
console.log(baseConverter(100345, 16)); // 187F9
console.log(baseConverter(100345, 35)); // 2BW0
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值