前言
瑞士計算機(jī)科學(xué)家Niklaus Wirth在1976年寫了一本書杠园,名為《算法+數(shù)據(jù)結(jié)構(gòu)=編程》立肘。
40多年后,這個等式仍被奉為真理挺庞。這就是為什么在面試過程中,需要考察軟件工程師對數(shù)據(jù)結(jié)構(gòu)的理解稼病。
幾乎所有的問題都需要面試者對數(shù)據(jù)結(jié)構(gòu)有深刻的理解选侨。無論你是初入職場的新兵(剛從大學(xué)或者編程培訓(xùn)班畢業(yè)),還是擁有幾十年經(jīng)驗的職場老鳥然走。
有些面試題會明確提及某種數(shù)據(jù)結(jié)構(gòu)侵俗,例如,“給定一個二叉樹丰刊“ィ”而另一些則隱含在面試題中,例如,“我們希望記錄每個作者相關(guān)的書籍?dāng)?shù)量寻歧≌普ぃ”
即便是對于一些非常基礎(chǔ)的工作來說码泛,學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)也是必須的猾封。那么,就讓我們先從一些基本概念開始入手噪珊。
什么是數(shù)據(jù)結(jié)構(gòu)晌缘?
數(shù)據(jù)結(jié)構(gòu):計算機(jī)存儲、組織數(shù)據(jù)的方式痢站。數(shù)據(jù)結(jié)構(gòu)是指相互之間存在一種或多種特定關(guān)系的數(shù)據(jù)元素的集合蚂四。通常情況下齿穗,精心選擇的數(shù)據(jù)結(jié)構(gòu)可以帶來更高的運(yùn)行或者存儲效率跪妥。數(shù)據(jù)結(jié)構(gòu)往往同高效的檢索算法和索引技術(shù)有關(guān)漩蟆。
簡單地說,數(shù)據(jù)結(jié)構(gòu)是以某種特定的布局方式存儲數(shù)據(jù)的容器呜叫。
這種“布局方式”決定了數(shù)據(jù)結(jié)構(gòu)對于某些操作是高效的空繁,而對于其他操作則是低效的。首先我們需要理解各種數(shù)據(jù)結(jié)構(gòu)朱庆,才能在處理實(shí)際問題時選取最合適的數(shù)據(jù)結(jié)構(gòu)盛泡。
為什么我們需要數(shù)據(jù)結(jié)構(gòu)?
數(shù)據(jù)是計算機(jī)科學(xué)當(dāng)中最關(guān)鍵的實(shí)體娱颊,而數(shù)據(jù)結(jié)構(gòu)則可以將數(shù)據(jù)以某種組織形式存儲
饭于,因此,數(shù)據(jù)結(jié)構(gòu)的價值不言而喻维蒙。
無論你以何種方式解決何種問題掰吕,你都需要處理數(shù)據(jù)——無論是涉及員工薪水、股票價格颅痊、購物清單殖熟,還是只是簡單的電話簿問題。
數(shù)據(jù)需要根據(jù)不同的場景斑响,按照特定的格式進(jìn)行存儲菱属。有很多數(shù)據(jù)結(jié)構(gòu)能夠滿足以不同格式存儲數(shù)據(jù)的需求。
常見的數(shù)據(jù)結(jié)構(gòu)
首先列出一些最常見的數(shù)據(jù)結(jié)構(gòu)舰罚,我們將逐一說明:
- 數(shù)組
- 棧
- 隊列
- 鏈表
- 樹
- 圖
- 字典樹(這是一種高效的樹形結(jié)構(gòu)纽门,但值得單獨(dú)說明)
- 散列表(哈希表)
數(shù)組
所謂數(shù)組,是有序的元素序列营罢。若將有限個類型相同的變量的集合命名赏陵,那么這個名稱為數(shù)組名饼齿。組成數(shù)組的各個變量稱為數(shù)組的分量,也稱為數(shù)組的元素蝙搔,有時也稱為下標(biāo)變量缕溉。用于區(qū)分?jǐn)?shù)組的各個元素的數(shù)字編號稱為下標(biāo)。數(shù)組是在程序設(shè)計中吃型,為了處理方便证鸥, 把具有相同類型的若干元素按無序的形式組織起來的一種形式。這些無序排列的同類數(shù)據(jù)元素的集合稱為數(shù)組勤晚。
數(shù)組是用于儲存多個相同類型數(shù)據(jù)的集合枉层。
數(shù)組是最簡單、也是使用最廣泛的數(shù)據(jù)結(jié)構(gòu)赐写。棧鸟蜡、隊列等其他數(shù)據(jù)結(jié)構(gòu)均由數(shù)組演變而來。下圖是一個包含元素(1血淌,2矩欠,3和4)的簡單數(shù)組财剖,數(shù)組長度為4悠夯。
每個數(shù)據(jù)元素都關(guān)聯(lián)一個正數(shù)值,我們稱之為索引躺坟,它表明數(shù)組中每個元素所在的位置沦补。大部分語言將初始索引定義為零。
以下是數(shù)組的兩種類型:
- 一維數(shù)組(如上所示)
- 多維數(shù)組(數(shù)組的數(shù)組)
(1) 數(shù)組的基本操作
Insert——在指定索引位置插入一個元素
Get——返回指定索引位置的元素
Delete——刪除指定索引位置的元素
Size——得到數(shù)組所有元素的數(shù)量
(2) 面試中關(guān)于數(shù)組的常見問題
- 尋找數(shù)組中第二小的元素
- 找到數(shù)組中第一個不重復(fù)出現(xiàn)的整數(shù)
- 合并兩個有序數(shù)組
- 重新排列數(shù)組中的正值和負(fù)值
棧
棧(stack)又名堆棧咪橙,它是一種運(yùn)算受限的線性表夕膀。限定僅在表尾進(jìn)行插入和刪除操作的線性表。這一端被稱為棧頂美侦,相對地产舞,把另一端稱為棧底.
可以把棧想象成一列垂直堆放的書。為了拿到中間的書菠剩,你需要移除放置在這上面的所有書易猫。這就是LIFO(后進(jìn)先出)
的工作原理。
下圖是包含三個數(shù)據(jù)元素(1具壮,2和3)的棧准颓,其中頂部的3將被最先移除:
(1) 棧的基本操作
- Push——在頂部插入一個元素
- Pop——返回并移除棧頂元素
- isEmpty——如果棧為空,則返回true
- Top——返回頂部元素棺妓,但并不移除它
(2) 面試中關(guān)于棧的常見問題
- 使用棧計算后綴表達(dá)式
- 對棧的元素進(jìn)行排序
- 判斷表達(dá)式是否括號平衡
隊列
隊列是一種特殊的線性表攘已,特殊之處在于它只允許在表的前端(front)進(jìn)行刪除操作,而在表的后端(rear)進(jìn)行插入操作怜跑,和棧一樣样勃,隊列是一種操作受限制的線性表。進(jìn)行插入操作的端稱為隊尾,進(jìn)行刪除操作的端稱為隊頭彤灶。
棧與隊列的最大差別在于棧是LIFO(后進(jìn)先出)看幼,而隊列是FIFO,即先進(jìn)先出幌陕。
一個完美的隊列現(xiàn)實(shí)例子:售票亭排隊隊伍诵姜。如果有新人加入,他需要到隊尾去排隊搏熄,而非隊首——排在前面的人會先拿到票棚唆,然后離開隊伍。
下圖是包含四個元素(1心例,2宵凌,3和4)的隊列,其中在頂部的1將被最先移除:
移除先入隊的元素止后、插入新元素
(1) 隊列的基本操作
- Enqueue()?——?在隊列尾部插入元素
- Dequeue()?——移除隊列頭部的元素
- isEmpty()——如果隊列為空瞎惫,則返回true
- Top()?——返回隊列的第一個元素
(2) 面試中關(guān)于隊列的常見問題
- 使用隊列表示棧
- 對隊列的前k個元素倒序
- 使用隊列生成從1到n的二進(jìn)制數(shù)
鏈表
鏈表是一種物理存儲單元上非連續(xù)、非順序的存儲結(jié)構(gòu)译株,數(shù)據(jù)元素的邏輯順序是通過鏈表中的指針鏈接次序?qū)崿F(xiàn)的瓜喇。鏈表由一系列結(jié)點(diǎn)(鏈表中每一個元素稱為結(jié)點(diǎn))組成,結(jié)點(diǎn)可以在運(yùn)行時動態(tài)生成歉糜。每個結(jié)點(diǎn)包括兩個部分:一個是存儲數(shù)據(jù)元素的數(shù)據(jù)域乘寒,另一個是存儲下一個結(jié)點(diǎn)地址的指針域。
使用鏈表結(jié)構(gòu)可以克服數(shù)組鏈表需要預(yù)先知道數(shù)據(jù)大小的缺點(diǎn)匪补,鏈表結(jié)構(gòu)可以充分利用計算機(jī)內(nèi)存空間伞辛,實(shí)現(xiàn)靈活的內(nèi)存動態(tài)管理。但是鏈表失去了數(shù)組隨機(jī)讀取的優(yōu)點(diǎn)夯缺,同時鏈表由于增加了結(jié)點(diǎn)的指針域蚤氏,空間開銷比較大。鏈表最明顯的好處就是踊兜,常規(guī)數(shù)組排列關(guān)聯(lián)項目的方式可能不同于這些數(shù)據(jù)項目在記憶體或磁盤上順序竿滨,數(shù)據(jù)的存取往往要在不同的排列順序中轉(zhuǎn)換。鏈表允許插入和移除表上任意位置上的節(jié)點(diǎn)润文,但是不允許隨機(jī)存取姐呐。
這是鏈表內(nèi)部結(jié)構(gòu)的展示:
(1) 鏈表包括以下類型:
- 單鏈表(單向)
- 雙向鏈表(雙向)
(2) 鏈表的基本操作:
- InsertAtEnd - 在鏈表的末尾插入指定元素
- InsertAtHead - 在鏈接列表的開頭/頭部插入指定元素
- Delete? - 從鏈接列表中刪除指定元素
- DeleteAtHead - 刪除鏈接列表的第一個元素
- Search? - 從鏈表中返回指定元素
- isEmpty - 如果鏈表為空,則返回true
圖
圖是一組以網(wǎng)絡(luò)形式相互連接的節(jié)點(diǎn)典蝌。節(jié)點(diǎn)也稱為頂點(diǎn)(Vertex)曙砂。 一對節(jié)點(diǎn)(x,y)稱為邊(edge)骏掀,表示頂點(diǎn)x連接到頂點(diǎn)y鸠澈。邊可以包含權(quán)重/成本柱告,顯示從頂點(diǎn)x到y(tǒng)所需的成本。
圖是由頂點(diǎn)集合(Vertex)及頂點(diǎn)間的關(guān)系集合組成的一種數(shù)據(jù)結(jié)構(gòu):Graph=( V, E )
V = {x | x ∈某個數(shù)據(jù)對象 } 是頂點(diǎn)的有窮非空集合笑陈;
E ={ (x, y) | x, y ∈V } 是頂點(diǎn)之間關(guān)系的有窮集合际度,也叫做邊(Edge)集合。
(1) 圖的類型
- 無向圖
- 有向圖
(2) 在程序語言中涵妥,圖可以用兩種形式表示:
- 鄰接矩陣
- 鄰接表
(3) 常見圖遍歷算法
- 廣度優(yōu)先搜索
- 深度優(yōu)先搜索
(4) 面試中關(guān)于圖的常見問題
- 實(shí)現(xiàn)廣度和深度優(yōu)先搜索
- 檢查圖是否為樹
- 計算圖的邊數(shù)
- 找到兩個頂點(diǎn)之間的最短路徑
樹
樹形結(jié)構(gòu)是一種層級式的數(shù)據(jù)結(jié)構(gòu)乖菱,由頂點(diǎn)(節(jié)點(diǎn))和連接它們的邊組成。 樹類似于圖蓬网,但區(qū)分樹和圖的重要特征是樹中不存在環(huán)路窒所。
樹形結(jié)構(gòu)被廣泛應(yīng)用于人工智能和復(fù)雜算法,它可以提供解決問題的有效存儲機(jī)制帆锋。
這是一個簡單樹的示意圖吵取,以及樹數(shù)據(jù)結(jié)構(gòu)中使用的基本術(shù)語:
- Root - 根節(jié)點(diǎn)
- Parent - 父節(jié)點(diǎn)
- Child - 子節(jié)點(diǎn)
- Leaf - 葉子節(jié)點(diǎn)
- Sibling - 兄弟節(jié)點(diǎn)
(1) 以下是樹形結(jié)構(gòu)的主要類型:
- N元樹
- 平衡樹
- 二叉樹
- 二叉搜索樹
- AVL樹
- 紅黑樹
- 2-3樹
其中,二叉樹和二叉搜索樹是最常用的樹
(2) 面試中關(guān)于樹結(jié)構(gòu)的常見問題:
- 求二叉樹的高度
- 在二叉搜索樹中查找第k個最大值
- 查找與根節(jié)點(diǎn)距離k的節(jié)點(diǎn)
- 在二叉樹中查找給定節(jié)點(diǎn)的祖先節(jié)點(diǎn)
字典樹(Trie)
又稱單詞查找樹锯厢,Trie樹皮官,是一種樹形結(jié)構(gòu),是一種哈希樹的變種实辑。典型應(yīng)用是用于統(tǒng)計捺氢,排序和保存大量的字符串(但不僅限于字符串),所以經(jīng)常被搜索引擎系統(tǒng)用于文本詞頻統(tǒng)計徙菠。它的優(yōu)點(diǎn)是:利用字符串的公共前綴來減少查詢時間讯沈,最大限度地減少無謂的字符串比較郁岩,查詢效率比哈希樹高婿奔。
以下是在字典樹中存儲三個單詞“top”,“thus”和“their”的例子:
這些單詞以頂部到底部的方式存儲问慎,其中綠色節(jié)點(diǎn)“p”萍摊,“s”和“r”分別表示“top”,“thus”和“theirs”的底部如叼。
哈希表
散列表(Hash table冰木,也叫哈希表),是根據(jù)關(guān)鍵碼值(Key value)而直接進(jìn)行訪問的數(shù)據(jù)結(jié)構(gòu)笼恰。也就是說踊沸,它通過把關(guān)鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度社证。這個映射函數(shù)叫做散列函數(shù)逼龟,存放記錄的數(shù)組叫做散列表。
給定表M追葡,存在函數(shù)f(key)腺律,對任意給定的關(guān)鍵字值key奕短,代入函數(shù)后若能得到包含該關(guān)鍵字的記錄在表中的地址,則稱表M為哈希(Hash)表匀钧,函數(shù)f(key)為哈希(Hash) 函數(shù)翎碑。
哈希表通常使用數(shù)組實(shí)現(xiàn)。
散列數(shù)據(jù)結(jié)構(gòu)的性能取決于以下三個因素:
- 哈希函數(shù)
- 哈希表的大小
- 碰撞處理方法
以上是在編程面試之前你應(yīng)該知曉的八大數(shù)據(jù)結(jié)構(gòu)之斯。