注:本文章內容源自《學習javascript數據結構與算法(第3版)》,算是本身的一個學習筆記,不喜勿噴。javascript
導語:咱們知道,能夠在數組的任意位置上進行刪除或者添加元素。然而,強大的數組在有些時候並不能知足我們人類的需求阿!你好比說有時候就須要一種能在添加或刪除元素時進行更多控制的數據結構。因此就有了今天我們要學的這個數據結構——棧。
java
天天據說哪一個哪一個大佬是全棧大佬,誰誰誰進階全棧了,可想而知棧這個字對於廣大程序員來講仍是很是熟悉的。那麼棧究竟是個什麼玩意兒呢?看看用普通話是怎麼解釋的吧!程序員
棧是一種聽從後進先出(LIFO)原則的有序集合。新添加或待刪除的元素都保存在棧的同一端,稱做棧頂,另外一端就叫棧底。在棧裏,新元素都靠近棧頂,舊元素都接近棧底。算法
首先建立一個類來表示棧:數組
class Stack {
constructor() {
this.items = [];
}
}複製代碼
咱們須要一種數據結構來保存棧裏的元素,俺們這裏選擇的是數組。數組容許咱們在任何位置添加或刪除元素(數組就是這麼硬核!)。因爲棧遵循的是LIFO原則,須要對元素的插入和刪除功能進行限制,爲了實現這些限制,接下來要爲棧聲明一些方法。bash
咱們要實現的第一個方法就是push。該方法負責往棧裏添加元素,可是得注意一點:該方法只添加元素到棧頂,也就是棧的末尾。因此這裏固然是用數組的push方法啦~數據結構
Push(element) {
this.items.push(element)
}複製代碼
第二個,我們來實現pop方法。該方法主要用來移除棧裏的元素。棧遵循LIFO的原則,因此移出去的是最後添加進去的元素。所以,可使用數組的pop方法~性能
pop() {
return this.items.pop();
}複製代碼
若是想知道棧裏最後添加的元素是什麼,能夠用 peek 方法。該方法將返回棧頂的元素。學習
peek() {
return this.items[this.items.length - 1]
}複製代碼
這裏要實現的方法是 isEmpty,若是棧爲空的話將返回 true,不然就返回 false。ui
isEmpty() {
return this.items.length == 0
}複製代碼
相似於數組的 length 屬性,咱們也能實現棧的 length。對於集合,最好用 size 代替length。由於棧的內部使用數組保存元素,因此能簡單地返回棧的長度。
size() {
return this.items.length
}複製代碼
最後,咱們來實現 clear 方法。clear 方法用來移除棧裏全部的元素,把棧清空。
clear() {
this.items = []
}複製代碼
固然啦,你不怕麻煩,也能夠用pop方法來清空哦~
通過上面一系列的操做,我們也算是建立了一個簡單的棧啦~那麼讓我們來使用一下吧,看看好使很差使~(不是有那麼句話嘛,是騾子是馬,牽出來溜溜)
const stack = new Stack()
console.log(stack.isEmpty()) // true 這個時候裏面還沒東西呢 確定是空啦複製代碼
接下來,往裏面添加一些東西吧,否則顯得太冷清啦~
stack.push('二蛋');
stack.push('日哥');複製代碼
若是調用 peek 方法,將輸出 '日哥',由於它是往棧裏添加的最後一個元素。
console.log(stack.peek()); // 日哥複製代碼
再添加一個元素。
stack.push('木木');
console.log(stack.size()); // 3
console.log(stack.isEmpty()) // false複製代碼
咱們往棧裏添加了 '木木'。若是調用 size 方法,輸出爲 3,由於棧裏有三個元素('二蛋'、'日哥' 和 '木木')。若是咱們調用 isEmpty 方法,會看到輸出了 false(由於棧裏有三個元素,不是空棧)。最後,咱們再添加一個元素。
stack.push('唐唐');複製代碼
而後,調用兩次 pop 方法從棧裏移除兩個元素。
stack.pop()
stack.pop()
console.log(stack.size()) // 2複製代碼
在兩次調用 pop 方法前,咱們的棧裏有四個元素。調用兩次後,如今棧裏僅剩下 '二蛋' 和 '日哥' 了。
上面已經實現了一個基於數組的棧,那麼爲何還要建立一個基於javascript對象的棧呢?難道是頭髮太多嗎?那確定不能是由於這個阿!事出皆有因,先看看爲何要這麼幹。
建立一個stack類最簡單的方式就是使用一個數組來存儲其元素。可是在現實生活的項目裏很常見的一種狀況是在處理大量數據的時候,咱們一樣須要評估如何操做數據纔是最高效的。(你看看,你看看,說到底仍是爲了性能考慮,纔有了這個用對象來建立一個棧的想法兒)。
那憑什麼說使用數組就不高效了呢?(數組:就是,你說說,憑啥我來處理就不高效了?)
答案就是在使用數組的時候大部分方法的時間複雜度都是O(n)。說到O(n),我已經蒙圈了,這究竟是個啥意思?時間複雜度是個啥意思?不清楚沒關係,這一篇文章的目的也不是着重介紹它,就長話短說吧!
O(n)的意思是,有時候咱們尋找一個數組中的元素,須要迭代整個數組才能找到咱們想要的那個元素,在最壞的狀況下那確定是要迭代數組的全部位置了,這一來不就耗費時間了嘛!這個n就表明數組的長度。試想一下,若是數組裏的元素足夠多,那須要的時間會更長(這無異於大海撈針阿~就像茫茫人海中你看到這篇文章的概率同樣)。
說完了爲何須要建立一個基於javascript對象的棧,那麼就讓俺們動起手來吧!
第一步確定是新建一個js文件,聲明一個Stack類,有了這一步,剩下的路就好走了~
class Stack{
constructor() {
this.count = 0
this.items = {}
}
}複製代碼
你看看,你看看,和上面數組的代碼一對比 這就發現不一樣了,多了一個count;items還從一個空數組變成了一個空對象。count就是用來幫助我們記錄棧的大小的,空對象就是用來存放棧中的元素的~
push(element) {
this.items[this.count] = element
this.count++
}複製代碼
在js中對象是一系列鍵值對的集合。要向棧中添加元素,咱們將使用 count 變量做爲 items 對象的鍵名,插入的元素則是它的值。在向棧插入元素後,咱們遞增 count 變量。
能夠往裏面插入兩個元素試試看。
const stack = new Stack();
stack.push('二蛋');
stack.push('木木');複製代碼
通過上面的操做之後,在內部items和count屬性以下所示
count: 0,
items: {
0: '二蛋',
1: '木木'
}複製代碼
isEmpty() {
return this.count === 0
}
size() {
return this.count
}複製代碼
//從棧中彈出元素
pop() {
if (this.isEmpty()) {
return undefined
}
this.count--;
const result = this.items[this.count]
delete this.items[this.count]
return result
}複製代碼
首先,咱們須要檢驗棧是否爲空。若是爲空,就返回 undefined。若是棧不爲空的話,咱們會將 count 屬性減 1,並保存棧頂的值,以便在刪除它以後將它返回。
// 查看棧頂的值並將棧清空
peek() {
if (this.isEmpty()) {
return undefined
}
return this.items[this.count - 1]
}
clear() {
this.items = {}
this.count = 0
}複製代碼
toString() {
if (this.isEmpty()) {
return undefined
}
let objString = `${this.items[0]}`
for (let i = 1; i < this.count; i++) {
objString = `${objString}, ${this.items[i]}`
}
return objString
}複製代碼
在數組版本中,咱們不須要關心 toString 方法的實現,由於數據結構能夠直接使用數組已經提供的 toString 方法。對於使用對象的版本,咱們將建立一個 toString 方法來像數組同樣打印出棧的內容。
若是棧是空的,咱們只需返回一個空字符串便可。若是它不是空的,就須要用它底部的第一個元素做爲字符串的初始值,而後迭代整個棧的鍵,一直到棧頂,添加一個逗號(,)以及下一個元素。