2018之線性表的基本概念

轉(zhuǎn)自:http://blog.csdn.net/oreo_go/article/details/52116214

一盖喷、線性表的定義

線性表:零個(gè)或多個(gè)數(shù)據(jù)元素的有限序列炮沐。

幾個(gè)關(guān)鍵的地方。
首先它是一個(gè)序列峰弹。也就是說店量,元素之間是有順序的,若元素存在多個(gè)鞠呈,則第一個(gè)元素?zé)o前驅(qū)融师,最后一個(gè)元素?zé)o后繼,其他每個(gè)元素都只有一個(gè)前驅(qū)和后繼蚁吝。

然后旱爆,線性表強(qiáng)調(diào)是有限的舀射。事實(shí)上,在計(jì)算機(jī)中處理的對(duì)象都是有限的怀伦,那種無限的數(shù)列后控,只存在于數(shù)學(xué)的概念中。

如果用數(shù)學(xué)語言來定義空镜。可如下:
**若將線性表記為(a1捌朴,…吴攒,ai-1,ai砂蔽,ai+1洼怔,…,an)左驾,則表中ai-1領(lǐng)先于ai镣隶,ai領(lǐng)先于ai+1,稱ai-1是ai的直接前驅(qū)元素诡右,ai+1是ai的直接后繼元素安岂。當(dāng)i = 1,2帆吻,…域那,n-1時(shí),ai有且僅有一個(gè)直接后繼猜煮,當(dāng)i = 2 , 3 , … , n時(shí)次员,ai有且僅有一個(gè)直接前驅(qū)。 **

所以線性表元素的個(gè)數(shù)n(n ≥ 0)定義為線性表長(zhǎng)度王带,當(dāng)n=0時(shí)淑蔚,稱為空表。

在非空表中的每個(gè)數(shù)據(jù)元素都有一個(gè)確定的位置愕撰,如a1是第一個(gè)數(shù)據(jù)元素刹衫,an是最后一個(gè)數(shù)據(jù)元素,ai是第i個(gè)數(shù)據(jù)元素盟戏,稱i為數(shù)據(jù)元素ai在線性表中的位序绪妹。

舉幾個(gè)例子,來判斷是否是線性表柿究。
第一個(gè):一年的星座列表邮旷,是不是線性表呢?

答:當(dāng)然是蝇摸,星座通常都是白羊座開頭婶肩,雙魚座收尾办陷,當(dāng)中的星座都有前驅(qū)后繼,而且一共才12個(gè)律歼,所以完全符合線性表的定義民镜。

第二個(gè):公司的組織交媾,總經(jīng)理管理幾個(gè)總監(jiān)险毁,每個(gè)總監(jiān)管理幾個(gè)經(jīng)理制圈,每個(gè)經(jīng)理管理各自的下述和員工。這樣的組織架構(gòu)是不是線性關(guān)系呢畔况?

答:不是鲸鹦,為什么不是呢?因?yàn)槊恳粋€(gè)元素跷跪,都有不止一個(gè)后繼馋嗜,所以它不是線性表。

第三個(gè):班級(jí)同學(xué)的友誼關(guān)系吵瞻,是不是線性表呢葛菇?

答:不是,因?yàn)槊總€(gè)人都可以和多個(gè)同學(xué)建立友誼橡羞,不滿足線性的定義眯停。

第四個(gè):班級(jí)同學(xué)的點(diǎn)名冊(cè),是不是線性表卿泽?是不是點(diǎn)名冊(cè)庵朝?

答:是,這和剛才的友誼關(guān)系是完全不同的又厉,因?yàn)樗怯邢扌蛄芯鸥矟M足類型相同特點(diǎn),這個(gè)點(diǎn)名冊(cè)中覆致,每個(gè)元素除學(xué)生的學(xué)號(hào)外侄旬,還可以有同學(xué)的姓名、性別煌妈、出生年月什么的儡羔,這其實(shí)就是我們之前將的數(shù)據(jù)項(xiàng)。在較復(fù)雜的線性表中璧诵,一個(gè)數(shù)據(jù)元素可以由若干個(gè)數(shù)據(jù)項(xiàng)組成汰蜘。

二、線性表的抽象數(shù)據(jù)類型

線性表的抽象數(shù)據(jù)類型定義如下:

ADT 線性表(List)
Data
    線性表的數(shù)據(jù)對(duì)象集合為{a1,a2,....,an},每個(gè)元素的類型均為DataType之宿。其中除了族操,第一個(gè)元素a1外,每一個(gè)元素有且只有一個(gè)直接前驅(qū)元素,除最后一個(gè)元素an外色难,每一個(gè)元素有且只有一個(gè)直接后繼元素泼舱。數(shù)據(jù)元素之間的關(guān)系是一對(duì)一的關(guān)系。

Operation
    InitList(*L):初始化操作枷莉,建立一個(gè)空的線性表娇昙。
    ListEmpty(L):若線性表為空,返回true笤妙,否則返回false冒掌。
    ClearList(*L):線性表清空。
    GetElem(L,i,*e):將線性表L中第i個(gè)位置元素返回給e蹲盘。
    LocateElem(L,e):在線性表L中查找與給定值e相等的元素宋渔,如果
        查找成功,返回該元素在表中的序列號(hào);否則辜限,返回0表示失敗。
    ListInsert(*L,i,e):在線性表的第i個(gè)位置插入元素e严蓖。
    ListDelete(*L,i,*e):刪除線性表L中的第i個(gè)元素薄嫡,并用e返回其值
    ListLength(L):返回線性表L的元素個(gè)數(shù)。

對(duì)于不同的應(yīng)用颗胡,線性表的操作時(shí)不同的毫深,上述操作時(shí)最基本的,問題中設(shè)計(jì)的關(guān)于線性表的更復(fù)雜操作毒姨,完全可以用這些基本操作的組合來實(shí)現(xiàn)哑蔫。

比如,要實(shí)現(xiàn)兩個(gè)線性表集合A和B的并集操作弧呐。即要使得集合A = A ∪ B闸迷,說白了,就是把存在集合B中但并不存在中的數(shù)據(jù)元素插到A中即可俘枫。

仔細(xì)分析一下這個(gè)操作腥沽,發(fā)現(xiàn)我們只要循環(huán)集合B中的元素,判斷是否存在A中鸠蚪,若不存在今阳,則插到A中即可。思路應(yīng)該是很容易想到的茅信。

假設(shè)我們La表示集合A盾舌,Lb表示集合B,則實(shí)現(xiàn)代碼如下:

//將所有的在線性表Lb中但不在La中的元素插入到La中
void unionL(List *La , List Lb)
{
    int La_len,Lb_len,i;
    ElemType e;
    La_len = ListLength(*La);
    Lb_len = ListLength(*Lb);
    for(i = 0 ;i ≤ Lb;i++)
    {
        GetElem(Lb,i,*e);//取出Lb中第i個(gè)數(shù)據(jù)元素賦給e
        if(!LocateElem(*La,e))//La中不存在和e元素相同的數(shù)據(jù)元素
        {
            ListInsert(La,++La_len,e);//插入
        }
    }
}

這里我們對(duì)于union操作蘸鲸,用到了前面線性表基本操作ListLength妖谴、GetElem、LocateElem酌摇,ListLength等窖维,可見榆综,對(duì)于復(fù)雜的個(gè)性化的操作,其實(shí)就是把基本操作組合起來實(shí)現(xiàn)的铸史。

三鼻疮、線性表的順序存儲(chǔ)結(jié)構(gòu)

1.順序存儲(chǔ)定義

說了這么多的線性表,我們來看線性表的物理結(jié)構(gòu)第一種——順序存儲(chǔ)結(jié)構(gòu)琳轿。

線性表的順序存儲(chǔ)結(jié)構(gòu)判沟,指定的是用一段地址連續(xù)的存儲(chǔ)單元一次存儲(chǔ)線性表的數(shù)據(jù)元素。

2.順序存儲(chǔ)方式

線性表的順序存儲(chǔ)方式崭篡,說白了挪哄,就是在內(nèi)存中找了一塊地方,把一定內(nèi)存空間占了琉闪,然后把相同數(shù)據(jù)類型的數(shù)據(jù)元素一次存在在里面迹炼。既然線性表的數(shù)據(jù)元素的類型都相同,所以用C語言的一維數(shù)組來實(shí)現(xiàn)順序存儲(chǔ)結(jié)構(gòu)颠毙,即把第一個(gè)數(shù)據(jù)元素存儲(chǔ)到數(shù)組下表為0的位置中斯入,接著把線性表相鄰的元素存儲(chǔ)在數(shù)組中相鄰的位置。

為了建立一個(gè)線性表蛀蜜,要在內(nèi)存中找一塊地刻两,于是這塊地的第一個(gè)位置就非常關(guān)鍵,它是存儲(chǔ)空間的起始位置滴某。

線性表中磅摹,我們估算這個(gè)線性表的最大存儲(chǔ)容量,建立一個(gè)數(shù)組霎奢,數(shù)組的長(zhǎng)度就是最大存儲(chǔ)容量户誓。

我們已經(jīng)有了起始位置,也有了最大的容量幕侠,于是我們可以在里面增加數(shù)據(jù)了厅克。隨著數(shù)據(jù)的插入,我們線性表的長(zhǎng)度開始變大橙依,不過線性表的當(dāng)前長(zhǎng)度不能超過存儲(chǔ)容量证舟,即數(shù)組的長(zhǎng)度。

來看線性表的順序存儲(chǔ)的結(jié)構(gòu)代碼窗骑。

 #define MAXSIZE 20 //存儲(chǔ)空間初始分配量
 typedef int ElemType;//ElemType根據(jù)實(shí)際情況而定女责,這里假設(shè)為int
 typedef struct
 {
     ElemType data[MAXSIZE];//數(shù)組存儲(chǔ)數(shù)據(jù)元素,最大值為MAXSIZE
     int length;//線性表當(dāng)前長(zhǎng)度
 }SqList;

這里我們發(fā)現(xiàn)描述順序存儲(chǔ)結(jié)構(gòu)需要三個(gè)屬性
存儲(chǔ)空間的起始位置:數(shù)組data,它的存儲(chǔ)位置就是存儲(chǔ)空間的存儲(chǔ)位置创译。

線性表的最大存儲(chǔ)容量:數(shù)組長(zhǎng)度MaxSize抵知。

線性表的當(dāng)前長(zhǎng)度:length。

3.數(shù)組長(zhǎng)度與線性表長(zhǎng)度區(qū)別

數(shù)組長(zhǎng)度是存放線性表的存儲(chǔ)空間的長(zhǎng)度,存儲(chǔ)空間分配完一般是不變的刷喜。

線性表長(zhǎng)度是線性表中元素?cái)?shù)據(jù)的個(gè)數(shù)残制,隨著線性表插入和刪除操作的進(jìn)行,這個(gè)量是變化的掖疮。

在任意時(shí)刻初茶,線性表的長(zhǎng)度應(yīng)該小于等于數(shù)組的長(zhǎng)度。

4.地址計(jì)算方法

線性表的起始是從1開始的浊闪,可數(shù)組卻是從0開始第一個(gè)下標(biāo)的恼布,于是線性表中第i個(gè)元素,存儲(chǔ)在數(shù)組下標(biāo)為i - 1的位置搁宾。

用數(shù)組存儲(chǔ)順序表意味著要分配固定長(zhǎng)度的數(shù)組空間折汞,由于線性表中可以進(jìn)行插入和刪除操作,因此分配的數(shù)組空間要大于等于當(dāng)前線性表的長(zhǎng)度盖腿。

由于每個(gè)數(shù)據(jù)元素爽待,不管他是整型、實(shí)型還是字符型翩腐,它都是需要占用一定的存儲(chǔ)空間的鸟款。假設(shè)占用的是c個(gè)存儲(chǔ)單元,那么線性表中第i + 1個(gè)元素的存儲(chǔ)位置和第i個(gè)數(shù)據(jù)元素的存儲(chǔ)位置滿足下列關(guān)系(LOC表示獲得存儲(chǔ)位置的函數(shù))栗菜。

LOC(ai+1) = LOC(ai) + c

所以對(duì)于第i個(gè)數(shù)據(jù)元素ai的存儲(chǔ)位置可以由a1推算得出:

LOC(ai) = LOC(ai) + (i - 1) * c

通過這個(gè)公式,隨時(shí)可以算出線性表中任意位置的地址蹄梢,不管他是第一個(gè)還是最后一個(gè)疙筹,都是相同的事件。那么我們對(duì)每個(gè)線性表位置的存入或者取出數(shù)據(jù)禁炒,對(duì)于計(jì)算機(jī)來說都是相等的時(shí)間而咆,也就是一個(gè)常數(shù),因此我們算法中學(xué)到的時(shí)間復(fù)雜度的概念來說幕袱,它的存取時(shí)間的性能為O(1)暴备。我們通常把具有這一特點(diǎn)的存儲(chǔ)結(jié)構(gòu)稱為隨機(jī)存取結(jié)構(gòu)。

四们豌、順序存儲(chǔ)結(jié)構(gòu)的插入與刪除

1.獲得元素操作

對(duì)于線性表的順序存儲(chǔ)結(jié)構(gòu)來說涯捻,我們要實(shí)現(xiàn)GetElem操作,即將線性表L中的第i個(gè)位置元素返回望迎,其實(shí)是非常簡(jiǎn)單的障癌。就程序而言,只要第i個(gè)元素在下標(biāo)范圍內(nèi)辩尊,就是把數(shù)組第i - 1下表值返回即可涛浙。
來看代碼:

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
//Status是函數(shù)的類型,其值是函數(shù)結(jié)果狀態(tài)代碼,如OK等
//初始條件:順序線性表L已經(jīng)存在轿亮,1 ≤ i ≤ ListLength(L)
//操作結(jié)果:用e返回L中第i個(gè)元素的值
Status GetElem(SqList L,int i,ElemType *e)
{
    if(L.length == 0 || i < 1 || i > L.length)
        return ERROR;
    *e = L.data[i - 1];
    return OK;
}

注意這里返回值類型Status是一個(gè)整型疮薇,返回OK代表1,ERROR代表0我注。

2.插入操作

剛才我們也談到按咒,這里的時(shí)間復(fù)雜度為O(1)。我們現(xiàn)在來考慮,如果我們要實(shí)現(xiàn)ListInsert(*L,i钓账,e)逛裤,即在線性表L中第i個(gè)位置插入新元素e,應(yīng)該如何操作趣些?

插入算法的思路

如果插入位置不合理,拋出異常

如果線性表長(zhǎng)度大于等于數(shù)組長(zhǎng)度剿另,則拋出異常或動(dòng)態(tài)增加容量贬蛙;

從最后一個(gè)元素開始向前遍歷到第i個(gè)元素雨女,分別將它們都向后移一位

將要插入元素填入位置i處阳准;

表長(zhǎng)加1氛堕。
實(shí)現(xiàn)代碼如下:

//初始條件:順序線性表L已存在,1 ≤ i ≤ ListLength(L)
//操作結(jié)果:在L的第i個(gè)位置插入新的數(shù)據(jù)元素e,L的長(zhǎng)度加1
Status ListInsert(SqList *L,int i,ElemType e)
{
    int k;
    if(L->length == MAXSIZE)//當(dāng)線性表已滿
        return ERROR;
    if(i < 1 || i >L->length + 1)//當(dāng)i不在范圍內(nèi)時(shí)
    {
        return ERROR;
    }
    if(i <= L->length)//若插入數(shù)據(jù)位置不在表尾
    {
        for(k = L->length-1;k > i-1;k--)
        {
            L->data[k + 1] = L->data[k];
        }
    }
    L->data[i - 1] = e;//將新元素插入
    L->length++;
    return OK;
}

3.刪除操作

刪除算法的思路:
如果刪除位置不合理,拋出異常野蝇;

取出刪除元素讼稚;

從刪除元素位置開始遍歷到最后一個(gè)元素位置,分別將它們向前移動(dòng)一個(gè)位置绕沈;

表長(zhǎng)減1锐想。

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

//初始條件:順序線性表L已經(jīng)存在,1 <= i <= ListLength(L)
//操作結(jié)果:刪除L的第i個(gè)元素,并用e返回其值,L的長(zhǎng)度減1
Status ListDelete(SqList *L ,int i , ElemType *e)
{
    int k;
    if(L->length == 0)//線性表為空
        return ERROR;
    if(i < 1 || i > L->length)//刪除位置不正確
        return ERROR;
    *e = L->data[I];
    if(i < L->length)
    {
        for(k = i;k < L->length;k++)
            L->data[k - 1] = L->data[k];
    }
    L->length--;
    return OK;
}

現(xiàn)在乍狐,我們來分析一下赠摇,插入和刪除的事件復(fù)雜度。
現(xiàn)在我們來看最好的情況浅蚪,如果一個(gè)元素要插入到最后一個(gè)位置藕帜,或者刪除最后一個(gè)位置,此時(shí)時(shí)間復(fù)雜度為O(1)惜傲,因?yàn)椴恍枰苿?dòng)元素的耘戚。

最壞的情況呢,如果元素要插入到第一個(gè)位置或者刪除第一個(gè)元素操漠,此時(shí)時(shí)間復(fù)雜度是多少呢收津?那就意味著所有元素向后或者向前饿这,所以這個(gè)時(shí)間復(fù)雜度為O(n)

至于平均的情況撞秋,由于元素插入到第i個(gè)位置长捧,或者刪除第i個(gè)元素,需要移動(dòng)n - i個(gè)元素吻贿,每個(gè)位置插入或刪除元素的可能性是相同的串结,也就是位置靠前,移動(dòng)元素多舅列,位置靠后肌割,移動(dòng)元素少。最終平均移動(dòng)次數(shù)和最中間那個(gè)元素的移動(dòng)次數(shù)相等帐要,為(n - 1)/ 2把敞。

根據(jù)時(shí)間復(fù)雜度的推導(dǎo),平均時(shí)間復(fù)雜度還是O(n)榨惠。

這說明說明奋早?線性表的順序存儲(chǔ)結(jié)構(gòu),在存赠橙、讀數(shù)據(jù)時(shí)耽装,不管是哪個(gè)位置,時(shí)間復(fù)雜度都是O(1)期揪;而插入或刪除時(shí)掉奄,時(shí)間復(fù)雜度都是O(n)。這就說明凤薛,它比較適合元素個(gè)數(shù)不太變化姓建,而更多是存取數(shù)據(jù)的應(yīng)用

4線性表順序存儲(chǔ)結(jié)構(gòu)的優(yōu)缺點(diǎn)

優(yōu)點(diǎn)
無須為表中元素之間的邏輯關(guān)系而增加額外的存儲(chǔ)空間

可以快速地存取表中任一位置的元素

缺點(diǎn)
插入和刪除需要移動(dòng)大量元素

當(dāng)線性表長(zhǎng)度變化較大時(shí)枉侧,難以確定存儲(chǔ)空間的容量

造成存儲(chǔ)空間的“碎片”

五引瀑、線性表的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)

1.線性表鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)定義

線性表的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)的特點(diǎn)是用一組任意的存儲(chǔ)單元存儲(chǔ)線性表的數(shù)據(jù)元素狂芋,這組存儲(chǔ)單元可以是連續(xù)的榨馁,也可以是不連續(xù)的。這就意味著帜矾,這些元素可以存在內(nèi)存未被占用的任意位置翼虫。

以前在順序結(jié)構(gòu)中,每個(gè)元素?cái)?shù)據(jù)只需要存儲(chǔ)數(shù)據(jù)元素信息就可以了÷庞現(xiàn)在在鏈?zhǔn)浇Y(jié)構(gòu)中珍剑,除了要存數(shù)據(jù)元素信息外,還要存儲(chǔ)它的后繼元素的存儲(chǔ)地址死陆。

因此招拙,為了表示每個(gè)數(shù)據(jù)元素ai與其直接后級(jí)元素ai+1之間的邏輯關(guān)系唧瘾,對(duì)數(shù)據(jù)元素ai來說,除了存儲(chǔ)其本身的信息之外别凤,還需存儲(chǔ)一個(gè)指示其直接后繼的信息(即直接后繼的存儲(chǔ)位置)饰序。我們把存儲(chǔ)數(shù)據(jù)元素信息的域稱為數(shù)據(jù)域,把存儲(chǔ)直接后繼位置的域稱為指針域规哪。指針域中存儲(chǔ)的信息稱作指針或鏈求豫。這兩部分信息組成數(shù)據(jù)元素ai的存儲(chǔ)映像,稱為結(jié)點(diǎn)(Node)诉稍。

n個(gè)結(jié)點(diǎn)(ai的存儲(chǔ)映像)鏈結(jié)成一個(gè)鏈表蝠嘉,即為線性表(a1,a2,….,an)的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu),因?yàn)榇随湵淼拿總€(gè)結(jié)點(diǎn)中只包含一個(gè)指針域杯巨,所以叫做單鏈表蚤告。單鏈表正是通過每個(gè)結(jié)點(diǎn)的指針域?qū)⒕€性表的數(shù)據(jù)元素按其邏輯次序鏈接在一起。

對(duì)于線性表來說舔箭,總得有個(gè)頭有個(gè)尾罩缴,鏈表也不例外。我們把鏈表中第一個(gè)結(jié)點(diǎn)的存儲(chǔ)位置叫做頭指針层扶,那么整個(gè)鏈表的存取就必須是從頭指針開始進(jìn)行了箫章。之后的每一個(gè)結(jié)點(diǎn),其實(shí)就是上一個(gè)的后繼指針指向的位置镜会。

最后一個(gè)檬寂,當(dāng)然意味著直接后繼不存在了,所以我們規(guī)定戳表,線性鏈表的最后一個(gè)結(jié)點(diǎn)指針為“空”(通常用NULL或“^”符號(hào)表示)桶至。

有時(shí),我們?yōu)榱烁臃奖愕貙?duì)鏈表進(jìn)行操作匾旭,會(huì)在單鏈表的第一個(gè)結(jié)點(diǎn)前附設(shè)一個(gè)結(jié)點(diǎn)镣屹,稱為頭結(jié)點(diǎn)。頭結(jié)點(diǎn)的數(shù)據(jù)域可以不存儲(chǔ)任何信息价涝,也可以存儲(chǔ)如線性表的長(zhǎng)度等附加信息女蜈,頭結(jié)點(diǎn)的指針域存儲(chǔ)指向第一個(gè)結(jié)點(diǎn)的指針。

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

頭指針與頭結(jié)點(diǎn)的異同點(diǎn)色瘩。
頭指針
① 頭指針是指鏈表指向第一個(gè)結(jié)點(diǎn)的指針伪窖,若鏈表有頭結(jié)點(diǎn),則是指向頭結(jié)點(diǎn)的指針

②頭指針具有標(biāo)識(shí)作用居兆,所以常用頭指針冠以鏈表的名字

③無論鏈表是否為空覆山,頭指針均不為空。頭指針是鏈表的必要元素泥栖。

頭結(jié)點(diǎn)
①頭結(jié)點(diǎn)是為了操作的統(tǒng)一和方便而設(shè)立的簇宽,放在第一元素的結(jié)點(diǎn)之間勋篓,其數(shù)據(jù)域一般無意義。

②有了頭結(jié)點(diǎn)魏割,對(duì)在第一元素結(jié)點(diǎn)前插入結(jié)點(diǎn)生巡,其操作與其它結(jié)點(diǎn)的操作就統(tǒng)一了。

③頭結(jié)點(diǎn)不一定是鏈表必須要素见妒。

3.線性鏈表式存儲(chǔ)結(jié)構(gòu)代碼描述

若線性鏈表為空表孤荣,則頭結(jié)點(diǎn)的指針域?yàn)椤翱铡薄?/p>

單鏈表中,我們?cè)贑語言中可用結(jié)構(gòu)指針來描述须揣。

//線性表的單鏈表存儲(chǔ)結(jié)構(gòu)
typedef struct Node
{
    ElemType data;
    struct Node *next;
}Node;
typedef struct Node *LinkList;//定義LinkList

在這個(gè)結(jié)構(gòu)定義中盐股,我們也就知道,結(jié)點(diǎn)由存放數(shù)據(jù)元素的數(shù)據(jù)域和存放后繼結(jié)點(diǎn)地址的指針域組成耻卡。 假設(shè)p是指向線性表第i個(gè)元素的指針疯汁,則該結(jié)點(diǎn)ai的數(shù)據(jù)域我們可以用p->data來表示,p->data的值是一個(gè)數(shù)據(jù)元素卵酪,結(jié)點(diǎn)ai的指針可以用p->next來表示幌蚊,p->next的值是一個(gè)指針。p->next指向誰呢溃卡?當(dāng)然是指向第i + 1個(gè)元素溢豆,即指向ai+1。也就是說p->data = ai瘸羡,那么p->next->data=ai+1

六漩仙、單鏈表的讀取

在線性表的順序存儲(chǔ)結(jié)構(gòu)中,我們要計(jì)算任意一個(gè)元素的存儲(chǔ)位置使很容易的犹赖。但在單鏈表中队他,由于第i個(gè)元素到底在哪?沒辦法一開始就知道峻村,必須從頭開始找麸折。因此,對(duì)于單鏈表實(shí)現(xiàn)獲取第i個(gè)元素的操作GetElem粘昨,在算法上垢啼,相對(duì)麻煩一些。

獲得鏈表第i個(gè)數(shù)據(jù)的算法思路:
1. 聲明一個(gè)指針p指向鏈表第一個(gè)結(jié)點(diǎn)雾棺,初始化j從1開始膊夹。
2. 當(dāng)j < i 時(shí)衬浑,就遍歷鏈表捌浩,讓p的指針向后移動(dòng),不斷指向下一結(jié)點(diǎn)工秩,j累加1;
3. 若鏈表末尾p為空尸饺,則說明第i個(gè)結(jié)點(diǎn)不存在进统;
4. 否則查找成功,返回結(jié)點(diǎn)p的數(shù)據(jù)浪听。
實(shí)現(xiàn)代碼如下:

//初始條件:順序線性表L已存在,1 ≤ i ≤ ListLength(L)
//操作結(jié)果:用e返回L中第i個(gè)數(shù)據(jù)元素的值
Status GetElem(LinkList L,int i,ElemType *e)
{
    int j;
    LinkList p;//聲明一指針
    p = L->next;//讓p指向鏈表L的第一個(gè)結(jié)點(diǎn)
    j = 1;//j為計(jì)數(shù)器
    while(p && j < i)//p不為空且計(jì)數(shù)器j還沒有等于i時(shí)螟碎,循環(huán)繼續(xù)
    {
        p = p->next;//讓p指向下也結(jié)點(diǎn)
        ++j;
    }
    if(p || j > i)
        return ERROR;//第i個(gè)結(jié)點(diǎn)不存在
    *e = p->data;//取第i個(gè)結(jié)點(diǎn)的數(shù)據(jù)
    return OK;
}

說白了,就是從頭開始找迹栓,直到第i個(gè)結(jié)點(diǎn)為止掉分。由于這個(gè)算法復(fù)雜度取決于i的位置,當(dāng)i = 1時(shí)克伊,不需要變量酥郭,而當(dāng)i = n時(shí)則遍歷n - 1次才可以。因此最壞情況的時(shí)間復(fù)雜度是O(n)愿吹。

由于單鏈表的結(jié)構(gòu)沒有定義表長(zhǎng)不从,所以不知道事先循環(huán)多少次,因此也就不方便使用for來控制循環(huán)犁跪。其主要核心思想是“工作指針后移”椿息,這其實(shí)也是很多算法常用技術(shù)。

八坷衍、單鏈表的插入與刪除

1.單鏈表的插入

假設(shè)存儲(chǔ)元素e的結(jié)點(diǎn)為s寝优,要實(shí)現(xiàn)結(jié)點(diǎn)p、p->next和s之間的邏輯關(guān)系的變化枫耳,只需要將s插到結(jié)點(diǎn)p和p->next之間即可倡勇。
根本不需要驚動(dòng)其他結(jié)點(diǎn),只需要讓s->next和p->next的指針做一點(diǎn)改變嘉涌。

//下面兩句不可交換順序
s->next = p->next;
p->next = s;

單鏈表第i個(gè)數(shù)據(jù)插入結(jié)點(diǎn)的算法思路:
1. 聲明一指針p指向鏈表頭結(jié)點(diǎn)妻熊,初始化j從1開始;
2. 當(dāng)j < i時(shí),就遍歷鏈表仑最,讓p的指針向后移動(dòng)扔役,不斷指向下一結(jié)點(diǎn),j累加1
3. 若到鏈表末尾p為空,則說明第i個(gè)結(jié)點(diǎn)不存在;
4. 若查找成功警医,在系統(tǒng)中生成一個(gè)空節(jié)點(diǎn)s亿胸;
5. 將數(shù)據(jù)元素e賦給s->data;
6. 單鏈表的插入標(biāo)準(zhǔn)語句s->next = p->next; p->next = s;
7. 返回成功

實(shí)現(xiàn)代碼算法如下:

//初始條件:順序線性表L已存在,1≤i≤ListLength(L)
//操作結(jié)果:在L中第i個(gè)結(jié)點(diǎn)位置之前插入新的數(shù)據(jù)元素,L的長(zhǎng)度加1
Status ListInsert(LinkList *L , int i , ElemType e)
{
    int j = 1;
    LinkList p,s;
    p = *L;
    while( p && j < i) //尋找第I個(gè)結(jié)點(diǎn)
    {
        p = p->next;
        ++j;
    }
    if( !p || j > 1)
    {
        return ERROR;//第i個(gè)結(jié)點(diǎn)不存在
    }
    s = (LinkList)malloc(sizeof(Node));//生成新結(jié)點(diǎn)
    s->data = e;
    s->next = p->next;//將p的后繼結(jié)點(diǎn)賦值給s的后繼
    p->next = s;//將s賦給p的后繼
    return OK;
}

在這段算法代碼中预皇,我們用到了C語言的malloc標(biāo)準(zhǔn)函數(shù)侈玄,它的作用就是生成一個(gè)新的結(jié)點(diǎn),其類型與Node是一樣的吟温,其實(shí)質(zhì)就是在內(nèi)存中開辟了一段空間序仙,用了存放數(shù)據(jù)e的s結(jié)點(diǎn)。

2.單鏈表的刪除

現(xiàn)在我們?cè)賮砜磫捂湵淼膭h除鲁豪。設(shè)存儲(chǔ)元素ai的結(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即是:

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

也就是說把p的后繼結(jié)點(diǎn)改成p的后繼的后繼結(jié)點(diǎn)船惨。

單鏈表第i個(gè)數(shù)據(jù)刪除結(jié)點(diǎn)的算法思路:
1. 聲明一指針p指向鏈表頭指針,初始化j從1開始缕陕;
2. 當(dāng)j < i時(shí)掷漱,就遍歷鏈表,讓p的指針向后移動(dòng)榄檬,不斷指向下一個(gè)結(jié)點(diǎn)卜范,i累加1;
3. 若到鏈表末尾p為空鹿榜,則說明第i個(gè)結(jié)點(diǎn)不存在海雪;
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)
8. 返回成功

實(shí)現(xiàn)代碼算法如下:

//初始條件:順序線性表L已存在,1≤ i ≤ListLength(L)
//操作結(jié)果:刪除L的i個(gè)結(jié)點(diǎn),并用e返回其值,L的長(zhǎng)度減1
Status ListDelete(LinkList *L,int i,ElemType *e)
{
    int j;
    Link p,q;
    p = *L;
    j = 1;
    while(p->next && j < i)//遍歷尋找第i - 1個(gè)結(jié)點(diǎn)
    {
        p = p->next;
        ++j;
    }
    if( !(p->next) || j > i)
        return ERROR;//第i個(gè)結(jié)點(diǎn)不存在
    q = p->next;
    p->next = q->next;//將q的后繼賦給p的后繼
    *e = q->data;//將q結(jié)點(diǎn)中的數(shù)據(jù)給e
    free(q);//讓系統(tǒng)回收此結(jié)點(diǎn)沪袭,釋放內(nèi)存
    return OK;
}

分析一下剛才我們講解的單鏈表插入和刪除算法湾宙,我們發(fā)現(xiàn),它們其實(shí)都是由兩部分組成:第一部分就是遍歷查找第i個(gè)結(jié)點(diǎn)冈绊;第二部分就是插入和刪除結(jié)點(diǎn)侠鳄。

從整個(gè)算法來說,我們很容易推導(dǎo)出:它們的時(shí)間復(fù)雜度都是O(n)死宣。
如果我們不知道第i個(gè)結(jié)點(diǎn)的指針位置伟恶,單鏈表數(shù)據(jù)結(jié)構(gòu)在插入和刪除操作上,與線下順序存儲(chǔ)結(jié)構(gòu)是沒有太大優(yōu)勢(shì)的毅该。但如果博秫,我們希望從第i個(gè)位置,插入10個(gè)結(jié)點(diǎn)眶掌,對(duì)于順序結(jié)構(gòu)意味著挡育,每次都要移動(dòng)n - i個(gè)結(jié)點(diǎn),每次都是O(n)朴爬。而單鏈表即寒,我們只需在第一次時(shí),找到第i個(gè)位置的指針,此時(shí)為O(n)蒿叠,接下來只是簡(jiǎn)單地通過賦值移動(dòng)指針而已,事件復(fù)雜度為O(1)蚣常。
顯然市咽,對(duì)于插入和刪除數(shù)據(jù)越頻繁的操作,單鏈表的優(yōu)勢(shì)就越明顯抵蚊。

八施绎、單鏈表的整表創(chuàng)建

順序存儲(chǔ)結(jié)構(gòu)的創(chuàng)建,其實(shí)就是一個(gè)數(shù)組的初始化贞绳,即聲明一個(gè)類型和大小的數(shù)組并賦值的過程谷醉。而單鏈表和順序存儲(chǔ)結(jié)構(gòu)就不一樣,它不像順序存儲(chǔ)結(jié)構(gòu)這么幾種冈闭,它可以很散俱尼,是一種動(dòng)態(tài)結(jié)構(gòu)。對(duì)于每個(gè)鏈表來說萎攒,它所占用空間的大小和位置使不需要預(yù)先分配劃定的遇八,可以根據(jù)系統(tǒng)的情況和實(shí)際的需求即可生成。

所以創(chuàng)建單鏈表的過程就是一個(gè)動(dòng)態(tài)生成鏈表的過程耍休。即從“空表”的初始狀態(tài)起刃永,一次建立各元素結(jié)點(diǎn),并逐個(gè)插入鏈表羊精。
單鏈表整表創(chuàng)建的思路算法:

  1. 聲明一指針p和計(jì)數(shù)器變量1斯够;

  2. 初始化一空鏈表;

  3. 讓L的頭結(jié)點(diǎn)的指針指向NULL喧锦,即建立一個(gè)帶頭結(jié)點(diǎn)的單鏈表读规;

  4. 循環(huán):

    生成一新結(jié)點(diǎn)賦值給p;
    隨機(jī)生成一數(shù)字賦給p的數(shù)據(jù)域p->data;
    將p插到頭結(jié)點(diǎn)與前一個(gè)新節(jié)點(diǎn)之間的位置。

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

//隨機(jī)產(chǎn)生n個(gè)元素的值燃少,建立帶表頭結(jié)點(diǎn)的單鏈表線性表L(頭插法)
void CreateListHead(LinkList *L,int n)
{
    LinkList p;
    int I;
    srand(time(0));//初始化隨機(jī)數(shù)種子
    *L = (LinkList)malloc(sizeof(Node));
    (*L)->next = NULL;//先建立一個(gè)帶頭結(jié)點(diǎn)的單鏈表
    for(i = 0;i < n;i++)
    {
        p = (LinkList)malloc(sizoef(Node));//生成新的結(jié)點(diǎn)
        p->data = rand() % 100 + 1;//隨機(jī)生成100以內(nèi)的數(shù)字
        p->next = (*L)->next;
        (*L)->next = p; //插入到表頭
    }
}

這段代碼里掖桦,我們始終讓新結(jié)點(diǎn)在第一的位置上,我們把這種算法簡(jiǎn)稱為頭插法供汛。

可事實(shí)上枪汪,我們還可以把新結(jié)點(diǎn)放在最后。這才是排隊(duì)時(shí)的正常思維怔昨。我們每次新結(jié)點(diǎn)都插在終端結(jié)點(diǎn)的后面雀久,這種算法稱之為尾插。

實(shí)現(xiàn)代碼算法如下:

//隨機(jī)產(chǎn)生n個(gè)元素的值,建立帶表頭結(jié)點(diǎn)的單鏈線性表L(尾插法)
void CreateListTail(LinkList *L,int n)
{
    LinkList p,r;
    int I;
    srand(time(0));//初始化隨機(jī)數(shù)種子
    *L = (LinkList)malloc(sizeof(Node));//為整個(gè)線性表
    r = *L;//r為指向尾部的結(jié)點(diǎn)
    for(i = 0;i < n;i++)
    {
        p = (Node *)malloc(sizeof(Node));//生成新結(jié)點(diǎn)
        p->data = rand() % 100 + 1;//隨機(jī)生成100以內(nèi)的數(shù)字
        r->next = p;//將表尾終端結(jié)點(diǎn)的指針指向新結(jié)點(diǎn)
        r = p; //就那個(gè)當(dāng)前新結(jié)點(diǎn)定義為表尾終端結(jié)點(diǎn)
    }
    r->next = NULL;//表示當(dāng)前鏈表結(jié)束
}

注意L與r的關(guān)系趁舀,L指整個(gè)單鏈表赖捌,而r指向尾節(jié)點(diǎn)的變量,r會(huì)隨著循環(huán)不斷地變化結(jié)點(diǎn),而L則是隨著循環(huán)增長(zhǎng)為一個(gè)多結(jié)點(diǎn)的鏈表越庇。

這里需要解釋一下罩锐,r->next = p的意思,其實(shí)就是將剛才的表尾終端結(jié)點(diǎn)r的指針指向新結(jié)點(diǎn)p卤唉。

九涩惑、單鏈表的整表刪除

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

單鏈表整表刪除的算法思路如下:
1. 聲明一結(jié)點(diǎn)p和q熬的;
2. 將第一個(gè)結(jié)點(diǎn)賦值給p痊硕;
3. 循環(huán)
將下一結(jié)點(diǎn)賦值給q;
釋放p;
將q賦值給p。

實(shí)現(xiàn)代碼算法如下:

//初始條件:順序線性表L已經(jīng)存在押框,操作結(jié)果:將L重置為空表
Status ClearList(LinkList *L)
{
    LinkList p,q;
    p = (*L)->next;//p指向第一個(gè)結(jié)點(diǎn)
    while(p)//沒到表尾
    {
        q = p->next;
        free(p);
        p = q;
    }
    (*L)->next = NULL;//頭結(jié)點(diǎn)指針域?yàn)榭?    return OK;
}

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

簡(jiǎn)單地對(duì)單鏈表結(jié)構(gòu)和順序存儲(chǔ)結(jié)構(gòu)作對(duì)比。
1橡伞、存儲(chǔ)分配方式
順序存儲(chǔ)結(jié)構(gòu)有一段連續(xù)的存儲(chǔ)單元依然存儲(chǔ)線性表的數(shù)據(jù)元素亭螟。
單鏈表采用鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu),用一組任意的存儲(chǔ)單元存放線性表的玩意骑歹。

2预烙、時(shí)間性能
查找:

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

插入與刪除

  • 順序存儲(chǔ)結(jié)構(gòu)需要平均移動(dòng)表長(zhǎng)一半的元素,時(shí)間為O(n)
  • 單鏈表在線出某位置的指針后道媚,插入和刪除時(shí)間僅為O(1)

3扁掸、空間性能

  • 順序存儲(chǔ)結(jié)構(gòu)需要預(yù)分配存儲(chǔ)空間,分大了最域,浪費(fèi)谴分,分小了易發(fā)生上溢。
  • 單鏈表不需要分配存儲(chǔ)空間镀脂,只要有就可以分配牺蹄,元素個(gè)數(shù)也不受限制。

通過上面的對(duì)比薄翅,我們可以得出一些經(jīng)驗(yàn)性的結(jié)論:


若線性表需要頻繁查找沙兰,很少進(jìn)入插入和刪除操作時(shí),宜采用順序存儲(chǔ)結(jié)構(gòu)翘魄。
若需要頻繁插入和刪除時(shí)鼎天,宜采用單鏈表結(jié)構(gòu)
比如游戲開發(fā)中暑竟,對(duì)于用戶注冊(cè)的個(gè)人信息斋射,除了注冊(cè)時(shí)插入數(shù)據(jù)外,絕大多數(shù)情況下都是讀取,所以應(yīng)該考慮用順序存儲(chǔ)結(jié)構(gòu)罗岖。而游戲中的玩家的武器或者裝備列表涧至,隨著玩家游戲過程中,可能隨時(shí)增加或刪除桑包,此時(shí)應(yīng)該用單鏈表比較合適南蓬。當(dāng)然,這只是簡(jiǎn)單地類比〖穸啵現(xiàn)實(shí)生活中的軟件開發(fā)蓖康,要考慮的問題會(huì)復(fù)雜得多铐炫。

當(dāng)線性表中的元素個(gè)數(shù)變化較大或者根本不知道有多大時(shí)垒手,最好用單鏈表結(jié)構(gòu),這樣可以不用考慮存儲(chǔ)空間大小問題倒信。
如果事先知道線性表的大致長(zhǎng)度科贬,比如一年12個(gè)月,這種用順序存儲(chǔ)結(jié)構(gòu)效率會(huì)高很多鳖悠。

總之榜掌,線性表的順序存儲(chǔ)結(jié)構(gòu)和單鏈表結(jié)構(gòu)各有其優(yōu)點(diǎn),不是簡(jiǎn)單地說哪個(gè)不好乘综,需要根據(jù)實(shí)際情況憎账,來綜合平衡采用哪種數(shù)據(jù)更能滿足和達(dá)到需求和性能。

十一卡辰、靜態(tài)鏈表

C語言具有指針能力胞皱,使得它可以非常容易地操作內(nèi)存中的地址和數(shù)據(jù),這比其他高級(jí)語言更加方便靈活九妈。
后來的面向?qū)ο笳Z言反砌,如Java、C#等萌朱,雖不使用指針宴树,但因?yàn)閱⒂昧藢?duì)象引用機(jī)制,從某種角度上也間接實(shí)現(xiàn)了指針的某些作用晶疼。但對(duì)于一些語言酒贬,如Basic、Fortran等早期的編程高級(jí)語言翠霍,由于沒有指針同衣,鏈表結(jié)構(gòu)就沒辦法實(shí)現(xiàn)。

有人想出用數(shù)組來代替指針壶运,來描述鏈表耐齐。

首先我們用數(shù)組的元素都是由兩個(gè)數(shù)據(jù)域組成,data和cur。也就是說埠况,數(shù)組的每個(gè)下表都對(duì)應(yīng)一個(gè)data和一個(gè)cur耸携。數(shù)據(jù)域data,用來存放數(shù)據(jù)元素辕翰,也就是通常我們要處理的數(shù)據(jù)夺衍;而cur相當(dāng)于單鏈表中的next指針,存放該元素后繼在數(shù)組中的下表喜命,我們把cur叫做游標(biāo)沟沙。

我們把這種用數(shù)組描述的鏈表叫靜態(tài)鏈表這種描述方法還有起名叫做游標(biāo)實(shí)現(xiàn)法壁榕。

為了我們方便插入數(shù)據(jù)矛紫,我們通常會(huì)把數(shù)組建立得大一些,以便有一些空閑空間可以方便插入不至于溢出牌里。

//線性表的靜態(tài)鏈表存儲(chǔ)結(jié)構(gòu)
#define MAXSIZE 1000//假設(shè)鏈表的最大長(zhǎng)度1000
typedef struct
{
    ElemType data;
    int cur;//游標(biāo)(Cursor),為0時(shí)表示無指向
}Component,StaticLinkList[MAXSIZE];

另外我們對(duì)數(shù)組的第一個(gè)和最后一個(gè)元素作為特殊元素處理颊咬,不存數(shù)據(jù)。我們通常把未被使用的數(shù)組元素稱為備用鏈表牡辽。而數(shù)組第一個(gè)元素喳篇,即下標(biāo)為0的元素的cur就存放備用鏈表的第一個(gè)結(jié)點(diǎn)的下表;而數(shù)組的最后一個(gè)元素的cur則存放第一個(gè)有數(shù)值的元素的下表,相當(dāng)于單鏈表中的頭結(jié)點(diǎn)作用态辛,當(dāng)整個(gè)鏈表為空時(shí)麸澜,則為02。

//將一維數(shù)組space中個(gè)分量鏈成一備用鏈表
//space[0].cur為頭指針奏黑,"0"表示空指針
Status InitList(StaticLinkList space)
{
    int I;
    for(i = 0;i < MAXSIZE - 1;i++)
        space[i].cur = i + 1;
    space[MAXSIZE - 1].cur = 0;//目前靜態(tài)鏈表為空
    return OK;
}

1.靜態(tài)鏈表的插入操作

靜態(tài)鏈表中要解決的是:如何用靜態(tài)模擬動(dòng)態(tài)鏈表結(jié)構(gòu)的存儲(chǔ)空間的分配炊邦,需要時(shí)申請(qǐng),不需要時(shí)釋放攀涵。

我們前面說過铣耘,在動(dòng)態(tài)鏈表中,結(jié)點(diǎn)的申請(qǐng)和釋放分別借用malloc()和free()兩個(gè)函數(shù)來實(shí)現(xiàn)以故。在靜態(tài)鏈表中蜗细,操作的是數(shù)組,不存在像動(dòng)態(tài)鏈表一樣的申請(qǐng)和釋放問題怒详,所以我們需要自己實(shí)現(xiàn)這兩個(gè)函數(shù)炉媒。

為了辨明數(shù)組中哪些分量未被使用,解決的辦法是將所有未被使用過的以及已被刪除的分量用游標(biāo)鏈成一個(gè)備用的鏈表昆烁,每當(dāng)進(jìn)行插入時(shí)吊骤,便可從備用鏈表上取得第一個(gè)結(jié)點(diǎn)作為待插入的新結(jié)點(diǎn)。

//若備用空間鏈表為空静尼,則返回分配的結(jié)點(diǎn)下表白粉,否則返回0
int Malloc_SLL(StaticLinkList space)
{
    int i = space[0].cur;//當(dāng)前數(shù)組的第一個(gè)元素的Cur存的值
    if(space[0].cur)
    {
        space[0].cur = space[i].cur;//由于要拿出一個(gè)分量來使用
                        //所以我們,就得把它的下一個(gè)分量用作備用
    }
    return I;
}

插入元素

//在L中第i個(gè)元素之前插入新的數(shù)據(jù)元素e
Status ListInsert(StaticLinkList L, int i,ElemType e)
{
    int j,k,l;
    k = MAX_SIZE - 1;//注意k首先是最后一個(gè)元素的下表
    if(i < 1 || i > ListLength(L) + 1)
        return ERROR;
    j = Malloc_SSL(L);//獲得空閑分量的下標(biāo)
    if(j)
    {
        L(j).data = e;//將數(shù)據(jù)賦值給此分量的下表
        for(l = 1;l <= i - 1;l++)//相當(dāng)于循環(huán)鏈表传泊,找到第i-1位
        {
            k = L[k].cur;
        }
        L[j].cur = L[k].cur;//新的第i個(gè)元素元素指向原本第i個(gè)元素
        L[k].cur = j;//第i - 1個(gè)元素指向新的第i個(gè)元素
        return OK;
    }
    return ERROR;
}

就這樣,我們實(shí)現(xiàn)了在數(shù)組中鸭巴,實(shí)現(xiàn)不移動(dòng)元素眷细,卻插入了數(shù)據(jù)的操作。

2.靜態(tài)鏈表的刪除操作

和前面一樣鹃祖,刪除元素時(shí)溪椎,原來是需要釋放結(jié)點(diǎn)的函數(shù)free()。我們也要自己實(shí)現(xiàn)它恬口。

//刪除在L中第i個(gè)數(shù)據(jù)元素e
Status ListDelete(StaticLinkList L,int i)
{
    int j , k;
    if(i < 1 || i > ListLength(L))
        return ERROR;
    k = MAX_SIZE - 1;
    for(j = 1;j < = i - 1;j++)//相當(dāng)于遍歷鏈表
    {
        k = L[k].cur;
    }
    j = L[k].cur;//把要?jiǎng)h除的數(shù)組下標(biāo)賦值給j
    Free_SLL(L,j);//調(diào)用刪除函數(shù)
    return OK;
}
//將下表為k的空閑結(jié)點(diǎn)回收到備用鏈表
void Free_SSL(StaticLinkList space,int k)
{
   space[k] = space[0].cur;//把原來第一位指向的下標(biāo)賦給新第一位
   space[0].cur = k;//要?jiǎng)h除的分量賦給第一個(gè)元素cur
}

靜態(tài)鏈表也就是相應(yīng)其他操作的相關(guān)實(shí)現(xiàn)校读。比如ListLength

//初始條件:靜態(tài)鏈表L已存在。操作結(jié)果:返回L中數(shù)據(jù)元素個(gè)數(shù)
int ListLength(StaticLinkList L)
{
    int j = 0;
    int i = L[MAXSIZE - 1].cur;
    while(i)
    {
        i = L[i].cur;
        j++;
    }
    return j;
}

3.靜態(tài)鏈表優(yōu)缺點(diǎn)

優(yōu)點(diǎn):在插入和刪除操作時(shí)祖能,只要修改游標(biāo)歉秫,不需要移動(dòng)元素,從而改進(jìn)了在順序存儲(chǔ)結(jié)構(gòu)中的插入和刪除操作需要移動(dòng)大量元素的缺點(diǎn)芯杀。

缺點(diǎn):
①?zèng)]有解決連續(xù)存儲(chǔ)分配帶來表長(zhǎng)難以確定的問題
②失去了順序存儲(chǔ)結(jié)構(gòu)隨機(jī)存儲(chǔ)的特性

十二端考、循環(huán)鏈表

對(duì)于單個(gè)鏈表雅潭,由于每個(gè)結(jié)點(diǎn)只存儲(chǔ)了向后的指針揭厚,到了尾標(biāo)志就停止了向后鏈的操作,這樣當(dāng)中某一結(jié)點(diǎn)就無法找到它的前驅(qū)結(jié)點(diǎn)了扶供。

將單鏈表中終端結(jié)點(diǎn)的指針由空指針改為指向頭結(jié)點(diǎn)筛圆,就使整個(gè)單鏈表形成一個(gè)環(huán), 這種頭尾相接的單鏈表稱為單循環(huán)鏈表椿浓,簡(jiǎn)稱循環(huán)鏈表太援。

循環(huán)鏈表解決了一個(gè)很麻煩的問題,如何從當(dāng)中一個(gè)結(jié)點(diǎn)出發(fā)扳碍,訪問到鏈表的全部結(jié)點(diǎn)提岔。

循環(huán)鏈表和單鏈表的主要差異就是在于循環(huán)的判斷條件上,原來是判斷p->next是否為空笋敞,現(xiàn)在則是p->next不等于頭結(jié)點(diǎn)碱蒙,則循環(huán)未結(jié)束。

十三夯巷、雙向鏈表

在單鏈表中赛惩,有了next指針,這就使得我們要查找下一結(jié)點(diǎn)的事件復(fù)雜度為O(1)趁餐∨缂妫可是如果我們要查找的是上一節(jié)點(diǎn)的話,那最壞的時(shí)間復(fù)雜度就是O(n)了后雷,因?yàn)槲覀兠看味家獜念^開始遍歷尋找季惯。

為了克服單向性這一缺點(diǎn)吠各,設(shè)計(jì)出了雙向鏈表。雙向鏈表(double linked list)是在單鏈表的每個(gè)結(jié)點(diǎn)中勉抓,再設(shè)置一個(gè)指向其前驅(qū)結(jié)點(diǎn)的指針域走孽。所以在雙向鏈表中的結(jié)點(diǎn)都有兩個(gè)指針域,一個(gè)指向直接后繼琳状,一個(gè)指向直接前驅(qū)磕瓷。

//線性表的雙向鏈表存儲(chǔ)結(jié)構(gòu)
typedef struct DulNode
{
    ElemType data;
    struct DuLNode *prior;//直接前驅(qū)指針
    struct DuLNode *next;//直接后繼指針
}DulNode,*DuLinkList;

既然單鏈表可以有循環(huán),那么雙向鏈表當(dāng)然可以是循環(huán)表念逞。

由于是雙向鏈表困食,對(duì)于鏈表中某一結(jié)點(diǎn)p,它的后繼的前驅(qū)是它自己翎承。它的前驅(qū)的后繼自然也是它自己硕盹。即:

p->next->prior = p = p->prior->next

插入操作時(shí),其實(shí)并不復(fù)雜叨咖,但是順序很重要瘩例。
假設(shè)存儲(chǔ)元素e的結(jié)點(diǎn)為s,要實(shí)現(xiàn)將結(jié)點(diǎn)s插入到p和p->next之間需要下面幾部甸各。

s->prior = p;//把p賦給s的前驅(qū)
s->next = p->next;//把p->next賦給s的后繼
p->next->prior = s;//把s賦給p->next的前驅(qū)
p->next = s;//把s賦給p的后繼

如要?jiǎng)h除結(jié)點(diǎn)p垛贤,只要下面兩步驟。

p->prior->next = p->next;//把p->next賦給p->prior的后繼
p->next->prior = p->prior;//把p->proir賦給p->next的前驅(qū)
free(p);//釋放p的空間

雙向鏈表對(duì)于單鏈表來說趣倾,要更復(fù)雜一些聘惦,對(duì)于插入和刪除時(shí),需要小心儒恋。
另外由于它每個(gè)結(jié)點(diǎn)需要幾輪兩份指針善绎,所以在空間上是要占用略多一些的。不過由于良好的對(duì)稱性诫尽,使得對(duì)某個(gè)結(jié)點(diǎn)的前后結(jié)點(diǎn)的操作禀酱,帶來了方便,可以有效提高算法的時(shí)間性能牧嫉。

說白了剂跟,也就是空間換時(shí)間

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末驹止,一起剝皮案震驚了整個(gè)濱河市浩聋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌臊恋,老刑警劉巖衣洁,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異抖仅,居然都是意外死亡坊夫,警方通過查閱死者的電腦和手機(jī)砖第,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來环凿,“玉大人梧兼,你說我怎么就攤上這事≈翘” “怎么了羽杰?”我有些...
    開封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)到推。 經(jīng)常有香客問我考赛,道長(zhǎng),這世上最難降的妖魔是什么莉测? 我笑而不...
    開封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任颜骤,我火速辦了婚禮,結(jié)果婚禮上捣卤,老公的妹妹穿的比我還像新娘忍抽。我一直安慰自己,他們只是感情好董朝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開白布鸠项。 她就那樣靜靜地躺著,像睡著了一般益涧。 火紅的嫁衣襯著肌膚如雪锈锤。 梳的紋絲不亂的頭發(fā)上驯鳖,一...
    開封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天闲询,我揣著相機(jī)與錄音,去河邊找鬼浅辙。 笑死扭弧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的记舆。 我是一名探鬼主播鸽捻,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼泽腮!你這毒婦竟也來了御蒲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤诊赊,失蹤者是張志新(化名)和其女友劉穎厚满,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碧磅,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年神汹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蜜另。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡货邓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出四濒,到底是詐尸還是另有隱情换况,我是刑警寧澤,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布盗蟆,位于F島的核電站复隆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏姆涩。R本人自食惡果不足惜挽拂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望骨饿。 院中可真熱鬧亏栈,春花似錦、人聲如沸宏赘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽察署。三九已至闷游,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贴汪,已是汗流浹背脐往。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扳埂,地道東北人业簿。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像阳懂,于是被迫代替她去往敵國(guó)和親梅尤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

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