W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
數(shù)組存儲(chǔ)在棧上和存儲(chǔ)在堆上,對(duì)時(shí)間效率和空間效率是否有影響?
存儲(chǔ)在棧上和堆上的數(shù)組都被存儲(chǔ)在連續(xù)內(nèi)存空間內(nèi),數(shù)據(jù)操作效率是基本一致的。然而,棧和堆具有各自的特點(diǎn),從而導(dǎo)致以下不同點(diǎn)。
為什么數(shù)組要求相同類型的元素,而在鏈表中卻沒(méi)有強(qiáng)調(diào)同類型呢?
鏈表由結(jié)點(diǎn)組成,結(jié)點(diǎn)之間通過(guò)引用(指針)連接,各個(gè)結(jié)點(diǎn)可以存儲(chǔ)不同類型的數(shù)據(jù),例如 int、double、string、object 等。
相對(duì)地,數(shù)組元素則必須是相同類型的,這樣才能通過(guò)計(jì)算偏移量來(lái)獲取對(duì)應(yīng)元素位置。例如,如果數(shù)組同時(shí)包含 int 和 long 兩種類型,單個(gè)元素分別占用 4 bytes 和 8 bytes ,那么此時(shí)就不能用以下公式計(jì)算偏移量了,因?yàn)閿?shù)組中包含了兩種長(zhǎng)度的元素。
# 元素內(nèi)存地址 = 數(shù)組內(nèi)存地址 + 元素長(zhǎng)度 * 元素索引
刪除節(jié)點(diǎn)后,是否需要把 ?P.next
?設(shè)為 None 呢?
不修改 ?P.next
? 也可以。從該鏈表的角度看,從頭結(jié)點(diǎn)遍歷到尾結(jié)點(diǎn)已經(jīng)遇不到 ?P
? 了。這意味著結(jié)點(diǎn) ?P
? 已經(jīng)從鏈表中刪除了,此時(shí)結(jié)點(diǎn) ?P
? 指向哪里都不會(huì)對(duì)這條鏈表產(chǎn)生影響了。
從垃圾回收的角度看,對(duì)于 Java、Python、Go 等擁有自動(dòng)垃圾回收的語(yǔ)言來(lái)說(shuō),節(jié)點(diǎn) ?P
? 是否被回收取決于是否有仍存在指向它的引用,而不是 ?P.next
? 的值。在 C 和 C++ 等語(yǔ)言中,我們需要手動(dòng)釋放節(jié)點(diǎn)內(nèi)存。
在鏈表中插入和刪除操作的時(shí)間復(fù)雜度是 O(1) 。但是增刪之前都需要 O(n) 查找元素,那為什么時(shí)間復(fù)雜度不是 O(n) 呢?
如果是先查找元素、再刪除元素,確實(shí)是 O(n) 。然而,鏈表的 O(1) 增刪的優(yōu)勢(shì)可以在其他應(yīng)用上得到體現(xiàn)。例如,雙向隊(duì)列適合使用鏈表實(shí)現(xiàn),我們維護(hù)一個(gè)指針變量始終指向頭結(jié)點(diǎn)、尾結(jié)點(diǎn),每次插入與刪除操作都是 O(1) 。
圖片“鏈表定義與存儲(chǔ)方式”中,淺藍(lán)色的存儲(chǔ)結(jié)點(diǎn)指針是占用一塊內(nèi)存地址嗎?還是和結(jié)點(diǎn)值各占一半呢?
文中的示意圖只是定性表示,定量表示需要根據(jù)具體情況進(jìn)行分析。
在列表末尾添加元素是否時(shí)時(shí)刻刻都為 O(1) ?
如果添加元素時(shí)超出列表長(zhǎng)度,則需要先擴(kuò)容列表再添加。系統(tǒng)會(huì)申請(qǐng)一塊新的內(nèi)存,并將原列表的所有元素搬運(yùn)過(guò)去,這時(shí)候時(shí)間復(fù)雜度就會(huì)是 O(n) 。
“列表的出現(xiàn)大大提升了數(shù)組的實(shí)用性,但副作用是會(huì)造成部分內(nèi)存空間浪費(fèi)”,這里的空間浪費(fèi)是指額外增加的變量如容量、長(zhǎng)度、擴(kuò)容倍數(shù)所占的內(nèi)存嗎?
這里的空間浪費(fèi)主要有兩方面含義:一方面,列表都會(huì)設(shè)定一個(gè)初始長(zhǎng)度,我們不一定需要用這么多。另一方面,為了防止頻繁擴(kuò)容,擴(kuò)容一般都會(huì)乘以一個(gè)系數(shù),比如 ×1.5 。這樣一來(lái),也會(huì)出現(xiàn)很多空位,我們通常不能完全填滿它們。
在 Python 中初始化? n = [1, 2, 3]
?后,這 3 個(gè)元素的地址是相連的,但是初始化 ?m = [2, 1, 3]
? 會(huì)發(fā)現(xiàn)它們每個(gè)元素的 id 并不是連續(xù)的,而是分別跟 ?n
? 中的相同。這些元素地址不連續(xù),那么 ?m
? 還是數(shù)組嗎?
假如把列表元素?fù)Q成鏈表節(jié)點(diǎn) ?n = [n1, n2, n3, n4, n5]
?,通常情況下這五個(gè)節(jié)點(diǎn)對(duì)象也是被分散存儲(chǔ)在內(nèi)存各處的。然而,給定一個(gè)列表索引,我們?nèi)匀豢梢栽?nbsp;O(1) 時(shí)間內(nèi)獲取到節(jié)點(diǎn)內(nèi)存地址,從而訪問(wèn)到對(duì)應(yīng)的節(jié)點(diǎn)。這是因?yàn)閿?shù)組中存儲(chǔ)的是節(jié)點(diǎn)的引用,而非節(jié)點(diǎn)本身。
與許多語(yǔ)言不同的是,在 Python 中數(shù)字也被包裝為對(duì)象,列表中存儲(chǔ)的不是數(shù)字本身,而是對(duì)數(shù)字的引用。因此,我們會(huì)發(fā)現(xiàn)兩個(gè)數(shù)組中的相同數(shù)字擁有同一個(gè) id ,并且這些數(shù)字的內(nèi)存地址是無(wú)須連續(xù)的。
C++ STL 里面的 std::list 已經(jīng)實(shí)現(xiàn)了雙向鏈表,但好像一些算法的書(shū)上都不怎么直接用這個(gè),是不是有什么局限性呢?
一方面,我們往往更青睞使用數(shù)組實(shí)現(xiàn)算法,而只有在必要時(shí)才使用鏈表,主要有兩個(gè)原因。
std::list
? 通常比 ?std::vector
? 更占用空間。另一方面,必要使用鏈表的情況主要是二叉樹(shù)和圖。棧和隊(duì)列往往會(huì)使用編程語(yǔ)言提供的 ?stack
? 和 ?queue
? ,而非鏈表。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: