跳到主要内容

栈(stack)

栈是一种很常见的数据结构。栈可以用来在函数调用的时候存储断点,做递归时要用到栈!

栈是一种 先进后出 的一种数据结构。

这就好比一摞书,最底下的把本书是第一个放进去的,取书时一般从顶部第一个开始取,取到最后才会把最底下(那个第一个放的)书取出来。故 “先进后出”。

栈

栈的一些操作方法:

一个栈一般有如下几个方法:

  • size 返回栈的长度
  • push(data) 入栈(将数据放入栈顶)
  • pop() 出栈(将数据从栈顶拿出)
  • clear() 清空栈
  • peek() 返回栈顶元素
  • isEmpty() 判断是否是空栈栈

当然,第一部肯定是创建一个栈。这里用 数组和 ES6 中的类来实现。在JavaScript数组中也有 push()pop() 两个方法,因此实现起来很容易

  • 数组中每次 push 操作就会把最新的数据放到数组最后一项,数组长度自动变化;

  • 数组中每次 pop 操作就会把数组中的最后一项返回出去,长度变小;
    利用这两个方法就实现了 先进后出 的目的。
    看以下代码:

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

    在栈操作中,我们不希望把 this.items 属性暴露出来。这应该怎么做呢?有两种办法:

  • 利用闭包;

  • 使用最新的(相对于写这篇文档) ES 类特性 —— 类的私有属性;

第一种,利用立即执行函数:

let Stack = (function(){
const items = [];
class Stack{
push(data){
items.push(data);
}
pop(){
return items.pop();
}
isEmpty(){
if(items.length === 0){
return true;
}
return false;
}
peek(){
return items[0];
}
size(){
return items.length;
}
clear(){
items.splice(0);
}
}
return Stack;
})();

上面代码中,立即执行函数的最后不要忘了 return Stack; 因为,立即执行函数执行完后应该把 class 类 —— Stack 暴露出来,让我们去 new 。函数中的 items 就可以通过闭包保存下来。clear 操作不应该 items = [] 这样只是把 items 的地址发生了改变,应使用 splice() 方法把其清零。

使用 ES6 中的 WeakMap() 来实现:

let Stack = (function(){
let items = new WeakMap();
class Stack(){
constructor(){
items.set(this,[]);
}
push(data){
let stack = items.get(this);
stack.push(data);
}
pop(){
let stack = items.get(this);
return stack.pop();
}
// ...... 其它方法
}
return Stack;
})();

使用 WeakMap 也是可以的,需要注意的是:WeakMap 数据类型的键只接受对象,刚好 this 是个不错的选择。不好的一点就是操作时,每次都要通过 get() 方法去取数组,这样有点繁杂。

通过 ES 5 实现:

function Stack(){
var items = [];
this.push = function(data){
items.push(data);
}
this.pop = function(){
return items.pop();
}
this.size = function(){
return items.length;
}
this.isEmpty = function(){
if(items.length === 0){
return true;
}
return false;
}
this.peek = function(){
return items[0];
}
this.clear = function(){
items.splice(0);
}
}

通过 ES6 Symbol 来实现:

let Stack = (function(){
const _items = Symbol();
class Stack{
constructor(){
this[_items] = [];
}
push(data){
this[_items].push(data);
}
pop(){
return this[_items].pop();
}
// .....
}
return Stack;
})();

通过,Symbol 方式来实现,并不是很理想的一种做法,因为我们并没有很好的做到属性的私有。因为在ES6新增的 Object.getOwnPropertySymbols() 方 法能够取到类里面声明的所有Symbols属性:

let objSymbols = Object.getOwnPropertySymbols(stack);
console.log(stack[objSymbols[0]]);
// 甚至改变内部数组的结果:
stack[objSymbols[0]].push(10);

最新的方案

在比 ES7 更新的ES版本中,提出了 类私有属性 的语法:

class Stack{
#items;
constructor(){
this.#items = [];
}
push(data){
this.#items.push(data);
}
// ......
}

只需在属性名前加上 # 字符即可实现,在外部不会被访问到。该语法在最新的 Chrome 浏览器上已实现。

栈的应用

一个简单的进制转换方法:把十进制转为 2-16 进制数

function numParse(number,n){
var ary = [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'];
if(number <= 1){
return number;
}
if(number < n){
return 0;
}
var stack = new Stack();
var b = 0,
a = number;
str = '';
while(1){
b = a % n;
stack.push(ary[b]);
a = Math.floor(a / n); // 注意这里应向下取整
if(a < n){
stack.push(ary[a]);
break;
}
}
while(stack.size()){
str += stack.pop();
}
return str;
}

比如一个十进制数 —— 10 转为二进制数:

  • 10 % 2 === 0 ---> 找到 ary[0] 存入栈;Math.floor(10 / 2) === 5;
  • 5 % 2 === ---> 找到 ary[1] 存入栈;Math.floor(5 / 2) === 2;
  • 2 % 2 === 0 ----> 找到 ary[0] 存入栈;Math.floor(2 / 2) === 1;
  • 此时应作判断: 1 < 2 ? (if(a < n){...}),执行 stack.push(ary[a]); 然后跳出循环
  • 最后通过循环出栈。

一次性添加多个项:

const Stack = (function(){
var items = [];
class Stack{
constructor(...item){
items.push(...item);
}
push(){
// .....
}
}
return Stack;
})();

打印栈元素:

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