數據結構這詞你們都不陌生吧,這但是計算機專業人員的必修課專業之一,若是想成爲專業的開發人員,必須深刻理解這門課程,在這系列文章裏,筆者將使用ES6,讓你們熟悉數據結構這門專業課的內容。javascript
到底什麼是數據結構?數據結構是計算機存儲、組織數據的方式。數據結構是指相互之間存在一種或多種特定關係的數據元素的集合。一般狀況下,精心選擇的數據結構能夠帶來更高的運行或者存儲效率。數據結構每每同高效的檢索算法和索引技術有關(來源百度百科)。更多關於數據結構的介紹,你們能夠先看看筆者的這篇文章《JavaScript 數據結構:什麼是數據結構?》。html
本篇文章筆者將從數據結構最基礎的結構開始介紹——stack(棧),筆者將從如下幾個方面進行介紹:前端
本篇文章閱讀時間預計10分鐘。java
棧是一種高效的數據結構(後進先出(LIFO)原則的有序集合),由於數據只能在棧頂添加或刪除,因此這樣的操做很快,並且容易實現。棧的使用遍及程序語言實現的方方面面。編程語言中的編譯器也會使用到堆棧,計算機內存也會使用堆棧來存儲變量和方法調用,瀏覽器的後退功能。除了計算機方面有諸多棧的應用,現實中也有實際例子,好比咱們從一摞書中拿一本書,吃自助餐從一摞盤子裏拿最上面的盤子,等等:算法
咱們如何使用JS模擬一個簡單的棧呢,首先咱們建立一個stack-array.js文件,聲明一個StackArray類,代碼以下:編程
class StackArray {
constructor() {
this.items = []; // {1}
}
}複製代碼
接下來該怎麼作?咱們須要一個可以存儲堆棧元素的數據結構,咱們可使用數組結構來完成,同時還須要咱們在堆棧中添加和移除數據元素,因爲堆棧後進先出的原則,咱們的添加和刪除方法稍微特別些,Stack這個類的實現包含如下幾個方法:數組
此方法負責向堆棧添加新元素,其中最重要的特色就是:只能將新元素添加到棧頂,即堆棧的末尾,咱們可使用數組的push方法正好符合這個需求,代碼以下:瀏覽器
push(element) {
this.items.push(element);
} 複製代碼
接下來咱們來實現pop()方法,此方法實現刪除棧頂的元素,因爲遵循LIFO原則,刪除的是最後的元素,咱們可使用數組自帶的pop方法,代碼以下:bash
pop() {
return this.items.pop();
} 複製代碼
核心的添加和刪除已經完成,如今咱們來實現相關的輔助方法, 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()方法後的效果:
上一小節咱們基於數組快速實現了棧,咱們清楚數組是有序數組,若是存儲大數據,內容過多的話,長度過大的話,會消耗更多的計算機內存,算法的複雜度就會增長(O(n),後面的文章將會介紹),爲了解決這個問題,咱們使用更原始的方法進行實現。首先咱們在stack.js文件裏聲明stack類,代碼以下:
class Stack {
constructor() {
this.count = 0;
this.items = {};
}
// methods
} 複製代碼
在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;複製代碼
判斷棧是否空,咱們只須要判斷count變量是否爲0便可,代碼以下:
isEmpty() {
return this.count === 0;
}複製代碼
改寫這個方法,咱們首先須要驗證堆棧是否爲空,若是未空返回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)。
更多精彩內容,請微信關注」前端達人」公衆號!