javascript的數據結構--實現一個棧

棧:不論是在哪一個語言,c/c++,java,python,還有我們的javascript都有這個數據結構,不少從別的行業轉開發的朋友都會通過這個學習過程。javascript

棧:一種聽從後進先出(LIFO)原則的有序集合。新添加的或待刪除的元素都保存在棧的
同一端,稱做棧頂,另外一端就叫棧底。在棧裏,新元素都靠近棧頂,舊元素都接近棧底。java

在現實生活中也能發現不少棧的例子。例如,下圖裏的一摞書或者餐廳裏堆放的盤子。
棧也被用在編程語言的編譯器和內存中保存變量、方法調用等。python

建立一個棧 (直接用ES6的class來寫吧)

先熟悉一下棧的一些方法:c++

push(element(s)) :添加一個(或幾個)新元素到棧頂。
pop() :移除棧頂的元素,同時返回被移除的元素。
peek() :返回棧頂的元素,不對棧作任何修改(這個方法不會移除棧頂的元素,僅僅返回它)。
isEmpty() :若是棧裏沒有任何元素就返回 true ,不然返回 false 。
clear() :移除棧裏的全部元素。
size() :返回棧裏的元素個數。這個方法和數組的 length 屬性很相似。
class Stack {
    constructor () {
        this.items = []; //{1}
    }
    push(element) {
        this.items.push(element);
    }
    pop() {
        this.items.pop();
    }
    peek() {
        return items[items.length-1];
    }
    isEmpty() {
        return items.length == 0;
    }
    size() {
        return items.length;
    }
    clear() {
        items = [];
    }
    print() {
        console.log(items);
    }
}

儘管代碼看起來更簡潔、更漂亮,變量 items 倒是公共的。ES6的類是基於原型的。雖然基
於原型的類比基於函數的類更節省內存,也更適合建立多個實例,卻不可以聲明私有屬性(變量)
或方法。並且,在這種狀況下,咱們但願 Stack 類的用戶只能訪問暴露給類的方法。不然,就有
可能從棧的中間移除元素(由於咱們用數組來存儲其值),這不是咱們但願看到的。git

1. 用ES6的限定做用域 Symbol 實現類算法

ES6新增了一種叫做 Symbol 的基本類型,它是不可變的,能夠用做對象的屬性。看看怎麼用
它來在 Stack 類中聲明 items 屬性:編程

let _items = Symbol(); //{1}
class Stack {
    constructor () {
        this[_items] = []; //{2}
    }
    //Stack方法
}

在上面的代碼中,咱們聲明瞭 Symbol 類型的變量 _items (行 {1} ),在類的 constructor 函
數中初始化它的值(行 {2} )。要訪問 _items ,只需把全部的 this.items 都換成 this[_items] 。
這種方法建立了一個假的私有屬性,由於ES6新增的 Object.getOwnPropertySymbols 方
法可以取到類裏面聲明的全部 Symbols 屬性。下面是一個破壞 Stack 類的例子:數組

let 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. 用ES6的 WeakMap 實現類閉包

有一種數據類型能夠確保屬性是私有的,這就是 WeakMap 。咱們會在第7章深刻探討 Map 這
種數據結構,如今只須要知道 WeakMap 能夠存儲鍵值對,其中鍵是對象,值能夠是任意數據類型。

若是用 WeakMap 來存儲 items 變量, Stack 類就是這樣的:

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

行 {1} ,聲明一個 WeakMap 類型的變量 items 。
行 {2} ,在 constructor 中,以 this ( Stack 類本身的引用)爲鍵,把表明棧的數組存入 items。
行 {3} ,從 WeakMap 中取出值,即以 this 爲鍵(行 {2} 設置的)從 items 中取值。
如今咱們知道, items 在 Stack 類裏是真正的私有屬性了,但還有一件事要作。 items 如今
仍然是在 Stack 類之外聲明的,所以誰均可以改動它。咱們要用一個閉包(外層函數)把 Stack
類包起來,這樣就只能在這個函數裏訪問 WeakMap :

let Stack = (function () {
    const items = new WeakMap();
    class Stack {
        constructor () {
            items.set(this, []);
        }
        //其餘方法
    }
    return Stack; //{4}
})();

當 Stack 函數裏的構造函數被調用時,會返回 Stack 類的一個實例(行 {4} )。

一個例子(把十進制的數轉換成任意進制的數)

function baseConverter(decNumber, base){
    var remStack = new Stack(),
        rem,
        baseString = '',
        digits = '0123456789ABCDEF'; //{5}
    while (decNumber > 0){
        rem = Math.floor(decNumber % base);
        remStack.push(rem);
        decNumber = Math.floor(decNumber / base);
    }
    while (!remStack.isEmpty()){
        baseString += digits[remStack.pop()]; //{6}
    }
    return baseString;
}

咱們只須要改變一個地方。在將十進制轉成二進制時,餘數是0或1;在將十進制轉成八進制
時,餘數是0到7之間的數;可是將十進制轉成16進制時,餘數是0到9之間的數字加上A、B、C、
D、E和F(對應十、十一、十二、1三、14和15)。所以,咱們須要對棧中的數字作個轉化才能夠(行
{5} 和行 {6} )。

參考:《Javascript數據結構與算法》

相關文章
相關標籤/搜索