摘要:整個(gè)程序在存入是數(shù)據(jù)大于數(shù)組長(zhǎng)度的時(shí)候才會(huì)發(fā)生數(shù)組的刪除操作。數(shù)組作為非常基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu),通過(guò)下標(biāo)隨機(jī)訪問(wèn)數(shù)組元素又是其非常基礎(chǔ)的編程操作,效率的優(yōu)化就要盡可能做到極致。
一、前言本系列所有文章的代碼都是用JavaScript實(shí)現(xiàn),之所以用JavaScript實(shí)現(xiàn)是因?yàn)樗梢灾苯釉跒g覽器宿主中運(yùn)行代碼,即在瀏覽器中按f12打開(kāi)控制臺(tái),選擇console按鈕,在下面空白的文本框把本例的代碼黏貼上去回車(chē)即可運(yùn)行。方便各位同學(xué)學(xué)習(xí)和調(diào)試。
數(shù)組這個(gè)概念相信各位同學(xué)在日常寫(xiě)代碼的時(shí)候肯定會(huì)經(jīng)常用到,我們通常用數(shù)組作為容器來(lái)存儲(chǔ)數(shù)據(jù)。基本上每一種編程語(yǔ)言都有這種數(shù)據(jù)結(jié)構(gòu),它是一個(gè)基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu),下面將仔細(xì)的講解數(shù)組的原理及應(yīng)用。
二、數(shù)組概念什么是數(shù)組呢?按照專業(yè)的名詞解釋,數(shù)組是一種線性表數(shù)據(jù)結(jié)構(gòu),它用連續(xù)的內(nèi)存空間來(lái)存儲(chǔ)一組具有相同類(lèi)型的數(shù)據(jù)。從定義里我們可以看到幾個(gè)關(guān)鍵詞,分別是線性表(Linear List)和連續(xù)的內(nèi)存空間和相同類(lèi)型的數(shù)據(jù)。
1.線性表線性表的意思其實(shí)就是數(shù)據(jù)排成像一條線一樣的結(jié)構(gòu)。每個(gè)線性表上的數(shù)據(jù)最多只有前和后兩個(gè)方向。其實(shí)除了數(shù)組,鏈表、隊(duì)列、棧等都是線性表結(jié)構(gòu)。而與線性表對(duì)立的則是非線性表 ,比如二叉樹(shù)、圖、堆等。之所以叫非線性,是因?yàn)榉蔷€性表中的數(shù)據(jù)并不是簡(jiǎn)單的前后關(guān)系。
2.連續(xù)的內(nèi)存空間和相同類(lèi)型的數(shù)據(jù)當(dāng)我們聲明一個(gè)數(shù)組的時(shí)候,計(jì)算機(jī)就會(huì)為數(shù)組分配一個(gè)連續(xù)的內(nèi)存空間。假如我們聲明的數(shù)組長(zhǎng)度是10,在數(shù)組中存儲(chǔ)的元素都說(shuō)int類(lèi)型的數(shù)據(jù),如果內(nèi)存的首地址為1000,則計(jì)算機(jī)為數(shù)組分配了1000~1039的連續(xù)內(nèi)存空間。數(shù)組和鏈表不同的一點(diǎn)就是數(shù)組存儲(chǔ)的都是連續(xù)的內(nèi)存空間,而鏈表存儲(chǔ)的都說(shuō)不連續(xù)的內(nèi)存空間,所以如果一個(gè)計(jì)算機(jī)的內(nèi)存只有1G的情況下,我們聲明了一個(gè)占用1G內(nèi)存的數(shù)組很有可能會(huì)導(dǎo)致內(nèi)存溢出,因?yàn)橛锌赡軆?nèi)存里有不連續(xù)的空間,而聲明1G內(nèi)存的鏈表則不會(huì)出現(xiàn)這種情況。
結(jié)合上面所說(shuō)的兩點(diǎn),數(shù)組由于是線性的并且是連續(xù)的內(nèi)存空間,隨機(jī)訪問(wèn)的時(shí)候時(shí)間復(fù)雜度非常的快,為O(1)。數(shù)組的隨機(jī)訪問(wèn)并不需要遍歷本身,只需要知道下標(biāo)就可以得出值。但是有利也有弊,與快速的查詢相反的就是在插入和刪除的時(shí)候所要耗費(fèi)更多的復(fù)雜度。在這里需要提一點(diǎn)的是,數(shù)組是隨機(jī)查找的時(shí)候時(shí)間復(fù)雜度為O(1),不能籠統(tǒng)的認(rèn)為數(shù)組在執(zhí)行查找操作的時(shí)候時(shí)間復(fù)雜度為O(1),如果你用二分查找來(lái)對(duì)數(shù)組進(jìn)行查找操作,耗費(fèi)的時(shí)間復(fù)雜度為O(logn)。
三、數(shù)組的插入和刪除上面提到數(shù)組由于連續(xù)的內(nèi)存空間導(dǎo)致了在執(zhí)行插入和刪除操作的時(shí)候占用大量的性能。首先我們來(lái)說(shuō)一下插入操作在數(shù)組的執(zhí)行過(guò)程。
假設(shè)我們聲明了一個(gè)數(shù)組長(zhǎng)度為n,如果我們要插入的數(shù)組在數(shù)組第m個(gè)位置的時(shí)候,為了能夠讓數(shù)據(jù)成功的插入下標(biāo)m當(dāng)中,我們要把m到n這一部分的數(shù)據(jù)往后移一位,然后把數(shù)據(jù)放入下標(biāo)m當(dāng)中。那如果數(shù)據(jù)是要插入到數(shù)組最后面的話,那時(shí)間復(fù)雜度也只是O(1),如果是在開(kāi)頭插入的話時(shí)間復(fù)雜度則為O(n),因?yàn)槊總€(gè)位置的概率都是一樣的,所以我們可以得到平均時(shí)間復(fù)雜度為:。
如果一個(gè)數(shù)組是有序的,我們?yōu)榱吮3謹(jǐn)?shù)組的有序性,的確只能用上述的方法來(lái)解。但是如果數(shù)組是無(wú)序的,為了避免大規(guī)模的數(shù)據(jù)移動(dòng),我們可以把當(dāng)前下標(biāo)m的數(shù)據(jù)放到最后面,把我們的值放入到下標(biāo)m當(dāng)中。利用這個(gè)方法我們可以將時(shí)間復(fù)雜度降到O(1),性能將極大的提升。
同理在刪除中,如果我們要?jiǎng)h除下標(biāo)為m的元素,為了內(nèi)存的連續(xù)性,也需要把m到n后面的數(shù)據(jù)往前移,不然就不連續(xù)。刪除的最好時(shí)間復(fù)雜度是O(1),即刪除的是結(jié)尾的數(shù)據(jù)的時(shí)候。最壞時(shí)間復(fù)雜度則為O(n),即在開(kāi)頭的數(shù)據(jù)被刪除。它的平均時(shí)間復(fù)雜度的公式也和上面插入的公式一樣,結(jié)果為O(n)。
那么如果我們對(duì)數(shù)組進(jìn)行頻繁的刪除操作,程序的性能將會(huì)極大的降低,有時(shí)候辦法可以解決呢?這個(gè)時(shí)候我們可以借助JVM標(biāo)記清除垃圾回收算法來(lái)實(shí)現(xiàn)。當(dāng)執(zhí)行刪除操作的時(shí)候我們并不是真的把數(shù)組里的元素給刪除掉,而是給該元素標(biāo)記一個(gè)刪除狀態(tài),等到后面數(shù)組沒(méi)有更多的空間存儲(chǔ)數(shù)組的時(shí)候再一次性的執(zhí)行刪除操作,極大地減少數(shù)據(jù)的遷移。下面用JavaScript代碼來(lái)簡(jiǎn)單的實(shí)現(xiàn)一下:
var arr = new Array(10)
var count = 0
function insertArr(obj) {
if (typeof arr[9] === "object") {
var tempArr = []
for (var a = 0; a < arr.length; a++) {
if (!arr[a].removeSign) {
arr[a].index = tempArr.length
arr[a].removeSign = false
tempArr.push(arr[a])
}
}
arr = tempArr
count = tempArr.length
if (arr.length === 10) {
console.error("數(shù)組越界")
return
}
}
arr[count] = {
value: obj.value,
removeSign: false,
index: count
}
count++
}
function removeArr(index){
if (arr.length === 0) {
console.error("數(shù)組長(zhǎng)度為0,不能刪除元素")
return
}
else if (index > arr.length) {
console.error("數(shù)組越界")
return
}
// 如果當(dāng)前的已標(biāo)記為true則查看下一個(gè)元素是否為true,如果不是則標(biāo)記為true,是的話則繼續(xù)遞歸
if (arr[index].removeSign) {
return removeArr(++index)
}
arr[index].removeSign = true
}