線性表結構 陣列 雜湊表與可變長度陣列

2022-09-12 01:15:17 字數 3349 閱讀 9090

前言:

陣列(array)是一種線性表資料結構,它用一組連續的記憶體空間,來儲存一組具有相同型別的資料。如果你學習過 c 語言,應該對這段定義很熟悉,但是在 php 這種動態語言中,因為陣列底層是通過雜湊表(後面我們會介紹這個資料結構)實現的,所以功能異常強大,這段常規的陣列定義在 php 中並不成立,php 的陣列可以儲存任何型別資料,如果與 j**a 對比的話,php 陣列整合了 j**a 的陣列、list、set、map 於一身,所以寫**的效率比 j**a 高了幾個量級。

拋開 php 或 j**ascript 這種動態語言,對於傳統的陣列,比如 c 語言和 j**a 中的陣列,在使用之前都需要宣告陣列儲存資料的型別和陣列的大小,陣列的優點是可以通過下標值隨機訪問陣列內的任何元素,演算法複雜度是 o(1),非常高效,但是缺點是刪除/插入元素比較費勁,以刪除為例,需要在刪除某個元素後,將後續元素都往前移一位,如果是插入,則需要將插入位置之後的元素都往後移,所以對陣列的插入/刪除而言,演算法複雜度是 o(n),當然了,這個是針對 c / j**a 這種語言而言,php 不受此約束,因為它不是傳統一樣上的陣列。

雜湊表(hash table,也叫雜湊表)是一種通過將鍵(key)對映為值(value)從而實現快速查詢的資料結構。

也就是說,它通過計算乙個關於鍵值的函式,將所需查詢的資料對映到表中乙個位置來訪問記錄,這加快了查詢速度。這個對映函式稱做雜湊函式,存放記錄的陣列稱做雜湊表。

乙個通俗的例子是,為了查詢**簿中某人的號碼,可以建立乙個按照人名首字母順序排列的表(即建立人名 x 到首字母f(x)的乙個函式關係),在首字母為w的表中查詢「王」姓的**號碼,顯然比直接查詢就要快得多。這裡使用人名作為關鍵字,「取首字母」是這個例子中雜湊函式的函式法則f(),存放首字母的表對應雜湊表。關鍵字和函式法則理論上可以任意確定。

基本概念

處理衝突

1)開放定址(open addressing)法:

所有的元素都在雜湊表中,每乙個表項或包含動態集合的乙個元素,或包含nil。這種方法中雜湊表可能被填滿,以致於不能插入任何新的元素。在開放定址法中,當要插入乙個元素時,可以連續地檢查或探測雜湊表的各項,直到有乙個空槽來放置待插入的關鍵字為止。

有三種技術用於開放定址法:線性探測、二次探測以及雙重探測。

<1>線性探測

給定乙個普通的雜湊函式h':u —>,線性探測方法採用的雜湊函式為:h(k,i) = (h'(k)+i)mod m,i=0,1,....,m-1  

探測時從i=0開始,首先探查t[h'(k)],然後依次探測t[h'(k)+1],…,直到t[h'(k)+m-1],此後又迴圈到t[0],t[1],…,直到探測到t[h'(k)-1]為止。探測過程終止於三種情況: 

(1)若當前探測的單元為空,則表示查詢失敗(若是插入則將key寫入其中); 

(2)若當前探測的單元中含有key,則查詢成功,但對於插入意味著失敗; 

(3)若探測到t[h'(k)-1]時仍未發現空單元也未找到key,則無論是查詢還是插入均意味著失敗(此時表滿)。

線性探測方法較容易實現,但是存在一次群集問題,即連續被占用的槽的序列變的越來越長。採用例子進行說明線性探測過程,已知一組關鍵字為(26,36,41,38,44,15,68,12,6,51),用除餘法構造雜湊函式,初始情況如下圖所示:

<2>二次探測

二次探測法的探查序列是:h(k,i) =(h'(k)+i*i)%m ,0≤i≤m-1 。初次的探測位置為t[h'(k)],後序的探測位置在次基礎上加乙個偏移量,該偏移量以二次的方式依賴於i。該方法的缺陷是不易探查到整個雜湊空間。

<3>雙重雜湊

該方法是開放定址的最好方法之一,因為其產生的排列具有隨機選擇的排列的許多特性。採用的雜湊函式為:h(k,i)=(h1(k)+ih2(k)) mod m。其中h1和h2為輔助雜湊函式。初始探測位置為t[h1(k)],後續的探測位置在此基礎上加上偏移量h2(k)模m。

將所有關鍵字為同義詞的結點鏈結在同乙個鍊錶中。若選定的雜湊表長度為m,則可將雜湊表定義為乙個由m個頭指標組成的指標陣列t[0..m-1]。凡是雜湊位址為i的結點,均插入到以t[i]為頭指標的單鏈表中。t中各分量的初值均應為空指標。在拉鍊法中,裝填因子α可以大於1,但一般均取α≤1。

最終結果如下圖所示:

3)除了在鍊錶中儲存衝突元素,還可以將衝突元素儲存在二叉搜尋樹中。

這會使得最壞情況下的執行時間達到o(logn)。實際上,除非出現非常不均勻的分布,否則很少採用這種方法。

時間複雜度

如果衝突發生很多次,最壞的情況下的時間複雜度是o(n) ,其中n是鍵的數量。

但是我們通常假設乙個不錯的實現方式會將衝突數量保持在最低水平,在此情況下,時間複雜度是o(1)

另一種方法是通過平衡二叉搜尋樹實現雜湊表。該方法的查詢時間是o(logn)。該方法的好處是用到的空間可能更少,因為我們不再需要分配乙個大陣列,還可以按照鍵的順序進行迭代訪問。

構造雜湊函式的方法

雜湊函式能使對乙個資料序列的訪問過程更加迅速有效,通過雜湊函式,資料元素將被更快定位。

拉賓-卡普(rabin-karp)子串查詢

arraylist可變長度陣列

在一部分語言中,陣列(這種情況下通常會被稱作鍊錶)可以自動改變長度。陣列或鍊錶會隨著新加入元素而增加長度。

而在另一部分語言中,比如j**a,陣列的長度是固定的,建立陣列時,長度即被確定了。

當你需要類似於陣列、同時提供動態長度的資料結構時,經常會用到arraylist。

arraylist是一種按需動態調整大小的陣列,資料訪問時間為o(1)。

一種典型的實現方法是在陣列存滿時將其擴容兩倍。每次擴容用時為o(n),不過這種操作頻次極少,因此均攤下來訪問時間仍為o(1)。

arraylistmerge(string words, string more)
為什麼均攤訪問時間是o(1)

假設你有乙個長度為n的陣列,

當我們將陣列元素增加到k時,陣列之前的大小為其一半,此時需要複製k/2個元素。

第一次擴容:複製1個元素

第二次擴容:複製2個元素

之前的擴容:複製n/4個元素

最終擴容:複製n/2個元素

,因此,插入n個元素總共需複製1+2+...+n/4+n/2,總計剛好小於n次(很接近n)

,因此,插入n個元素總計用時為o(n)。平均下來每次插入操作用時為o(1),儘管某些插入操作在最壞情況下用時為o(n)。

線性表長度和陣列長度

陣列的長度是指存放線性表的儲存空間的長度,儲存分配後這個量一般是不變的。有個別同學可能會問,陣列的大小一定不可以變的嗎?我怎麼看到有書中談到可以動態分配的一維陣列。是的,一般高階語言,比如c,vb,c 都可以用程式設計手段實現動態記憶體分配陣列,不過著會帶來效能上的 損耗。線性表的長度是線性表中元素...

線性表之線性表與陣列的區別

線性結構是最簡單也是最常用的資料結構之一。線性結構的特點 在資料元素有限集中,除第乙個元素無直接前驅,最後乙個元素無直接後續以外,每個資料元素有且僅有乙個直接前驅元素和乙個直接後繼元素。如果線性表中的資料元素時物件時,陣列存放的是物件的引用,即線性表中所有資料元素的物件引用是存放在一組連續的位址空間...

線性表(3) 雜湊鍊錶

pragma once ifndef key value pair h define key value pair h template class key value pair 有參構造 key value pair key type m key,value type m value key m ...