數(shù)據(jù)結(jié)構(gòu)(四)靜態(tài)鏈表疼蛾、循環(huán)鏈表肛跌、雙向鏈表

靜態(tài)鏈表:數(shù)據(jù)全部存儲在數(shù)組中(和順序表一樣),但存儲位置是隨機的察郁,數(shù)據(jù)之間"一對一"的邏輯關(guān)系通過一個整形變量(稱為"游標"衍慎,和指針功能類似)維持。
例如皮钠,使用靜態(tài)鏈表存儲 {1,2,3} 的過程如下:

創(chuàng)建一個足夠大的數(shù)組稳捆,假設(shè)大小為 6,如圖1 所示:


圖1:空數(shù)組

接著麦轰,在將數(shù)據(jù)存放到數(shù)組中時乔夯,給各個數(shù)據(jù)元素配備一個整形變量,此變量用于指明各個元素的直接后繼元素所在數(shù)組中的位置下標款侵,如圖 2 所示:


圖2:靜態(tài)鏈表存儲數(shù)據(jù)

通常末荐,靜態(tài)鏈表會將第一個數(shù)據(jù)元素放到數(shù)組下標為 1 的位置(a[1])中。

圖 2 中新锈,從 a[1] 存儲的數(shù)據(jù)元素 1 開始甲脏,通過存儲的游標變量 3,就可以在 a[3] 中找到元素 1 的直接后繼元素 2妹笆;同樣块请,通過元素 a[3] 存儲的游標變量 5,可以在 a[5] 中找到元素 2 的直接后繼元素 3拳缠,這樣的循環(huán)過程直到某元素的游標變量為 0 截止(因為 a[0] 默認不存儲數(shù)據(jù)元素)墩新。

類似圖 2 這樣,通過 "數(shù)組+游標" 的方式存儲具有線性關(guān)系數(shù)據(jù)的存儲結(jié)構(gòu)就是靜態(tài)鏈表脊凰。


靜態(tài)鏈表中的節(jié)點

靜態(tài)鏈表存儲數(shù)據(jù)元素也需要自定義數(shù)據(jù)類型抖棘,至少需要包含以下2部分信息:

  • 數(shù)據(jù)域:用于存儲數(shù)據(jù)元素的值
  • 游標:其實就是數(shù)組的下表茂腥,表示直接后繼元素所在數(shù)組中的位置

備用鏈表

圖2顯示的靜態(tài)鏈表還不夠完整,靜態(tài)鏈表搜中切省,出了數(shù)據(jù)本身通過游標組成的立案表外最岗,還需要有一條連接各個空閑位置的鏈表,稱為備用鏈表

通常朝捆,備用鏈表的表頭位于數(shù)組下標為 0(a[0]) 的位置般渡,而數(shù)據(jù)鏈表的表頭位于數(shù)組下標為 1(a[1])的位置。

圖3:備用鏈表和數(shù)據(jù)鏈表

靜態(tài)鏈表中設(shè)置備用鏈表的好處是芙盘,可以清楚地知道數(shù)組中是否有空閑位置驯用,以便數(shù)據(jù)鏈表添加新數(shù)據(jù)時使用。比如儒老,若靜態(tài)鏈表中數(shù)組下標為 0 的位置上存有數(shù)據(jù)蝴乔,則證明數(shù)組已滿。

靜態(tài)鏈表的實現(xiàn)

假設(shè)使用靜態(tài)鏈表(數(shù)組長度為 6)存儲 {1,2,3}驮樊,則需經(jīng)歷以下幾個階段薇正。

在數(shù)據(jù)鏈表未初始化之前,數(shù)組中所有位置都處于空閑狀態(tài)囚衔,因此都應(yīng)被鏈接在備用鏈表上挖腰,如圖 4 所示:


圖4 未存儲前靜態(tài)鏈表的結(jié)構(gòu)

當向靜態(tài)鏈表中添加數(shù)據(jù)時,需提前從備用鏈表中摘除節(jié)點练湿,以供新數(shù)據(jù)使用猴仑。

備用鏈表摘除節(jié)點最簡單的方法是摘除 a[0] 的直接后繼節(jié)點;同樣肥哎,向備用鏈表中添加空閑節(jié)點也是添加作為 a[0] 新的直接后繼節(jié)點辽俗。因為 a[0] 是備用鏈表的第一個節(jié)點,我們知道它的位置贤姆,操作它的直接后繼節(jié)點相對容易榆苞,無需遍歷備用鏈表,耗費的時間復雜度為 O(1)霞捡。

因此坐漏,在圖 4 的基礎(chǔ)上,向靜態(tài)鏈表中添加元素 1 的過程如圖 5 所示:


圖5 靜態(tài)鏈表中添加元素1

在圖5的基礎(chǔ)上碧信,添加元素2的過程如圖6所示:


圖6:靜態(tài)鏈表中繼續(xù)添加元素2

在圖 6 的基礎(chǔ)上赊琳,繼續(xù)添加元素 3 ,過程如圖 7 所示:


圖7:靜態(tài)鏈表中繼續(xù)添加元素3

靜態(tài)鏈表與動態(tài)鏈表區(qū)別

類似于圖8 這樣的鏈表砰碴,我們稱他為“動態(tài)鏈表”躏筏。


圖8:鏈表存儲結(jié)構(gòu)

類似于圖9 這樣的鏈表,我們稱他為“靜態(tài)鏈表”呈枉。


圖9:靜態(tài)鏈表存儲數(shù)據(jù)示意圖

靜態(tài)鏈表和動態(tài)鏈表的共同點是趁尼,數(shù)據(jù)之間"一對一"的邏輯關(guān)系都是依靠指針(靜態(tài)鏈表中稱"游標")來維持埃碱,僅此而已。

靜態(tài)鏈表

使用靜態(tài)鏈表存儲數(shù)據(jù)酥泞,需要預先申請足夠大的一整塊內(nèi)存空間砚殿,也就是說,靜態(tài)鏈表存儲數(shù)據(jù)元素的個數(shù)從其創(chuàng)建的那一刻就已經(jīng)確定芝囤,后期無法更改似炎。

靜態(tài)鏈表是在固定大小的存儲空間內(nèi)隨機存儲各個元素,這就造成了靜態(tài)鏈表中需要使用另一條鏈表(一般稱為“備用鏈表”)悯姊,來記錄空閑存儲空間的位置羡藐。

這意味著使用靜態(tài)鏈表除了操作數(shù)據(jù)鏈表,還需要操作備用鏈表悯许。

動態(tài)鏈表

動態(tài)鏈表不需要預先申請內(nèi)存空間仆嗦,也就是說動態(tài)鏈表存儲數(shù)據(jù)元素的個數(shù)是不限的,想存多少就存多少岸晦。

同時欧啤,使用動態(tài)鏈表整個過程是需要操控一條存儲數(shù)據(jù)的鏈表。當添加刪除元素的時启上,只需要申請malloc或釋放free空間即可(java中不需要,gc自動回收)店印,實現(xiàn)簡單冈在。


循環(huán)鏈表

無論是靜態(tài)鏈表還是動態(tài)鏈表,有時候在解決具體問題時按摘,需要我們對其結(jié)構(gòu)進行稍微地調(diào)整包券。比如,可以把鏈表兩頭連接炫贤,使其成為一個環(huán)狀鏈表溅固,通常稱為循環(huán)鏈表。


圖10 循環(huán)鏈表

需要注意的是兰珍,雖然循環(huán)鏈表成環(huán)狀侍郭,但本質(zhì)上還是鏈表,因此在循環(huán)鏈表中掠河,依然能夠找到頭指針和首元節(jié)點等亮元。循環(huán)鏈表和普通鏈表相比,唯一的不同就是循環(huán)鏈表首尾相連唠摹,其他都完全一樣爆捞。

循環(huán)鏈表實現(xiàn)約瑟夫環(huán)

約瑟夫環(huán)問題,是一個經(jīng)典的循環(huán)鏈表問題勾拉,題意是:已知 n 個人(分別用編號 1煮甥,2盗温,3,…成肘,n 表示)圍坐在一張圓桌周圍卖局,從編號為 k 的人開始順時針報數(shù),數(shù)到 m 的那個人出列艇劫;他的下一個人又從 1 開始吼驶,還是順時針開始報數(shù),數(shù)到 m 的那個人又出列店煞;依次重復下去蟹演,直到圓桌上剩余一個人。

如圖 11 所示顷蟀,假設(shè)此時圓周周圍有 5 個人酒请,要求從編號為 3 的人開始順時針數(shù)數(shù),數(shù)到 2 的那個人出列:


圖11:循環(huán)鏈表實現(xiàn)約瑟夫環(huán)

出列順序依次為:

  • 編號為 3 的人開始數(shù) 1鸣个,然后 4 數(shù) 2羞反,所以 4 先出列;
  • 4 出列后囤萤,從 5 開始數(shù) 1昼窗,1 數(shù) 2,所以 1 出列涛舍;
  • 1 出列后澄惊,從 2 開始數(shù) 1,3 數(shù) 2富雅,所以 3 出列掸驱;
  • 3 出列后,從 5 開始數(shù) 1没佑,2 數(shù) 2毕贼,所以 2 出列;
  • 最后只剩下 5 自己蛤奢,所以 5 勝出鬼癣。

約瑟夫環(huán)問題有多種變形,比如順時針轉(zhuǎn)改為逆時針等远剩,雖然問題的細節(jié)有多種變數(shù)扣溺,但解決問題的中心思想是一樣的,即使用循環(huán)鏈表瓜晤。

//解決了約瑟夫環(huán)問題锥余,但未使用循環(huán)鏈表
private static void senario(int total, int cycle) {
        List<Integer> all = new LinkedList<Integer>();
        for(int i = 1;i <= total;i++){
            all.add(i);
        }
        int i = 0;
        for(int n = 1;n < total;n++){
            i = (i + cycle -1) % all.size();
            System.out.print(all.get(i)+(n==total-1?"\n":" "));
            all.remove(i);
        }
        System.out.printf("幸存者為:%d號 (人數(shù)=%d, 周期=%d, (默認從第1個人開始、正向報數(shù)))\n", 
                all.get(0), total, cycle);
    }

循環(huán)鏈表和動態(tài)鏈表唯一不同在于它的首尾連接痢掠,這也注定了在使用循環(huán)鏈表時驱犹,附帶最多的操作就是遍歷鏈表嘲恍。

在遍歷的過程中,尤其要注意循環(huán)鏈表雖然首尾相連雄驹,但并不表示該鏈表沒有第一個節(jié)點和最后一個結(jié)點佃牛。所以,不要隨意改變頭指針的指向医舆。


雙向鏈表

無論是動態(tài)鏈表還是靜態(tài)鏈表俘侠,表中各節(jié)點中都只包含一個指針(游標),且都統(tǒng)一指向直接后繼節(jié)點蔬将,通常稱這類鏈表為單向鏈表(或單鏈表)爷速。

如果算法中需要大量地找某指定結(jié)點的前趨結(jié)點,鏈表并不是效率最優(yōu)的存儲結(jié)構(gòu)霞怀,為了能夠高效率解決"從后往前"的問題惫东,可以使用雙向鏈表(簡稱雙鏈表)。


圖12:雙向鏈表結(jié)構(gòu)示意圖

雙向毙石,指的是各節(jié)點之間的邏輯關(guān)系是雙向的廉沮,但通常頭指針只設(shè)置一個,除非實際情況需要徐矩。

從圖 11 中可以看到滞时,雙向鏈表中各節(jié)點包含以下 3 部分信息(如圖 12 所示):

  • 指針域:用于指向直接前驅(qū)節(jié)點
  • 數(shù)據(jù)域:用于存儲數(shù)據(jù)元素
  • 指針域:用于存儲直接后繼節(jié)點
圖12:雙向鏈表節(jié)點構(gòu)成

雙向鏈表的基本操作

添加至表頭

假設(shè)新元素節(jié)點為 temp,表頭節(jié)點為 head滤灯,則需要做以下 2 步操作即可

  • temp.next = head; head.prior=temp;
  • 將頭指針重新指向temp
圖13 添加元素至雙鏈表的表頭
添加至表中

新節(jié)點先與直接后繼節(jié)點建立雙層邏輯關(guān)系
新節(jié)點的直接前驅(qū)節(jié)點與新節(jié)點建立雙層邏輯關(guān)系

圖14:添加元素至雙鏈表表中
添加至表尾

找到雙鏈表最后一個節(jié)點
讓新節(jié)點與最后一個節(jié)點進行雙層邏輯關(guān)系

圖15:雙鏈表尾部添加數(shù)據(jù)
雙鏈表刪除漂洋,查找,更改

與單鏈表幾乎無異力喷。


雙向循環(huán)鏈表

雙向鏈表也可以進行首尾連接,構(gòu)成雙向循環(huán)鏈表演训。如圖 16 所示:


圖16:雙向循環(huán)鏈表示意圖

當問題中涉及到需要 "循環(huán)往復" 地遍歷表中數(shù)據(jù)時弟孟,就需要使用雙向循環(huán)鏈表。例如样悟,前面章節(jié)我們對約瑟夫環(huán)問題進行了研究拂募,其實約瑟夫環(huán)問題有多種玩法,每次順時針報數(shù)后窟她,下一輪可以逆時針報數(shù)陈症,然后再順時針......一直到剩下最后一個人。解決這個問題就需要使用雙向循環(huán)鏈表結(jié)構(gòu)震糖。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末录肯,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子吊说,更是在濱河造成了極大的恐慌论咏,老刑警劉巖优炬,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異厅贪,居然都是意外死亡蠢护,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進店門养涮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葵硕,“玉大人,你說我怎么就攤上這事贯吓⌒赴迹” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵宣决,是天一觀的道長蘸劈。 經(jīng)常有香客問我,道長尊沸,這世上最難降的妖魔是什么威沫? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮洼专,結(jié)果婚禮上棒掠,老公的妹妹穿的比我還像新娘。我一直安慰自己屁商,他們只是感情好烟很,可當我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蜡镶,像睡著了一般雾袱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上官还,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天芹橡,我揣著相機與錄音,去河邊找鬼望伦。 笑死林说,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的屯伞。 我是一名探鬼主播腿箩,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼劣摇!你這毒婦竟也來了珠移?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎剑梳,沒想到半個月后唆貌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡垢乙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年锨咙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片追逮。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡酪刀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出钮孵,到底是詐尸還是另有隱情骂倘,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布巴席,位于F島的核電站历涝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏漾唉。R本人自食惡果不足惜荧库,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赵刑。 院中可真熱鬧分衫,春花似錦、人聲如沸般此。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽铐懊。三九已至邀桑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間科乎,已是汗流浹背概漱。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留喜喂,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓竿裂,卻偏偏與公主長得像玉吁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子腻异,可洞房花燭夜當晚...
    茶點故事閱讀 43,494評論 2 348

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