圖解數(shù)據(jù)結(jié)構(gòu)之?dāng)?shù)組、鏈表、棧、隊(duì)列

一 .數(shù)組

數(shù)組(Array) 是一種很常見(jiàn)的數(shù)據(jù)結(jié)構(gòu)。它是由相同類(lèi)型的元素(element)的集合所組成,并且被分配一塊連續(xù)的內(nèi)存來(lái)存儲(chǔ)(與鏈表對(duì)比),利用元素的索引(index)可以計(jì)算出該元素對(duì)應(yīng)的存儲(chǔ)地址。
它的特點(diǎn)是提供隨機(jī)訪(fǎng)問(wèn)并且容量有限。

假如數(shù)組的長(zhǎng)度為 n
訪(fǎng)問(wèn):O(1)//訪(fǎng)問(wèn)特定位置的元素   
插入:O(n )//最壞的情況發(fā)生在插入發(fā)生在數(shù)組的首部并需要移動(dòng)所有元素時(shí)
刪除:O(n)//最壞的情況發(fā)生在刪除數(shù)組的開(kāi)頭發(fā)生并需要移動(dòng)第一元素后面所有的元素時(shí)

二 .鏈表

2.1 鏈表簡(jiǎn)介

鏈表(LinkedList) 雖然是一種線(xiàn)性表,但是并不會(huì)按線(xiàn)性的順序存儲(chǔ)數(shù)據(jù),而是在每一個(gè)節(jié)點(diǎn)里存到下一個(gè)節(jié)點(diǎn)的指針(Pointer)。由于不必須按順序存儲(chǔ),鏈表在插入和刪除的時(shí)候可以達(dá)到 O(1) 的復(fù)雜度,比另一種線(xiàn)性表順序表快得多,但是查找一個(gè)節(jié)點(diǎn)或者訪(fǎng)問(wèn)特定編號(hào)的節(jié)點(diǎn)則需要 O(n) 的時(shí)間,而順序表相應(yīng)的時(shí)間復(fù)雜度分別是O(logn) 和O(1)。

使用鏈表結(jié)構(gòu)可以克服數(shù)組需要預(yù)先知道數(shù)據(jù)大小的缺點(diǎn),鏈表結(jié)構(gòu)可以充分利用計(jì)算機(jī)內(nèi)存空間,實(shí)現(xiàn)靈活的內(nèi)存動(dòng)態(tài)管理。但鏈表不會(huì)節(jié)省空間,相比于數(shù)組會(huì)占用更多的空間,因?yàn)殒湵碇忻總€(gè)節(jié)點(diǎn)存放的還有指向其他節(jié)點(diǎn)的指針。鏈表不具有數(shù)組隨機(jī)讀取的優(yōu)點(diǎn),但是插入刪除元素的時(shí)間復(fù)雜度為O(1)

2.2 鏈表分類(lèi)

常見(jiàn)鏈表分類(lèi):

  1. 單鏈表

  2. 雙向鏈表

  3. 循環(huán)鏈表

  4. 雙向循環(huán)鏈表

假如鏈表中有n個(gè)元素
訪(fǎng)問(wèn):O(n)//訪(fǎng)問(wèn)特定位置的元素插入
刪除:O(1)//必須要要知道插入元素的位置

2.2.1 單鏈表

單鏈表 單向鏈表只有一個(gè)方向,結(jié)點(diǎn)只有一個(gè)后繼指針 next 指向后面的節(jié)點(diǎn)。因此,鏈表這種數(shù)據(jù)結(jié)構(gòu)通常在物理內(nèi)存上是不連續(xù)的。我們習(xí)慣性地把第一個(gè)結(jié)點(diǎn)叫作頭結(jié)點(diǎn),鏈表通常有一個(gè)不保存任何值的 head 節(jié)點(diǎn)(頭結(jié)點(diǎn)),通過(guò)頭結(jié)點(diǎn)我們可以遍歷整個(gè)鏈表。尾結(jié)點(diǎn)通常指向null。

image

2.2.2 循環(huán)鏈表

循環(huán)鏈表 其實(shí)是一種特殊的單鏈表,和單鏈表不同的是循環(huán)鏈表的尾結(jié)點(diǎn)不是指向null,而是指向鏈表的頭結(jié)點(diǎn)。

image

2.2.3 雙向鏈表

雙向鏈表 包含兩個(gè)指針,一個(gè)prev指向前一個(gè)節(jié)點(diǎn),一個(gè)next指向后一個(gè)節(jié)點(diǎn)。

image

2.2.4 雙向循環(huán)鏈表

雙向循環(huán)鏈表 最后一個(gè)節(jié)點(diǎn)的 next 指向head,而 head 的prev指向最后一個(gè)節(jié)點(diǎn),構(gòu)成一個(gè)環(huán)。

image

2.3 數(shù)組vs鏈表

  1. 數(shù)組使用的是連續(xù)內(nèi)存空間對(duì)CPU的緩存機(jī)制友好,鏈表則相反。

  2. 數(shù)組的大小固定,聲明之后就要占用所需的連續(xù)內(nèi)存空間。如果聲明的數(shù)組過(guò)小的話(huà),需要再申請(qǐng)一個(gè)更大的內(nèi)存空間,然后將原數(shù)組拷貝進(jìn)去。數(shù)組多的情況下,這將是非常耗時(shí)的。鏈表則天然支持動(dòng)態(tài)擴(kuò)容。

三 棧

3.1 棧簡(jiǎn)介

(stack)只允許在有序的線(xiàn)性數(shù)據(jù)集合的一端(稱(chēng)為棧頂 top)進(jìn)行加入數(shù)據(jù)(push)和移除數(shù)據(jù)(pop)。因而按照 后進(jìn)先出(LIFO, Last In First Out) 的原理運(yùn)作。在棧中,push 和 pop 的操作都發(fā)生在棧頂。 棧常用一維數(shù)組或鏈表來(lái)實(shí)現(xiàn),用數(shù)組實(shí)現(xiàn)的隊(duì)列叫作 順序棧 ,用鏈表實(shí)現(xiàn)的隊(duì)列叫作 鏈?zhǔn)綏?/strong> 。

假設(shè)堆棧中有n個(gè)元素
訪(fǎng)問(wèn):O(n)//最壞情況 插入
刪除:O(1)//頂端插入和刪除元素
image

3.2 棧的常見(jiàn)應(yīng)用常見(jiàn)應(yīng)用場(chǎng)景

3.2.1 實(shí)現(xiàn)瀏覽器的回退和前進(jìn)功能

我們只需要使用兩個(gè)棧(Stack1和Stack2)和就能實(shí)現(xiàn)這個(gè)功能。比如你按順序查看了 1,2,3,4 這四個(gè)頁(yè)面,我們依次把 1,2,3,4 這四個(gè)頁(yè)面壓入 Stack1 中。當(dāng)你想回頭看2這個(gè)頁(yè)面的時(shí)候,你點(diǎn)擊回退按鈕,我們依次把4,3這兩個(gè)頁(yè)面從Stack1 彈出,然后壓入 Stack2 中。假如你又想回到頁(yè)面3,你點(diǎn)擊前進(jìn)按鈕,我們將3頁(yè)面從Stack2 彈出,然后壓入到 Stack1 中。示例圖如下:

image

3.2.2 檢查符號(hào)是否成對(duì)出現(xiàn)

給定一個(gè)只包括 '('')''{''}''['']' 的字符串,判斷該字符串是否有效。

有效字符串需滿(mǎn)足:

  1. 左括號(hào)必須用相同類(lèi)型的右括號(hào)閉合。
  1. 左括號(hào)必須以正確的順序閉合。

比如 "()"、"()[]{}"、"{[]}" 都是有效字符串,而 "(]" 、"([)]" 則不是。

這個(gè)問(wèn)題實(shí)際是Leetcode的一道題目,我們可以利用棧 Stack 來(lái)解決這個(gè)問(wèn)題。

  1. 首先我們將括號(hào)間的對(duì)應(yīng)規(guī)則存放在 Map 中,這一點(diǎn)應(yīng)該毋容置疑;

  2. 創(chuàng)建一個(gè)棧。遍歷字符串,如果字符是左括號(hào)就直接加入stack中,否則將stack的棧頂元素與這個(gè)括號(hào)做比較,如果不相等就直接返回false。遍歷結(jié)束,如果stack為空,返回 true

public boolean isValid(String s){
    // 括號(hào)之間的對(duì)應(yīng)規(guī)則
    HashMap<Character, Character> mappings = new HashMap<Character, Character>();
    mappings.put(')', '(');
    mappings.put('}', '{');
    mappings.put(']', '[');
    Stack<Character> stack = new Stack<Character>();
    char[] chars = s.toCharArray();
    for (int i = 0; i < chars.length; i++) {
        if (mappings.containsKey(chars[i])) {
            char topElement = stack.empty() ? '#' : stack.pop();
            if (topElement != mappings.get(chars[i])) {
                return false;
            }
        } else {
            stack.push(chars[i]);
        }
    }
    return stack.isEmpty();
}

3.2.3 反轉(zhuǎn)字符串

將字符串中的每個(gè)字符先入棧再出棧就可以了。

3.2.4 維護(hù)函數(shù)調(diào)用

最后一個(gè)被調(diào)用的函數(shù)必須先完成執(zhí)行,符合棧的 后進(jìn)先出(LIFO, Last In First Out)特性。

四. 隊(duì)列

4.1 隊(duì)列簡(jiǎn)介

隊(duì)列先進(jìn)先出( FIFO,F(xiàn)irst In, First Out) 的線(xiàn)性表。在具體應(yīng)用中通常用鏈表或者數(shù)組來(lái)實(shí)現(xiàn),用數(shù)組實(shí)現(xiàn)的隊(duì)列叫作 順序隊(duì)列 ,用鏈表實(shí)現(xiàn)的隊(duì)列叫作 鏈?zhǔn)疥?duì)列隊(duì)列只允許在后端(rear)進(jìn)行插入操作也就是 入隊(duì) enqueue,在前端(front)進(jìn)行刪除操作也就是出隊(duì) dequeue

隊(duì)列的操作方式和堆棧類(lèi)似,唯一的區(qū)別在于隊(duì)列只允許新數(shù)據(jù)在后端進(jìn)行添加。

假設(shè)隊(duì)列中有n個(gè)元素
訪(fǎng)問(wèn):O(n)//最壞情況插入
刪除:O(1)//后端插入前端刪除元素
image

4.2 隊(duì)列分類(lèi)

4.2.1 單隊(duì)列

單隊(duì)列就是常見(jiàn)的隊(duì)列, 每次添加元素時(shí),都是添加到隊(duì)尾。單隊(duì)列又分為 順序隊(duì)列(數(shù)組實(shí)現(xiàn))鏈?zhǔn)疥?duì)列(鏈表實(shí)現(xiàn))

順序隊(duì)列存在“假溢出”的問(wèn)題也就是明明有位置卻不能添加的情況。

假設(shè)下圖是一個(gè)順序隊(duì)列,我們將前兩個(gè)元素1,2 出隊(duì),并入隊(duì)兩個(gè)元素7,8。當(dāng)進(jìn)行入隊(duì)、出隊(duì)操作的時(shí)候,front和 rear 都會(huì)持續(xù)往后移動(dòng),當(dāng) rear 移動(dòng)到最后的時(shí)候,我們無(wú)法再往隊(duì)列中添加數(shù)據(jù),即使數(shù)組中還有空余空間,這種現(xiàn)象就是 ”假溢出“ 。除了假溢出問(wèn)題之外,如下圖所示,當(dāng)添加元素8的時(shí)候,rear 指針移動(dòng)到數(shù)組之外(越界)。

為了避免當(dāng)只有一個(gè)元素的時(shí)候,隊(duì)頭和隊(duì)尾重合使處理變得麻煩,所以引入兩個(gè)指針,front 指針指向?qū)︻^元素,rear 指針指向隊(duì)列最后一個(gè)元素的下一個(gè)位置,這樣當(dāng) front 等于 rear 時(shí),此隊(duì)列不是還剩一個(gè)元素,而是空隊(duì)列。——From 《大話(huà)數(shù)據(jù)結(jié)構(gòu)》

image

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

循環(huán)隊(duì)列可以解決順序隊(duì)列的假溢出和越界問(wèn)題。解決辦法就是:從頭開(kāi)始,這樣也就會(huì)形成頭尾相接的循環(huán),這也就是循環(huán)隊(duì)列名字的由來(lái)。

還是用上面的圖,我們將 rear 指針指向數(shù)組下標(biāo)為 0 的位置就不會(huì)有越界問(wèn)題了。當(dāng)我們?cè)傧蜿?duì)列中添加元素的時(shí)候, rear 向后移動(dòng)。

image

順序隊(duì)列中,我們說(shuō) front==rear 的時(shí)候隊(duì)列為空,循環(huán)隊(duì)列中則不一樣,也可能為滿(mǎn),如上圖所示。解決辦法有兩種:

  1. 可以設(shè)置一個(gè)標(biāo)志變量 flag,當(dāng) front==rear 并且 flag=0 的時(shí)候隊(duì)列為空,當(dāng)front==rear 并且 flag=1 的時(shí)候隊(duì)列為滿(mǎn)。

  2. 隊(duì)列為空的時(shí)候就是 front==rear ,隊(duì)列滿(mǎn)的時(shí)候,我們保證數(shù)組還有一個(gè)空閑的位置,rear 就指向這個(gè)空閑位置,如下圖所示,那么現(xiàn)在判斷隊(duì)列是否為滿(mǎn)的條件就是: (rear+1) % QueueSize= front

image

常見(jiàn)應(yīng)用場(chǎng)景

  • 阻塞隊(duì)列: 阻塞隊(duì)列可以看成在隊(duì)列基礎(chǔ)上加了阻塞操作的隊(duì)列。當(dāng)隊(duì)列為空的時(shí)候,出隊(duì)操作阻塞,當(dāng)隊(duì)列滿(mǎn)的時(shí)候,入隊(duì)操作阻塞。使用阻塞隊(duì)列我們可以很容易實(shí)現(xiàn)“生產(chǎn)者 - 消費(fèi)者“模型。

  • 線(xiàn)程池中的請(qǐng)求/任務(wù)隊(duì)列: 線(xiàn)程池中沒(méi)有空閑線(xiàn)程時(shí),新的任務(wù)請(qǐng)求線(xiàn)程資源時(shí),線(xiàn)程池該如何處理呢?答案是將這些請(qǐng)求放在隊(duì)列中,當(dāng)有空閑線(xiàn)程的時(shí)候,會(huì)循環(huán)中反復(fù)從隊(duì)列中獲取任務(wù)來(lái)執(zhí)行。隊(duì)列分為無(wú)界隊(duì)列(基于鏈表)和有界隊(duì)列(基于數(shù)組)。無(wú)界隊(duì)列的特點(diǎn)就是可以一直入列,除非系統(tǒng)資源耗盡,比如 :FixedThreadPool 使用無(wú)界隊(duì)列 LinkedBlockingQueue。但是有界隊(duì)列就不一樣了,當(dāng)隊(duì)列滿(mǎn)的話(huà)后面再有任務(wù)/請(qǐng)求就會(huì)拒絕,在 Java 中的體現(xiàn)就是會(huì)拋出java.util.concurrent.RejectedExecutionException 異常。

  • linux內(nèi)核進(jìn)程隊(duì)列(按優(yōu)先級(jí)排隊(duì))

  • 實(shí)現(xiàn)生活中的派對(duì),播放器上的播放列表;

  • 消息隊(duì)列

  • 等等……

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,595評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,560評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,035評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,814評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,224評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,444評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,988評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,804評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,998評(píng)論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評(píng)論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,237評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,665評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,927評(píng)論 1 287
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,706評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,993評(píng)論 2 374

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