学习JavaScript数据结构与算法(栈)

292 阅读4分钟

1.栈数据结构

栈是一种遵从后进先出(LIFO)原则的有序集合。新添加或待删除的元素都保存在栈的同一端,称作栈顶,另一端就叫栈底。在栈里,新元素都靠近栈顶,旧元素都靠近栈底。

现实生活中典型的栈的例子就是一摞书或者餐厅里叠放的盘子。

1.1 创建一个基于数组的栈

创建一个类来表示栈。

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

1.2 向栈添加元素

实现方法push,添加一个(或几个)新元素到栈顶。

push(element) {
    this.items.push(element);
}

1.3 从栈移除元素

实现方法pop,移除栈顶的元素,同时返回被移除的元素。

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

1.4 查看栈顶元素

实现方法peek,返回栈顶的元素,不对栈做任何修改。

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

1.5 检查栈是否为空

实现方法isEmpty,如果栈里没有任何元素就返回true,否则返回false。

isEmpty() {
    return this.items.length === 0;
}

1.6 清空栈元素

实现方法clear,移除栈里的所有元素。

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

1.7 返回栈元素个数

实现方法size,返回栈里的元素个数。该方法和数组的length属性很类似。

size() {
    return this.items.length;
}

1.8 完整的Stack类

class Stack {
    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 = [];
    }
    size() {
        return this.items.length;
    }
}

1.9 使用Stack类

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

const stack = new Stack();
console.log(stack.isEmpty()); // 输出为true

往栈里添加元素5,8

stack.push(5);
stack.push(8);

调用peek方法

console.log(stack.peek()); // 8

再添加一个元素

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

最后,再添加一个元素

stack.push(15);
console.log(stack.size()); // 4

然后,调用两次pop方法从栈里移除两个元素。

stack.pop();
stack.pop();
console.log(stack.size()); // 2

2.创建一个基于JavaScript对象的Stack类

创建一个Stack类最简单的方式是使用一个数组来存储其元素。在处理大量数据的时候,我们需要评估如何操作数据最高效。在使用数组时,大部分方法的时间复杂度是O(n),另外,数组是元素的一个有序集合,为了保证元素排列有序,它会占用更多的内存空间。

为了解决上述问题,我们可以使用一个JavaScript对象来存储所有的栈元素,保证它们的顺序并且遵循LIFO原则。

首先声明一个Stack类。

class Stack {
    constructor() {
        this.count = 0;
        this.items = {};
    }
}

2.1 向栈中插入元素

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

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

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

2.3 从栈中弹出元素

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

2.4 查看栈顶的值并将栈清空

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

2.5 创建toString方法

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;
}

3.用栈解决问题

从十进制到二进制

十进制转化成二进制,可以将十进制数除以2(二进制是满二进一)并对商取整,直到结果是0为止。

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);
    }
    while(!remStack.isEmpty()) {
        binaryString += remStack.pop().toString();
    }
    return binaryString;
}

console.log(decimalToBinary(233));
console.log(decimalToBinary(10));
console.log(decimalToBinary(1000));

进制转换算法

我们可以修改上述算法,使之能把十进制转换成基数为2~36的任意进制。

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()];
    }
    return baseString;
}

console.log(baseConverter(100345,2)); 
console.log(baseConverter(100345,8)); 
console.log(baseConverter(100345,16)); 
console.log(baseConverter(100345,35)); 

5.小结

本节学习了栈这一数据结构的相关知识。使用数组和JavaScript对象实现了栈,还涉及了如何用push和pop往栈里添加和移除元素。

6.经典题目

平衡圆括号

给定一个只包括 '(',')','{','}','[',']'的字符串,判断字符串是否有效。 有效字符串需满足: 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 注意空字符串可被认为是有效字符串。

思路:当遇到右边的括号时,入栈,当遇到左边的括号时,判断栈顶是否与左边的括号匹配,如果不是的话,直接返回false,如果是进行下一次判断,循环结束,如果栈为空,则返回true,如果不为空,则返回false。

function(s) {
    const item = [];
    const opens = '([{';
    const closers = ')]}';
    let index = 0;
    let symbol;
    let top;

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