【數據結構基礎】棧簡介(使用ES6)

數據結構這詞你們都不陌生吧,這但是計算機專業人員的必修課專業之一,若是想成爲專業的開發人員,必須深刻理解這門課程,在這系列文章裏,筆者將使用ES6,讓你們熟悉數據結構這門專業課的內容。javascript

到底什麼是數據結構?數據結構是計算機存儲、組織數據的方式。數據結構是指相互之間存在一種或多種特定關係的數據元素的集合。一般狀況下,精心選擇的數據結構能夠帶來更高的運行或者存儲效率。數據結構每每同高效的檢索算法和索引技術有關(來源百度百科)。更多關於數據結構的介紹,你們能夠先看看筆者的這篇文章《JavaScript 數據結構:什麼是數據結構?》html

本篇文章筆者將從數據結構最基礎的結構開始介紹——stack(棧),筆者將從如下幾個方面進行介紹:前端

  • 什麼是棧?
  • 如何用JS簡單的實現棧?
  • 建立更高效的基於對象Stack類
  • 實際應用舉例
  • 練習題

本篇文章閱讀時間預計10分鐘。java

什麼是棧?

棧是一種高效的數據結構(後進先出(LIFO)原則的有序集合),由於數據只能在棧頂添加或刪除,因此這樣的操做很快,並且容易實現。棧的使用遍及程序語言實現的方方面面。編程語言中的編譯器也會使用到堆棧,計算機內存也會使用堆棧來存儲變量和方法調用,瀏覽器的後退功能。除了計算機方面有諸多棧的應用,現實中也有實際例子,好比咱們從一摞書中拿一本書,吃自助餐從一摞盤子裏拿最上面的盤子,等等:算法

如何用JS簡單的實現棧?

咱們如何使用JS模擬一個簡單的棧呢,首先咱們建立一個stack-array.js文件,聲明一個StackArray類,代碼以下:編程

class StackArray {
    constructor() {
        this.items = []; // {1}
    }
}複製代碼

接下來該怎麼作?咱們須要一個可以存儲堆棧元素的數據結構,咱們可使用數組結構來完成,同時還須要咱們在堆棧中添加和移除數據元素,因爲堆棧後進先出的原則,咱們的添加和刪除方法稍微特別些,Stack這個類的實現包含如下幾個方法:數組

  1. push(element(s)): 此方法將新添加的元素添加至堆棧的頂部
  2. pop():此方法刪除棧頂的元素,同時返回已刪除的元素
  3. peek(): 返回堆棧的頂部元素
  4. isEmpty(): 判斷堆棧是否爲空,若是爲空,返回True, 不然返回False。
  5. clear(): 清空堆棧全部的元素。
  6. size(): 此方法返回堆棧元素的數量,相似數組的長度。
  7. toArray(): 以數組的形式返回堆棧的元素。
  8. toString():以字符串的形式輸出堆棧內容。

push()

此方法負責向堆棧添加新元素,其中最重要的特色就是:只能將新元素添加到棧頂,即堆棧的末尾,咱們可使用數組的push方法正好符合這個需求,代碼以下:瀏覽器

push(element) {
    this.items.push(element);
} 複製代碼

pop()

接下來咱們來實現pop()方法,此方法實現刪除棧頂的元素,因爲遵循LIFO原則,刪除的是最後的元素,咱們可使用數組自帶的pop方法,代碼以下:bash

pop() {
    return this.items.pop();
} 複製代碼

peek()、isEmpty()、size()、clear()

核心的添加和刪除已經完成,如今咱們來實現相關的輔助方法, peek()方法讓咱們獲取堆棧最後的一個元素,實現代碼以下:微信

peek() {
    return this.items[this.items.length - 1];
} 複製代碼

isEmpty()的方法也十分簡單,判斷堆棧數組的長度是否爲0便可,代碼以下:

isEmpty() {
    return this.items.length === 0;
} 複製代碼

size()方法更簡單,使用數組的length方法便可,代碼以下

size() {
    return this.items.length;
} 複製代碼

最後實現最簡單的清空方法clear(),將堆棧變量從新置空賦值便可:

clear() {
    this.items = [];
}複製代碼

最終完成的stack-array.js代碼以下:

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

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

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

    toArray() {
        return this.items;
    }

    toString() {
        return this.items.toString();
    }
}複製代碼

接下來咱們建立一個stackdemo.js的文件,引入咱們的stack-array.js文件,咱們一塊兒來實踐下如何使用咱們建立好的StackArray類,代碼以下:

import StackArray from 'stack-array.js';
const stack = new StackArray();

console.log('stack.isEmpty() => ', stack.isEmpty()); // outputs true

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

console.log('stack after push 5 and 8 => ', stack.toString());

console.log('stack.peek() => ', stack.peek()); // outputs 8

stack.push(11);

console.log('stack.size() after push 11 => ', stack.size()); // outputs 3
console.log('stack.isEmpty() => ', stack.isEmpty()); // outputs false

stack.push(15);

stack.pop();
stack.pop();

console.log('stack.size() after push 15 and pop twice => ', stack.size()); // outputs 2複製代碼

咱們能夠新建一個stackdemo.html,引入stackdemo.js(<script type="module" src="stackdemo.js"></script>),打開stackdemo.html便可,堆棧的運行示意以下圖所示:

執行push()方法後的效果:

執行pop()方法後的效果:

建立更高效的基於對象Stack類

上一小節咱們基於數組快速實現了棧,咱們清楚數組是有序數組,若是存儲大數據,內容過多的話,長度過大的話,會消耗更多的計算機內存,算法的複雜度就會增長(O(n),後面的文章將會介紹),爲了解決這個問題,咱們使用更原始的方法進行實現。首先咱們在stack.js文件裏聲明stack類,代碼以下:

class Stack {
    constructor() {
        this.count = 0;
        this.items = {};
    }
    // methods
} 複製代碼

push()

在JS中,對象是一組鍵值對,咱們能夠將使用count變量做爲items對象的鍵,元素是其值,添完新元素後,count變量加1,代碼實現以下:

push(element) {
    this.items[this.count] = element;
    this.count++;
}複製代碼

好比咱們能夠向空的棧裏添加新元素5和8,代碼以下:

const stack = new Stack();
stack.push(5);
stack.push(8);複製代碼

若是輸出Stack對象的items和count,效果以下:

items = {
    0: 5,
    1: 8
};
count = 2;複製代碼

isEmpty()

判斷棧是否空,咱們只須要判斷count變量是否爲0便可,代碼以下:

isEmpty() {
    return this.count === 0;
}複製代碼

pop()

改寫這個方法,咱們首先須要驗證堆棧是否爲空,若是未空返回undefined,若是不爲空,咱們將變量count的值減1,同時刪除對應的屬性,代碼以下:

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

接下來咱們改寫其餘的方法,完整代碼以下:

export default 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];
  }

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

  size() {
    return this.count;
  }

  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;
  }
}複製代碼

雖然咱們類完成了,你們是否是以爲有點問題,因爲咱們建立的類的屬性對於任何開發人員都是公開的,咱們但願只能在棧頂添加元素,不但願在其餘位置添加元素,可是咱們在Stack類中聲明的items和count屬性不受保護,這是JS的規則問題,難道咱們沒有辦法改變了嗎?答案是能夠,咱們能夠ES6加入的新類型Symbol數據類型做爲對象的屬性具備私有性的特色(關於Symbol數據類型,筆者的這篇文章有過介紹《【ES6基礎】Symbol介紹:獨一無二的值》),改寫基於stack-array.js版本的代碼,代碼以下:

const _items = Symbol('stackItems');

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

    size() {
        return this[_items].length;
    }

    clear() {
        this[_items] = [];
    }

    print() {
        console.log(this.toString());
    }

    toString() {
        return this[_items].toString();
    }
}

const stack = new Stack();
const 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複製代碼

實際應用舉例

堆棧在實際的問題中有着各類各樣的運用,好比咱們會常用各類軟件的撤銷操做功能,尤爲是Java和C#編程語言使用堆棧來變量存儲和方法調用,而且能夠拋出堆棧溢出異常,尤爲是在使用遞歸算法時。接下來,咱們親自動手實現個10進制轉2進制的功能。

咱們已經熟悉十進制。 然而,二進制表示在計算機科學中很是重要,由於計算機中的全部內容都由二進制數字(0和1)表示。 若是沒有在十進制和二進制數之間來回轉換的能力,與計算機進行通訊將會十分困難。要將10進制轉換成2進制,咱們須要將要轉換數字除以2,再將結果除以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)); // 11101001
console.log(decimalToBinary(10)); // 1010
console.log(decimalToBinary(1000)); // 1111101000」複製代碼

練習題

剛纔咱們實踐了十進制轉二進制,爲了讓其更有通用性,由於在實際應用中,不只僅有二進制的轉換需求,好比還有8進制、16進制等,如今筆者要給你們留做業了,實現函數baseConverter(decNumber, base),第一參數是要轉換的10進制數,第二個參數是須要轉換的進制,讓其具有10進制數任意轉換成2~36進制的需求,歡迎你們在留言區貼代碼。

小節

本篇文章,咱們瞭解了什麼是數據結構,並深刻學習了堆棧這個數據結構,以及如何用JS代碼實現堆棧,並講解了不一樣的實現方式,同時瞭解棧在計算機領域的應用,並一塊兒實踐了一個十進制數轉二進制的練習,接下來本系列文章,筆者將帶着你們一塊兒深刻學習和堆棧相似的隊列結構,惟一不一樣的就是先進先出(FIFO)。

更多精彩內容,請微信關注」前端達人」公衆號!

相關文章
相關標籤/搜索