C數據結構 - 靜態鏈表

對於線性鏈表可用一維數組來進行描述,這種描述方法便於在沒有指針類型的高級程序設計語言中使用鏈表結構,即使用數組描述的鏈表稱爲靜態鏈表。由於全局數組是存儲在靜態區也叫做靜態鏈表。

C語言具有指針能力使其非常容易地操作內存中的地址和數據,對於面向對象的語言雖然不使用指針,但因爲啓用了對象引用機制,從某種角度也間接實現了指針的某些作用。但對於早期的編程語言,由於沒有指針,鏈表結構等也就無法實現了,怎麼辦呢?於是,就有了使用數組來代替指針來描述單鏈表。使用數組描述的鏈表叫做靜態鏈表(static list),又稱爲遊標實現法。

動態鏈表

靜態鏈表和動態鏈表是線性錶鏈式存儲的兩種不同的表示方式。

  • 在動態鏈表中,節點的申請和釋放分別使用內存申請函數,如C語言的malloc()和free()兩個函數實現。所以鏈表長度沒有限制。由於是動態申請內存,所以每個節點的物理地址不連續,要通過指針來順序訪問。
  • 在靜態鏈表中,操作的是數組,不存在像動態鏈表的節點申請和釋放問題,所以需要自己實現這兩個函數,纔可以做插入和刪除操作。靜態鏈表使用類似於數組方法實現,是順序的存儲結構,在物理上是連續的,且需要預先分配地址空間大小,所以靜態鏈表的初始長度一般是固定的,在做插入和刪除操作時不需要移動元素,僅需要修改指針。

靜態鏈表原理

一個數組的邏輯分爲兩部分:空閒鏈表部分和非空閒鏈表部分,它們通過指針來連接,空閒鏈表由空閒頭節點連接起來,非空閒鏈表由非空閒頭節點連接起來。空閒鏈表本質上是作爲需要管理的內存,當插入節點時,向管理的內存部分申請空間。當刪除節點時,向管理的內存部分釋放內存。

空閒鏈表

  • 下標爲0的節點是空閒鏈表的頭節點
  • 下標爲1的節點是非空閒鏈表的頭節點

首先,讓數組的元素由兩個數據域組成,data和cursor。即數組的每個下標都對應一個data和一個cursor。數據域data用來存放數據元素,遊標cursor相當於單鏈表中的next指針,存放元素後繼在數組中的下標。

/*線性表的靜態鏈表存儲結構*/
#define MAXSIZE 1000

typedef struct
{
    ElemType data;//數據域
    int cursor;//遊標,爲0表示無指向。
} Component, StaticLinkList[MAXSIZE];

StaticLinkList space;

結構體數組中,space[0]作爲備用鏈表的頭指針,space[1]作爲鏈表的頭指針。

 

結構體數組

初始化數組狀態

  • 對數組第一個和最後一個元素作爲特殊元素處理,不存數據。
  • 把未被使用的數組元素稱爲備用鏈表
  • 數組第一個元素即下標爲0的元素的cursor就存放備用鏈表的第一個節點的下標
  • 數組最後一個元素的cursor則存放第一個有數值的元素的下標,相當於單鏈表中的頭節點作用。
  • 當整個鏈表爲空時則爲O^2

image.png

遊標實現法

線性表的靜態鏈表

/**
 * 將一維數組space中各分量鏈成一備用鏈表
 * space[0].cursor 爲頭指針,0表示空指針。
 */
Status InitList(StaticLinkList space)
{
    int i;
    for(i=0; i<MAXSIZE; i++){
        space[i].cursor = i+1;
    }
    //靜態鏈表爲空,最後一個元素的遊標爲0
    space[MAXSIZE-1].cursor = 0;
    return OK;
}

假設已經將數據存入靜態鏈表,比如分別存放甲乙丙丁午己庚等數據。

 

靜態鏈表

存放數據

靜態鏈表的插入操作

靜態鏈表如何模擬單鏈表進行插入和刪除操作呢?靜態鏈表要解決的是:如何用靜態鏈表模擬動態鏈表結構的存儲空間分配,也就是需要的時候申請,不需要的時候釋放。

爲了辨明數組中那些分量未被使用,解決的辦法是將所有未被使用過的及已被刪除的分量用遊標鏈成一個備用的鏈表,每當進行插入時,便可以從備用鏈表上取得第一個節點作爲待插入的新節點。

靜態鏈表的插入

 

靜態鏈表的插入

獲得空閒分量的下標

/**
 * 辨明數組中那些分量未被使用,解決的辦法是將所有未被使用多的及已被刪除的分量用遊標鏈成一個備用的鏈表。
 * 每當進行插入時,便可以從備用鏈表上取得第一個節點作爲待插入的新節點。
 */
int MallocSLL(StaticLinkList space)
{
    int i = space[0].cursor;//當前數組第一個元素的遊標,即第一個備用空閒的下標。
    //若備用空間鏈表非空則返回分配的節點的下標否則返回0
    if(space[0].cursor){
        //將下一個分量用來做備用
        space[0].cursor = space[i].cursor;
    }
    return i;
}

在靜態鏈表某個元素之前插入新元素

Status InsertSLL(StaticLinkList L, int i, ElemType e)
{   
    int j,k,l;
    //邊界檢查
    if(i<1 || i>ListLength(L)+1){
        return ERROR;
    }
    //獲得空閒分量的下標
    int j = MallocSSL(L);
    if(j){
        //將數據賦值給此分量的數據域
        L[j].data = e;
        //找到第i個元素之前的位置
        k = MAXSIZE - 1;//最後一個元素
        for(l=1; l<=i-1; l++){
            k = L[k].cursor;
        }
        //將第i個元素之前的遊標值賦值給新元素的遊標
        L[j].cursor = L[k].cursor;
        //將新元素的下標賦值給地i個元素之前的元素的遊標
        L[k].cursor = j;
        return OK;
    }
}



作者:JunChow520
鏈接:https://www.jianshu.com/p/e3362da4324f
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章