學(xué)習(xí)JavaScript數(shù)據(jù)結(jié)構(gòu)

一、數(shù)組Array

典型應(yīng)用之斐波那契數(shù)列:前2個(gè)項(xiàng)都為1,從第三項(xiàng)開始,每一項(xiàng)都等于前2項(xiàng)之和,求斐波那契數(shù)列的前20項(xiàng)。

第一反應(yīng)是,用2個(gè)變量記錄前兩項(xiàng),每次循環(huán)時(shí)更新這兩個(gè)變量并打印出來:


以上做法的問題是這2個(gè)變量僅作為臨時(shí)計(jì)算實(shí)用,結(jié)果的20個(gè)數(shù)并沒能保存下來

用數(shù)組的方式:目的是要把這20個(gè)數(shù)利用數(shù)組arr保存下來,并去掉numPrev1 和 numPrev2這兩個(gè)臨時(shí)的變量,利用數(shù)組下標(biāo)取值的方式計(jì)算出每一項(xiàng):


數(shù)組的便利之處就在于【能只用一個(gè)變量存多個(gè)數(shù)據(jù),不用創(chuàng)建很多變量來存每一項(xiàng)】,【利用數(shù)組下標(biāo)能很方便地取各個(gè)位置上的值】,對(duì)保存和處理那些【在順序上有規(guī)律的數(shù)據(jù)】來說非常實(shí)用。

二、棧Stack


后進(jìn)先出,操作方向僅在棧頂

棧是一個(gè)遵循后進(jìn)先出原則(LIFO)的有序集合,我們可以在數(shù)組的?任意位置?上操作元素,但棧限制了操作位置:只能在棧頂?進(jìn)行存取操作。棧被用來在編程語言中保存變量、方法的調(diào)用順序;保存訪問過的路徑,比如瀏覽器保存歷史記錄以回退到上一路徑等。

1.創(chuàng)建一個(gè)基于數(shù)組的棧

2.創(chuàng)建一個(gè)基于對(duì)象的棧

將key為0的作為棧底,key越大越靠近棧頂

3.使用WeakMap保護(hù)私有屬性

使用以上兩種方法時(shí),Stack的實(shí)例仍能訪問并修改_items屬性,用下劃線命名只是一種約定,依賴于開發(fā)者的常識(shí),我們希望能保護(hù)內(nèi)部的元素,將_items作為私有屬性,限制實(shí)例對(duì)它的訪問:


WeakMap?的設(shè)計(jì)目的在于,有時(shí)我們想在某個(gè)對(duì)象上面存放一些數(shù)據(jù),但是這會(huì)形成對(duì)于這個(gè)對(duì)象的引用,只要引用存在,垃圾回收?機(jī)制就不會(huì)釋放對(duì)象占用的內(nèi)存。 WeakMap 就是為了解決這個(gè)問題而誕生的,它的鍵名所引用的對(duì)象都是弱引用 ,只要所引用的對(duì)象的?其他引用?都被清除,垃圾回收機(jī)制就會(huì)釋放該對(duì)象所占用的內(nèi)存。也就是說,一旦不再需要,WeakMap 里面的鍵名對(duì)象和所對(duì)應(yīng)的鍵值對(duì)會(huì)自動(dòng)消失,不用手動(dòng)刪除引用

4.利用棧實(shí)現(xiàn)進(jìn)制轉(zhuǎn)換

將10進(jìn)制轉(zhuǎn)換為2~36的任意進(jìn)制,例如轉(zhuǎn)換為二進(jìn)制,將十進(jìn)制的數(shù)除以2,每次將余數(shù)壓入棧中,用商繼續(xù)除以2,直到商為0.

5.校驗(yàn)圓括號(hào)是否平衡

三、隊(duì)列(Queue)與雙端隊(duì)列(Deque:double-ended queue)

1.隊(duì)列


先進(jìn)先出

隊(duì)列是遵循先進(jìn)先出(FIFO,也成為先來先服務(wù))原則的一組有序的項(xiàng)。它與棧非常類似,但棧?只在棧頂?進(jìn)行存取,而隊(duì)列是只能在 隊(duì)尾存,隊(duì)首取?:最新添加的元素必須排在末尾,最早被添加的元素最先得到服務(wù)。

常見的例子就是打印隊(duì)列,先來先打印,后來先排隊(duì)

經(jīng)典問題之擊鼓傳花

圍圈傳遞花,音樂停花在誰手里則誰淘汰,游戲里小朋友位置不變,傳遞花,用隊(duì)列模擬就是相當(dāng)于隊(duì)列首部放一束花不懂,為危險(xiǎn)位置,隊(duì)列首部的小朋友不斷跑到隊(duì)列尾,音樂停誰在隊(duì)首誰淘汰。

循環(huán)隊(duì)列

2.雙端隊(duì)列

雙端隊(duì)列,允許我們同時(shí)從?兩端進(jìn)行存取

常見的例子是用雙端隊(duì)列存儲(chǔ)最近的幾次操作,最早的操作排在最前面,當(dāng)需要撤銷時(shí)從隊(duì)尾彈出最近的一次操作,就像棧一樣,后進(jìn)的先出;當(dāng)存儲(chǔ)的操作數(shù)達(dá)到設(shè)定時(shí),有最新操作進(jìn)來就需要移除最早的操作,這個(gè)時(shí)候要從最前面移除,這時(shí)候就像隊(duì)列一樣,先進(jìn)的先出,所以雙端隊(duì)列就像棧和隊(duì)列相結(jié)合的一種數(shù)據(jù)結(jié)構(gòu)。

雙端隊(duì)列是兩端都可以存取,不過這個(gè)例子是在兩端取,僅需要在一端存

判斷字符串是否是回文(正讀和反讀相同)

用棧的思路是先反轉(zhuǎn)再對(duì)比原字符串

用雙端隊(duì)列的思路是同時(shí)從兩端取一個(gè)字符,對(duì)比是否相同

四、鏈表LinkedList

1. 鏈表LinkedList

鏈表能夠確定一組(不是保存一組,鏈表本身只存第一個(gè)元素head的引用)有序的元素集合。不同于數(shù)組的是,鏈表中的元素在內(nèi)存中并不是連續(xù)放置的,每個(gè)節(jié)點(diǎn)都存儲(chǔ)元素本身和指向下一個(gè)節(jié)點(diǎn)的指針,元素順序就是靠這些指向下一節(jié)點(diǎn)的指針來確定的。

實(shí)際這些節(jié)點(diǎn)并不是這樣順序排列的

在數(shù)組中插入或刪除元素的成本很高,因?yàn)樾枰苿?dòng)元素;

鏈表的好處就是添加或刪除時(shí)不需要移動(dòng)元素,只需要改變指針指向;

當(dāng)然,鏈表的缺點(diǎn)是額外保存了指針,而且因?yàn)樵卦趦?nèi)存中不是連續(xù)放置的,在訪問鏈表中間的元素時(shí),只能從前往后迭代,無法像數(shù)組一樣能直接通過位置找到元素;

當(dāng)你需要添加和移除很多元素時(shí),應(yīng)該首選鏈表而非數(shù)組。

刪除元素


2.雙向鏈表

每個(gè)節(jié)點(diǎn)除了存儲(chǔ)下個(gè)節(jié)點(diǎn)的指針外,還要存儲(chǔ)上一個(gè)節(jié)點(diǎn)的指針

雙向鏈表由于同時(shí)保存了前后兩個(gè)節(jié)點(diǎn)的引用,可以進(jìn)行前序和后序遍歷,普通鏈表只能從前往后。由于可以進(jìn)行雙向遍歷,當(dāng)確定了要操作的元素位置時(shí),可以根據(jù)位置的前后來決定遍歷的方向,以減少迭代次數(shù)。

但是每個(gè)節(jié)點(diǎn)都要多分配一個(gè)指針存儲(chǔ)前一個(gè)結(jié)點(diǎn)引用,增刪時(shí)也需要維護(hù)前驅(qū)節(jié)點(diǎn)的指向。

3.循環(huán)鏈表

與鏈表的不同點(diǎn)在于,鏈表的最后一個(gè)元素的next不再指向undefined而是指向head節(jié)點(diǎn);雙向鏈表的head節(jié)點(diǎn)的prev指向最后一個(gè)節(jié)點(diǎn)。

循環(huán)鏈表的優(yōu)點(diǎn)在于,從任意一個(gè)位置出發(fā),都能遍歷整個(gè)鏈表;普通鏈表則只能從指定位置遍歷到尾部,或首部(如果是雙向鏈表的話)。

循環(huán)鏈表中沒有NULL指針,在遍歷時(shí)也可以減少判斷null的這個(gè)操作

五、集合Set

集合由一組無序且不相同的項(xiàng)組成。例如:N = {'A', 10, '哈', 4};


集合內(nèi)的項(xiàng)無序且唯一

我們可以使用對(duì)象來模擬集合。將項(xiàng)作為對(duì)象的key以保證唯一性,增加項(xiàng)時(shí)則增加屬性,刪除項(xiàng)則刪除該屬性。

在進(jìn)行集合運(yùn)算時(shí),可以用擴(kuò)展運(yùn)算符將集合轉(zhuǎn)化為數(shù)組來操作:

1.并集:new Set([...setA,...setB])

2.交集:new Set([...setA].filter(i=>setB.has(i)))

3.差集:new Set([...setA].filter(i=>!setB.has(i)))

六、字典Map和散列表HashMap(page 145)

集合是以[值,值]的形式存儲(chǔ)元素,字典以[鍵,值]的形式來存儲(chǔ)元素。字典也稱作映射、符號(hào)、關(guān)聯(lián)數(shù)組。

在字典中,我們將key轉(zhuǎn)化為字符串后,將value存在該字符串屬性中。

在散列表(哈希表)中,我們【將key轉(zhuǎn)化為字符串再使用每個(gè)字符的ASCII值轉(zhuǎn)成一個(gè)數(shù)字】,將value存在該數(shù)字位置。這個(gè)轉(zhuǎn)換函數(shù)是散列(hash)函數(shù),散列函數(shù)的作用,就是給定一個(gè)鍵值,返回值在表中的位置。

1.處理散列表中的沖突

當(dāng)不同的key有相同的散列值時(shí),會(huì)有沖突,因?yàn)椴煌闹祵?duì)應(yīng)同一個(gè)位置。

方法1:分離鏈接:為散列表的每一個(gè)位置創(chuàng)建一個(gè)鏈表,這樣一個(gè)位置上相當(dāng)于能存多個(gè)值了,不過存的時(shí)候需要既存值也存鍵,因?yàn)槿〉臅r(shí)候需要使用鍵來匹配鏈表中的值。

方法2:線性探查:當(dāng)添加元素時(shí)發(fā)現(xiàn)位置被占了,就從此位置向后迭代,以找一個(gè)空閑的位置并插入。在查找值時(shí),如果要查找的位置上有值,并且所存的key與要查找的key相同,則返回,否則向后查找哈希值為當(dāng)前位置的元素并匹配key。當(dāng)要?jiǎng)h除某個(gè)值時(shí),除了刪除那個(gè)位置上的值以外,還要將其他沖突的元素移動(dòng)至之前的位置 ,不產(chǎn)生空位置。


如果位置被占了就往后找一個(gè)空閑位置

七、樹

一棵樹包含一系列存在父子關(guān)系的節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)都有一個(gè)父節(jié)點(diǎn)和0/多個(gè)子節(jié)點(diǎn)。子樹,由一個(gè)節(jié)點(diǎn)和它的后代構(gòu)成;

節(jié)點(diǎn)的一個(gè)屬性是深度,取決于祖先的數(shù)量;樹的高度取決于所有節(jié)點(diǎn)深度的最大值

在樹中,我們將節(jié)點(diǎn)稱為【鍵】

1.二叉樹與二叉搜索樹(BST)

二叉樹中的節(jié)點(diǎn)最多只能有2個(gè)子節(jié)點(diǎn),二叉搜索樹只允許在左側(cè)節(jié)點(diǎn)存儲(chǔ)比父節(jié)點(diǎn)小的值,右側(cè)存儲(chǔ)大的值。


二叉搜索樹

2.樹的遍歷

2.1 中序遍歷 (先訪問左節(jié)點(diǎn)——再訪問自身——再訪問右節(jié)點(diǎn))

以從最小到最大的順序訪問所有節(jié)點(diǎn);


3,4,6,7,9,10,12,50
中序遍歷

2.2 前序遍歷(先訪問自身——再訪問左節(jié)點(diǎn)——再訪問右節(jié)點(diǎn))

前序遍歷

2.3 后序遍歷(先訪問左節(jié)點(diǎn)——再訪問右節(jié)點(diǎn)——再訪問自身)

3,4,9,7,6,12,50,10


后序遍歷

3.在樹中搜索值

4.移除樹中的節(jié)點(diǎn)

4.1 移除一個(gè)葉子節(jié)點(diǎn)

node4.left = null; node3 = null;

4.2 移除只有一個(gè)子節(jié)點(diǎn)的節(jié)點(diǎn)

node6.left = node4.left; node4 = null;

4.3 移除有兩個(gè)子節(jié)點(diǎn)的節(jié)點(diǎn)

替換為右子樹里最小的節(jié)點(diǎn)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容