JavaScript的棧

棧數(shù)據(jù)結(jié)構(gòu)

棧是一種遵從后進(jìn)先出(LIFO)原則的有序集合曙强。新增加的或刪除的元素都保存在棧的同一端救湖,稱作棧頂拣播,另外的一端就是棧底晾咪。在棧里面,新元素都靠近棧頂贮配,舊元素都接近棧底谍倦。

創(chuàng)建棧

我們將創(chuàng)建一個(gè)類來標(biāo)識棧。讓我們從基礎(chǔ)開始泪勒,先聲明這個(gè)類:

function Stack(){
  //各種屬性和方法的聲明
}

首先昼蛀,我們需要一種數(shù)據(jù)結(jié)構(gòu)來保存棧的元素宴猾,可以選擇數(shù)組:

let items = [];

接下來,我們要為我們的棧聲明一些方法叼旋。

  • push(element(s)):添加一個(gè)(或幾個(gè))新元素到棧頂仇哆。
  • pop():移除棧頂?shù)脑兀瑫r(shí)返回被移除的元素夫植。
  • peek():返回棧頂?shù)脑囟锾蓿粚W鋈魏蔚男薷模ㄟ@個(gè)方法不會(huì)移除棧頂?shù)脑兀瑑H僅是返回它)
  • isEmpty():如果棧里沒有任何元素都返回true偷崩,否則返回false辟拷。
  • clear():移除棧里的所有元素
  • size():返回棧里的元素個(gè)數(shù),這個(gè)方法和數(shù)組的length屬性很類似阐斜。
向棧添加元素

我們要先實(shí)現(xiàn)第一個(gè)方法push衫冻,這個(gè)方法負(fù)責(zé)往棧里添加新元素,有一點(diǎn)很重要:該方法只添加元素到棧頂谒出,也就是棧的末尾隅俘。push方法可以這樣寫:

this.push = function (element){
  item.push(element);
}

因?yàn)槲覀兪褂昧藬?shù)組還是保存棧里的元素,所以可以用上一章學(xué)到了push方法來實(shí)現(xiàn)笤喳。

從棧移除元素

接下來为居,我們來實(shí)現(xiàn)pop方法。這個(gè)方法只要用來移除棧中的元素杀狡。棧遵從LIFO原則蒙畴,因此移出的是最后添加進(jìn)去的元素。因此呜象,我們可以用上一章講數(shù)組時(shí)介紹的pop方法膳凝。棧的pop方法可以這樣寫:

this.pop = function(){
  return items.pop();
}

只能用pushpop方法添加和刪除棧中的元素,這樣一來恭陡,我們的棧自然就遵從了LIFO原則蹬音。

查看棧頂元素

現(xiàn)在我們的類實(shí)現(xiàn)一些額外的輔助方法,如果想知道棧里最后添加的元素是什么休玩,可以用peek方法著淆,這個(gè)方法將返回棧頂?shù)脑兀?/p>

this.peek = function(){
  return items[items.length - 1];
}
棧中的元素結(jié)構(gòu)

如上圖中,有一個(gè)包含三個(gè)元素的棧拴疤,因此內(nèi)部數(shù)組的長度就是3.數(shù)組中最后一項(xiàng)的位數(shù)是2永部,length-1 (3-1)正是2

檢測棧是否為空

下一步我們來實(shí)現(xiàn)isEmpty呐矾,如果棧為空的話講返回true扬舒,否則就返回false

this.isEmpty = function (){
  return items.length == 0;
}

使用isEmpty方法,我們能簡單的判斷內(nèi)部數(shù)組的長度是否為0.
那么下面我們來看一下size方法:

this.size = function (){
  return items.length;
}
清空和打印棧元素

最后凫佛,我們來實(shí)現(xiàn)clear方法讲坎。clear方法用來移除棧中的所有元素孕惜,把棧清空。實(shí)現(xiàn)最簡單的方式是:

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

另外我們可以多調(diào)用pop方法晨炕,把數(shù)組中的元素全部移除衫画,這樣也能實(shí)現(xiàn)clear方法。
完成了瓮栗!棧已經(jīng)實(shí)現(xiàn)削罩,通過一個(gè)例子來放松一下:為了檢查棧里的內(nèi)容,我們開實(shí)現(xiàn)一個(gè)輔助方法费奸,交print弥激。他會(huì)把棧里的元素輸出到控制臺:

this.print = function (){
  console.log(items.toString());
}
使用Stack類

在深入了解棧的應(yīng)用前,我們先來學(xué)習(xí)如何使用Stack類愿阐。
首先微服,我們需要初始化Stack類。然后缨历,驗(yàn)證一下棧是否為空(輸出true以蕴,因?yàn)檫€沒有往棧里添加元素)

let stack = new Stack();
console.log(stack.isEmpty()); // 輸出為true

// 向棧中添加元素
stack.push(5);
stack.push(8);

//查看棧頂元素
console.log(stack.peek()); //輸出 8

//再添加一個(gè)元素
stack.push(11);
console.log(stack.size()); // 輸出 3
console.log(stack.isEmpty()); //輸出false

//再添加一個(gè)元素
stack.push(15);

現(xiàn)在我們直觀的看一下我們對棧的操作,以及棧的當(dāng)前狀態(tài):

具體操作步驟

然后調(diào)用兩次pop函數(shù)來移除2個(gè)元素:

stack.pop();
stack.pop();
console.log(stack.size()); // 輸出2
stack.print(); // 輸出[5,8]

現(xiàn)在我們直觀的看一下我們對棧的操作辛孵,以及棧的當(dāng)前狀態(tài):

具體操作步驟

ECMAScript 6 和stack類

我們花時(shí)間分析一下代碼丛肮,看看是都能用ECMAScript6(ES6)的新功能來改進(jìn)。
我們創(chuàng)建一個(gè)可以當(dāng)做類來使用Stack函數(shù)魄缚。JavaScript函數(shù)都有構(gòu)造函數(shù)宝与,可以用來模擬類的行為。我們生命了一個(gè)私有的items變量冶匹,它只能被Stack函數(shù)/類訪問习劫。然而,這個(gè)方法為每個(gè)類的實(shí)例都創(chuàng)建一個(gè)items變量的副本徙硅。因此榜聂,如果要?jiǎng)?chuàng)建多個(gè)Stack實(shí)例搞疗,它就不太適合了嗓蘑。

用ES6 語法聲明Stack類

我們看一下下面的代碼:

class Stack {
  constructor(){
    this.item = []; //{1}
  }
  push(element){
    this.items.push(element);
  }
  //其他方法
}

我們只是用ES6的簡化語法那Stack函數(shù)轉(zhuǎn)化成Stack類。這種方法不能像其他語言(Java匿乃,C++桩皿,C#)一樣直接在類里面聲明變量,只能在類的構(gòu)造函數(shù)constructor里聲明在類的其他函數(shù)里用this.nameofVariable就可任意引用這個(gè)變量幢炸。
盡管代碼看起來更簡潔泄隔,更漂亮,變量items卻是公共的宛徊。ES6的類是基于原型的佛嬉。雖然基本原型的類比基本函數(shù)的類更節(jié)省內(nèi)存逻澳,也更適合創(chuàng)造多個(gè)實(shí)例,卻不能夠聲明私有屬性(變量)或方法暖呕。而且斜做,在這個(gè)情況下,我們希望Stack類的用戶只能訪問暴露給類的方法湾揽。否則瓤逼,就有可能從棧的中間移除元素,這不是我們希望看到的库物。
看一下ES6的限定作用域Symbol實(shí)現(xiàn)類
1.用ES6的限定作用域Symbol實(shí)現(xiàn)類
ES6增加了一種叫做symbol的基本類型霸旗,它是不可變的,可以用作對象的屬性戚揭,看看怎么用它來在Stack類中聲明items屬性:

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

在上面的代碼中诱告,我們聲明了Symbol類型的變量_items(行1),在類的constructor函數(shù)中初始化它的值(行2)毫目。要訪問_items,只需把所有的this.items都換成this[_items]蔬啡。
這種方法創(chuàng)建了一個(gè)私有屬性,因?yàn)?code>ES6新增的Object.getOwnPropertySymbol方法能夠取到類里面聲明的所有Symbol屬性镀虐,下面是一個(gè)破壞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()
console.log(objectSymbols[0].push(1));
stack.print(); // 輸出:5,8,1

上面的代碼我們可以看到箱蟆,訪問stack[objectSymbols[0]]是可以得到_items.并且,_items屬性是一個(gè)數(shù)組刮便,可以進(jìn)行任何的數(shù)組操作空猜,比如從中間刪除或是添加元素。我們操作的是棧恨旱,不應(yīng)該出現(xiàn)這樣的操作辈毯。
2.ES6是的weakMap
有一種數(shù)據(jù)類型可以確保屬性是私有的,這就是WeakMap搜贤。我們會(huì)在以后深入探討Map這種數(shù)據(jù)結(jié)構(gòu)谆沃,現(xiàn)在只需要知道weakMap可以用來存儲鍵值對,其中鍵是對象仪芒,值可以是任意數(shù)據(jù)類型唁影。
如果用weakMap來存儲items變量,stack就是這樣的:

const items = new weakMap(); //聲明一個(gè)weakMap類型的變量items
class Stack{
  constructor(){
    items.set(this, []); //以this為鍵掂名,把代表?xiàng)5臄?shù)組存入items
  }
  push(element){
    let s = items.get(this); //從weekMap中取出值
    s.push(element)
  }
  pop(){
    let s = items.get(this);
    let r = s.pop();
    return r;
  }
}

在上面的代碼中据沈,itemsstack類是真正的私有屬性,但是還有一件事要去做饺蔑。items現(xiàn)在仍然是在Stack類以外聲明的锌介,因此誰都可以來修改Stack這個(gè)類,這個(gè)時(shí)候我們需要一個(gè)閉包(外層函數(shù))把Stack類包起來沒這樣就只能在這個(gè)函數(shù)里訪問weakMap().

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

用棧解決的問題

從十進(jìn)制到二進(jìn)制

在現(xiàn)實(shí)生活中,我們主要使用十進(jìn)制孔祸,但是在科學(xué)計(jì)算中隆敢,二進(jìn)制是十分重要的,因?yàn)橛?jì)算機(jī)中的所有的內(nèi)容都是用二進(jìn)制來表現(xiàn)的(0和1)崔慧,沒有十進(jìn)制和二進(jìn)制的相互轉(zhuǎn)換能力筑公,與計(jì)算機(jī)的交流就會(huì)十分的困難。要把十進(jìn)制轉(zhuǎn)換成二進(jìn)制尊浪,我們可以將改十進(jìn)制數(shù)字和2整除(二進(jìn)制就是滿二進(jìn)一)匣屡,知道結(jié)果為0為止,現(xiàn)在我們舉個(gè)栗子拇涤,把十進(jìn)制數(shù)字10轉(zhuǎn)換成二進(jìn)制的數(shù)組捣作,大概的過程就是這樣的:

十進(jìn)制變二進(jìn)制的操作步驟

大學(xué)的計(jì)算機(jī)課程一般都會(huì)先教這個(gè)進(jìn)制轉(zhuǎn)換。下面對應(yīng)的算法描述:

function divideby2(decNumber){
  let remStack = new Stack(),rem,binaryString = '';
  while (decNumber > 0){
    rem = Math.floor(decNumber % 2);
    remStack.push(rem);
    decNumber = Math.floor(decNumber / 2);
  }
  while(!remStack.isEmpty()){
    binaryString  += remStack.pop().toString();
  }
  return binaryString ;
}

從上面的算法中鹅士,我們很容易進(jìn)行修改券躁,讓十進(jìn)制可以轉(zhuǎn)換成任意進(jìn)制,除了讓十進(jìn)制和2整除轉(zhuǎn)換為二進(jìn)制掉盅,還可以傳入其他任意進(jìn)制的基數(shù)為參數(shù)也拜,就像下面的算法是這樣的:

function baseConverter(decNumber , base){
  let remStack = new Stack(),
                 rem,
                 binaryString = '';
                 digits = '0123456789ABCDEF';
  while (decNumber > 0){
    rem = Math.floor(decNumber % base);
    remStack.push(rem);
    decNumber = Math.floor(decNumber / base);
  }
  while(!remStack.isEmpty()){
    binaryString  += remStack.pop().toString();
  }
  return binaryString ;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市趾痘,隨后出現(xiàn)的幾起案子慢哈,更是在濱河造成了極大的恐慌,老刑警劉巖永票,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卵贱,死亡現(xiàn)場離奇詭異,居然都是意外死亡侣集,警方通過查閱死者的電腦和手機(jī)键俱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來世分,“玉大人编振,你說我怎么就攤上這事〕袈瘢” “怎么了踪央?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長斋泄。 經(jīng)常有香客問我杯瞻,道長镐牺,這世上最難降的妖魔是什么炫掐? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮睬涧,結(jié)果婚禮上募胃,老公的妹妹穿的比我還像新娘旗唁。我一直安慰自己,他們只是感情好痹束,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布检疫。 她就那樣靜靜地躺著,像睡著了一般祷嘶。 火紅的嫁衣襯著肌膚如雪屎媳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天论巍,我揣著相機(jī)與錄音烛谊,去河邊找鬼。 笑死嘉汰,一個(gè)胖子當(dāng)著我的面吹牛丹禀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鞋怀,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼双泪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了密似?” 一聲冷哼從身側(cè)響起焙矛,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎残腌,沒想到半個(gè)月后薄扁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡废累,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年邓梅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片邑滨。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡日缨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出掖看,到底是詐尸還是另有隱情匣距,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布哎壳,位于F島的核電站毅待,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏归榕。R本人自食惡果不足惜尸红,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧外里,春花似錦怎爵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至墩莫,卻和暖如春芙委,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狂秦。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工题山, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人故痊。 一個(gè)月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓顶瞳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親愕秫。 傳聞我的和親對象是個(gè)殘疾皇子慨菱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容