線性表--鏈?zhǔn)酱鎯Y(jié)構(gòu)--單鏈表

線性表--鏈?zhǔn)酱鎯Y(jié)構(gòu)--單鏈表

一、定義

1.特點(diǎn):

  • 用一組任意的存儲單元存儲線性表的數(shù)據(jù)元素,這組存儲單元可以存在內(nèi)存中未被占用的任意位置。
  • 比起順序存儲結(jié)構(gòu)每個數(shù)據(jù)元素只需要存儲一個位置就可以了〈亟粒現(xiàn)在鏈?zhǔn)酱鎯Y(jié)構(gòu)中,除了要存儲數(shù)據(jù)元素信息外软吐,還要存儲它的后繼元素的存儲地址(指針)瘩将。
  • 也就是說除了存儲其本身的信息外,還需存儲一個指示其直接后繼的存儲位置的信息凹耙。

2.定義:

  • 我們把存儲數(shù)據(jù)元素信息的域稱為數(shù)據(jù)域姿现,把存儲直接后繼位置的域稱為指針域。指針域中存儲的信息稱為指針或鏈肖抱。這兩部分信息組成數(shù)據(jù)元素稱為存儲映像备典,稱為結(jié)點(diǎn)(Node)。
  • n個結(jié)點(diǎn)鏈接成一個鏈表虐沥,即為線性表(a1, a2, a3, …, an)的鏈?zhǔn)酱鎯Y(jié)構(gòu)熊经。
  • 因?yàn)榇随湵淼拿總€結(jié)點(diǎn)中只包含一個指針域泽艘,所以叫做單鏈表欲险。
單鏈表.png

我們把鏈表中的第一個結(jié)點(diǎn)的存儲位置叫做頭指針镐依,最后一個結(jié)點(diǎn)指針為空(NULL)。

單鏈表結(jié)構(gòu).png

頭結(jié)點(diǎn)的數(shù)據(jù)域一般不存儲任何信息

3.頭指針與頭結(jié)點(diǎn)的異同

頭指針

  • 頭指針是指鏈表指向第一個結(jié)點(diǎn)的指針天试,若鏈表有頭結(jié)點(diǎn)槐壳,則是指向頭結(jié)點(diǎn)的指針。
  • 頭指針具有標(biāo)識作用喜每,所以常用頭指針冠以鏈表的名字(指針變量的名字)务唐。
  • 無論鏈表是否為空,頭指針均不為空带兜。
  • 頭指針是鏈表的必要元素枫笛。

頭結(jié)點(diǎn)

  • 頭結(jié)點(diǎn)是為了操作的統(tǒng)一和方便而設(shè)立的,放在第一個元素的結(jié)點(diǎn)之前刚照,其數(shù)據(jù)域一般無意義(但也可以用來存放鏈表的長度)刑巧。
  • 有了頭結(jié)點(diǎn),對在第一元素結(jié)點(diǎn)前插入結(jié)點(diǎn)和刪除第一結(jié)點(diǎn)起操作與其它結(jié)點(diǎn)的操作就統(tǒng)一了无畔。
  • 頭結(jié)點(diǎn)不一定是鏈表的必須要素啊楚。

二、單鏈表存儲結(jié)構(gòu)

單鏈表圖例.jpg
空鏈表圖例.jpg

C語言中可以用結(jié)構(gòu)指針來描述單鏈表:

typedef struct Node
{
    ElemType data;      // 數(shù)據(jù)域
    struct Node* Next;  // 指針域
} Node;
    typedef struct Node* LinkList;

結(jié)點(diǎn)由存放數(shù)據(jù)元素的數(shù)據(jù)域和存放后繼結(jié)點(diǎn)地址的指針域組成浑彰。

例1:假設(shè)p是指向線性表第i個元素的指針恭理,則該結(jié)點(diǎn)ai的數(shù)據(jù)域我們可以用p->data的值是一個數(shù)據(jù)元素,結(jié)點(diǎn)ai的指針域可以用p->next來表示郭变,p->next的值是一個指針颜价。

那么p->next指向第i+1個元素。也就是指向ai+1的指針诉濒。

例2:如果p->data = ai周伦,那么p->next->data = ?
p->next->data = ai+1。

三循诉、單鏈表的操作--讀取横辆、插入和刪除

1.讀取

對于單鏈表實(shí)現(xiàn)獲取第i個元素的數(shù)據(jù)的操作GetElem,在算法上相對要麻煩一些:

獲得鏈表第i個數(shù)據(jù)的算法思路:

  1. 聲明一個結(jié)點(diǎn)p指向鏈表第一個結(jié)點(diǎn)茄猫,初始化j從1開始狈蚤;
  2. 當(dāng)j<i時,就遍歷鏈表划纽,讓p的指針向后移動脆侮,不斷指向一下結(jié)點(diǎn),j+1勇劣;
  3. 若到鏈表末尾p為空靖避,則說明第i個元素不存在潭枣;
  4. 否則查找成功,返回結(jié)點(diǎn)p的數(shù)據(jù)幻捏。

使用代碼實(shí)現(xiàn)GetElem.c:

/* 初始條件:順序線性表L已存在盆犁,1<=i<=ListLength(L) */
/* 操作結(jié)果:用e返回L中第i個數(shù)據(jù)元素的值 */

Status GetElem( LinkList L, int i, ElemType *e )
{
    int j;
    LinkList p;

    p = L->next;
    j = 1;

    while( p && j<i )
    {
        p = p->next;
        ++j;
    }

    if( !p || j>i )
    {
        return ERROR;
    }

    *e = p->data;

    return OK;
}

就是從頭開始找,直到第i個元素為止:

  • 由于這個算法的時間復(fù)雜度取決于i的位置篡九,當(dāng)i=1時谐岁,則不需要遍歷,而i=n時則遍歷n-1次才可以榛臼。因此最壞情況的時間復(fù)雜度為O(n)伊佃。
  • 由于單鏈表的結(jié)構(gòu)中沒有定義表長,所以不能實(shí)現(xiàn)知道要循環(huán)多少次沛善,因此也就不方便使用for來控制循環(huán)航揉。
  • 其核心思想叫做“工作指針后移”,這其實(shí)也是很多算法的常用技術(shù)金刁。

2.插入

單鏈表的插入:假設(shè)存儲元素e的結(jié)點(diǎn)為s帅涂,要實(shí)現(xiàn)結(jié)點(diǎn)p、p->next和s之間邏輯關(guān)系的變化胀葱,如圖:

結(jié)點(diǎn)之間的邏輯關(guān)系.jpg

要將結(jié)點(diǎn)s插入到ai和ai+1之間漠秋,只需要讓s->next和p->next的指針做一點(diǎn)改變。
s->next = p->next;
p->next = s;
如圖:

插入操作.jpg

這兩句代碼的順序可不可以交換過來抵屿?
先p->next = s;
再s->next = p->next;

如果先執(zhí)行p->next的話會先被覆蓋為s的地址庆锦,那么s->next = p->next其實(shí)就等于s->next = s了。

所以這兩句是無論如何不能弄反的轧葛。

單鏈表第i個數(shù)據(jù)插入結(jié)點(diǎn)的算法思路:

  1. 聲明一結(jié)點(diǎn)p指向鏈表頭結(jié)點(diǎn)搂抒,初始化j從1開始;
  2. 當(dāng)j<1時尿扯,就遍歷鏈表求晶,讓p的指針向后移動,不斷指向下一結(jié)點(diǎn)衷笋,j累加1芳杏;
  3. 若到鏈表末尾p為空,則說明第i個元素不存在辟宗;
  4. 否則查找成功爵赵,在系統(tǒng)中生成一個空結(jié)點(diǎn)s;
  5. 將數(shù)據(jù)元素e賦值給s->data泊脐;
  6. 單鏈表的插入剛才兩個標(biāo)準(zhǔn)語句空幻;
  7. 返回成功。

代碼實(shí)現(xiàn):

/* 初始條件:順序線性表L已存在容客,1<=i<=ListLength(L) */
/* 操作結(jié)果:在L中第i個位置之前插入新的數(shù)據(jù)元素e秕铛,L的長度加1 */

Status ListInsert(LinkList *L, int i, ElemType e)
{
    int j;
    LinkList p, s;

    p = *L;
    j = 1;

    while( p && j<i )   // 用于尋找第i個結(jié)點(diǎn)
    {
        p = p->next;
        j++;
    }

    if( !p || j>i )
    {
        return ERROR;
    }

    s = (LinkList)malloc(sizeof(Node));
    s->data = e;

    s->next = p->next;
    p->next = s;

    return OK;
}

3.刪除

單鏈表的刪除.png

假設(shè)元素a2的結(jié)點(diǎn)為q约郁,要實(shí)現(xiàn)結(jié)點(diǎn)q刪除單鏈表的操作,其實(shí)就是將它的前繼結(jié)點(diǎn)的指針繞過指向后繼結(jié)點(diǎn)即可但两。

那我們所要做的鬓梅,實(shí)際上就是一步:

可以這樣:p->next = p->next->next;
也可以是:q=p->next; p->next=q->next;

單鏈表第i個數(shù)據(jù)刪除結(jié)點(diǎn)的算法思路:

  1. 聲明結(jié)點(diǎn)p指向鏈表第一個結(jié)點(diǎn),初始化j=1镜遣;
  2. 當(dāng)j<1時己肮,就遍歷鏈表士袄,讓P的指針向后移動悲关,不斷指向下一個結(jié)點(diǎn),j累加1娄柳;
  3. 若到鏈表末尾p為空寓辱,則說明第i個元素不存在;
  4. 否則查找成功赤拒,將欲刪除結(jié)點(diǎn)p->next賦值給q秫筏;
  5. 單鏈表的刪除標(biāo)準(zhǔn)語句p->next = q->next;
  6. 將q結(jié)點(diǎn)中的數(shù)據(jù)賦值給e挎挖,作為返回这敬;
  7. 釋放q結(jié)點(diǎn)。

代碼實(shí)現(xiàn):

/* 初始條件:順序線性表L已存在蕉朵,1<=i<=ListLength(L) */
/* 操作結(jié)果:刪除L的第i個數(shù)據(jù)元素崔涂,并用e返回其值,L的長度-1 */

Status ListDelete(LinkList *L, int i, ElemType *e)
{
    int j;
    LinkList p, q;

    p = *L;
    j = 1;

    while( p->next && j<i )
    {
        p = p->next;
        ++j;
    }

    if( !(p->next) || j>i )
    {
        return ERROR;
    }

    q = p->next;
    p->next = q->next;

    *e = q->data;
    free(q);

    return OK;
}

效率問題:

無論是單鏈表插入還是刪除算法始衅,它們其實(shí)都是由兩個部分組成:

第一部分就是遍歷查找第i個元素冷蚂,
第二部分就是實(shí)現(xiàn)插入和刪除元素。

它們的時間復(fù)雜度都是O(n)汛闸。

  • 如果在我們不知道第i個元素的指針位置蝙茶,單鏈表數(shù)據(jù)結(jié)構(gòu)在插入和刪除操作上,與線性表的順序存儲結(jié)構(gòu)是沒有太大優(yōu)勢的诸老。
  • 但如果隆夯,我們希望從第i個位置開始,插入連續(xù)10個元素别伏,對于順序存儲結(jié)構(gòu)意味著蹄衷,每一次插入都需要移動n-i個位置,所以每次都是O(n)畸肆。
  • 而單鏈表宦芦,我們只需要在第一次時,找到第i個位置的指針轴脐,此時為O(n)调卑,接下來只是簡單地通過賦值移動指針而已抡砂,時間復(fù)雜度都是O(1)。
  • 顯然恬涧,對于插入或刪除數(shù)據(jù)越頻繁的操作注益,單鏈表的效率優(yōu)勢就越是明顯

四、單鏈表的整表創(chuàng)建

單鏈表的存儲結(jié)構(gòu)溯捆,它的數(shù)據(jù)可以是分散在內(nèi)存各個角落的丑搔,他的增長也是動態(tài)的。對于每個鏈表來說提揍,它所占用空間的大小和位置是不需要預(yù)先分配劃定的啤月,可以根據(jù)系統(tǒng)的情況和實(shí)際的需求即時生成。

單鏈表的整表創(chuàng)建:
創(chuàng)建單鏈表的過程是一個動態(tài)生成鏈表的過程劳跃,從“空表”的初始狀態(tài)起谎仲,依次建立各元素結(jié)點(diǎn)并逐個插入鏈表。

單鏈表整表創(chuàng)建的算法思路如下:

  1. 聲明一結(jié)點(diǎn)p和計數(shù)器變量i;
  2. 初始化一空鏈表L;
  3. 讓L的頭結(jié)點(diǎn)的指針指向NULL疫鹊,即建立一個帶頭結(jié)點(diǎn)的單鏈表;
  4. 循環(huán)實(shí)現(xiàn)后繼結(jié)點(diǎn)的賦值和插入辙诞。

①頭插法建立單鏈表
頭插法從一個空表開始,生成新結(jié)點(diǎn)轻抱,讀取數(shù)據(jù)存放到新結(jié)點(diǎn)的數(shù)據(jù)域中飞涂,然后將新結(jié)點(diǎn)插入到當(dāng)前鏈表的表頭上,直到結(jié)束為止十拣。
就是把新加進(jìn)的元素放在表頭后的第一個位置:

  1. 先讓新節(jié)點(diǎn)的next指向頭節(jié)點(diǎn)之后
  2. 然后讓表頭的next指向新節(jié)點(diǎn)

代碼實(shí)現(xiàn):

/* 頭插法建立單鏈表示例 */

void CreateListHead(LinkList *L, int n)
{
    LinkList p;
    int i;

    srand(time(0));   // 初始化隨機(jī)數(shù)種子

    *L = (LinkList)malloc(sizeof(Node));
    (*L)->next = NULL;

    for( i=0; i < n; i++ )
    {
        p = (LinkList)malloc(sizeof(Node));  // 生成新結(jié)點(diǎn)
        p->data = rand()%100+1;
        p->next = (*L)->next;
        (*L)->next = p;
    }
}
頭插法演示.png

②尾插法建立單鏈表
頭插法建立鏈表雖然算法簡單封拧,但生成的鏈表中結(jié)點(diǎn)的次序和輸入的順序相反。
我們可以把思維逆過來:把新結(jié)點(diǎn)都插入到最后夭问,這種算法稱之為尾插法泽西。

尾插法演示.png

代碼實(shí)現(xiàn):

/* 尾插法建立單鏈表演示 */

void CreateListTail(LinkList *L, int n)
{
    LinkList p, r;
    int i;

    srand(time(0));
    *L = (LinkList)malloc(sizeof(Node));
    r = *L;

    for( i=0; i < n; i++ )
    {
        p = (Node *)malloc(sizeof(Node));
        p->data = rand()%100+1;
        r->next = p;
        r = p;                 // 
    }

    r->next = NULL;
}

五、單鏈表的整表刪除

當(dāng)我們不打算使用這個單鏈表時缰趋,我們需要把它銷毀捧杉;其實(shí)也就是在內(nèi)存中將它釋放掉,以便于留出空間給其他程序或軟件使用秘血。

單鏈表整表刪除的算法思路如下:

  1. 聲明結(jié)點(diǎn)p和q味抖;
  2. 將第一個結(jié)點(diǎn)賦值給p,下一結(jié)點(diǎn)賦值給q灰粮;
  3. 循環(huán)執(zhí)行釋放p和將q賦值給p的操作仔涩;

代碼實(shí)現(xiàn):

Status ClearList(LinkList *L)
{
    LinkList p, q;

    p = (*L)->next;

    while(p)
    {
        q = p->next;
        free(p);
        p = q;
    }

    (*L)->next = NULL;

    return OK;
}

這段算法代碼里,常見的錯誤就是有同學(xué)會覺得q變量沒有存在的必要粘舟,只需要在循環(huán)體內(nèi)直接寫free(p); p = p->next; 即可熔脂?
p是一個結(jié)點(diǎn)佩研,它除了有數(shù)據(jù)域,還有指針域霞揉。當(dāng)我們做free(p);時旬薯,其實(shí)是對它整個結(jié)點(diǎn)進(jìn)行刪除和內(nèi)存釋放的工作。而我們整表刪除是需要一個個結(jié)點(diǎn)刪除的适秩,所以我們就需要q來記載p的下一個結(jié)點(diǎn)绊序。

六、單鏈表結(jié)構(gòu)與順序存儲結(jié)構(gòu)優(yōu)缺點(diǎn)

分別從存儲分配方式秽荞、時間性能骤公、空間性能三方面來做對比:

1.存儲分配方式:

  • 順序存儲結(jié)構(gòu)用一段連續(xù)的存儲單元依次存儲線性表的數(shù)據(jù)元素。
  • 單鏈表采用鏈?zhǔn)酱鎯Y(jié)構(gòu)蚂会,用一組任意的存儲單元存放線性表的元素淋样。

2.時間性能:

查找:

  • 順序存儲結(jié)構(gòu)O(1)
  • 單鏈表O(n)

插入和刪除:

  • 順序存儲結(jié)構(gòu)需要平均移動表長一半的元素,時間為O(n)
  • 單鏈表在計算出某位置的指針后胁住,插入和刪除時間僅為O(1)

3.空間性能:

  • 順序存儲結(jié)構(gòu)需要預(yù)分配存儲空間,分大了刊咳,容易造成空間浪費(fèi)彪见,分小了,容易發(fā)生溢出娱挨。
  • 單鏈表不需要分配存儲空間余指,只要有就可以分配,元素個數(shù)也不受限制跷坝。

結(jié)論:

  • 若線性表需要頻繁查找酵镜,很少進(jìn)行插入和刪除操作時,宜采用順序存儲結(jié)構(gòu)柴钻。
  • 若需要頻繁插入和刪除時淮韭,宜采用單鏈表結(jié)構(gòu)。
  • 比如說游戲開發(fā)中贴届,對于用戶注冊的個人信息靠粪,除了注冊時插入數(shù)據(jù)外,絕大多數(shù)情況都是讀取毫蚓,所以應(yīng)該考慮用順序存儲結(jié)構(gòu)占键。
  • 而游戲中的玩家的武器或者裝備列表,隨著玩家的游戲過程中元潘,可能會隨時增加或刪除畔乙,此時再用順序存儲就不太合適了,單鏈表結(jié)構(gòu)就可以大展拳腳了翩概。
  • 當(dāng)線性表中的元素個數(shù)變化較大或者根本不知道有多大時牲距,最好用單鏈表結(jié)構(gòu)袖订,這樣可以不需要考慮存儲空間的大小問題。
  • 而如果事先知道線性表的大致長度嗅虏,比如一年12個月洛姑,一周就是星期一至星期日共七天,這種用順序存儲結(jié)構(gòu)效率會高很多皮服。

總之楞艾,線性表的順序存儲結(jié)構(gòu)和單鏈表結(jié)構(gòu)各有其優(yōu)缺點(diǎn),不能簡單的說哪個好龄广,哪個不好硫眯,需要根據(jù)實(shí)際情況,來綜合平衡采用哪種數(shù)據(jù)結(jié)構(gòu)更能滿足和達(dá)到需求和性能择同。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末两入,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子敲才,更是在濱河造成了極大的恐慌裹纳,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件紧武,死亡現(xiàn)場離奇詭異剃氧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)阻星,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門朋鞍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人妥箕,你說我怎么就攤上這事滥酥。” “怎么了畦幢?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵坎吻,是天一觀的道長。 經(jīng)常有香客問我呛讲,道長禾怠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任贝搁,我火速辦了婚禮吗氏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雷逆。我一直安慰自己弦讽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著往产,像睡著了一般被碗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仿村,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天锐朴,我揣著相機(jī)與錄音,去河邊找鬼蔼囊。 笑死焚志,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的畏鼓。 我是一名探鬼主播酱酬,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼云矫!你這毒婦竟也來了膳沽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤让禀,失蹤者是張志新(化名)和其女友劉穎挑社,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體堆缘,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡滔灶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吼肥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡麻车,死狀恐怖缀皱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情动猬,我是刑警寧澤啤斗,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站赁咙,受9級特大地震影響钮莲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜彼水,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一崔拥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧凤覆,春花似錦链瓦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽渤刃。三九已至,卻和暖如春贴膘,著一層夾襖步出監(jiān)牢的瞬間卖子,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工刑峡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洋闽,地道東北人。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓氛琢,卻偏偏與公主長得像喊递,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子阳似,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354

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