1. 程式人生 > >Day4 —— 靜態連結串列的概念及基本操作的實現

Day4 —— 靜態連結串列的概念及基本操作的實現

靜態連結串列的概念及基本操作的實現

首先,先回顧一下靜態連結串列的概念

靜態連結串列是對於一些早期的程式設計高階語言沒有指標而出來的一種描述單鏈表的結構,它以陣列來代替指標,那麼它是怎麼構成的呢?接下來一一道來。

首先,對於靜態連結串列中的每一個元素,都是由兩個資料域組成的,一個是data、一個是cur,也就是說陣列中的每一個下標都對應一個data和一個cur。其中data為元素的資料域,用來存放資料元素;cur為遊標,也就是相當於單鏈表中的next指標,用於存放該元素的後繼(也就是存放後繼元素在陣列中的下標)。通常我們把這種用陣列描述的連結串列叫做靜態連結串列(遊標實現法)。所以,我們一般以以下程式碼來實現靜態連結串列的結構:

#define MAX_SIZE 1000

/****** 統一的資料型別(這裡以角色ID和名稱為例)******/
typedef struct
{
    int id;
    char *name;
}ElementType;

/****** 定義靜態連結串列的結構 ******/
typedef struct
{
    ElementType data;   //資料域
    int next;           //int cursor; 遊標:如果為0表示無指向
}StaticLinkList[MAX_SIZE];
  • 另外,對於陣列第一個和最後一個元素要特殊處理,不存資料!

  • 並且通常把未被使用的陣列元素稱為備用連結串列。

  • 對於陣列的第一個元素,即下標為0的元素的cur就存放備用連結串列的第一個結點的下標;對於陣列的最後一個元素的cur則存放第一個有數值的元素的下標,相當於單鏈表中的頭結點!當整個連結串列為空表時,它的cur為0,如下圖:

image

  • 即靜態連結串列初始化時的狀態,程式碼實現如下:

/** 初始化連結串列 */
Status InitStaticLinkList(StaticLinkList slList)
{
    for(int i = 0; i < MAX_SIZE; i++)
    {
        //給元素資料域賦不可能的初值
        slList[i].data.id =
-1; slList[i].data.name = NULL; //初始化各個元素的遊標 slList[i].next = i + 1; } //將最後一個結點置空(空表時最後一個元素的遊標為0) slList[MAX_SIZE - 1].next = 0; return OK; }
  • 若靜態連結串列中已存入資料(這裡以1000為表最大長度),則它將處於下圖所示這種狀態:

    image

  • 此時D1這裡的遊標域就存有下一個元素的下標2,D2則存有D3的下標3,以此類推,到最後一個有值元素D999時,由於它後面已無元素,所以它的遊標為0。而最後一個元素的遊標則存放第一個有值元素D1的下標1,第一個元素則因表已存滿,所以遊標為1000。

  • 初始化完成後繼而到插入操作

  • 對於靜態連結串列,我們需要注意的是如何用靜態模擬動態連結串列結構的儲存空間的分配,需要時申請,不用時釋放。

  • 所以需要實現兩個函式用來分配空間以及釋放空間。

  • 對於分配空間,可以將所有未被使用過的以及已被刪除的元素用遊標鏈結成一個備用連結串列,每當進行插入時,便可以從備用連結串列取得第一個結點分配給插入的元素。

  • 分配空間的程式碼實現如下:

/** 為靜態連結串列分配一個空間的記憶體,返回ERROR表示分配失敗 */
Status mallocSSL(StaticLinkList slList)
{
    //判斷是否滿表,若滿表返回ERROR
    if(slList[0].next == MAX_SIZE - 1)
    {
        return ERROR;
    }

    //拿到第一個空閒結點的下標(備用連結串列第一個結點)
    int cursor = slList[0].next;

    //取出備用連結串列第一個結點
    if(cursor)
    {
        //讓新的空閒結點的下標為取出結點的next,也就是它的下一個結點變成了備用連結串列中的新的第一個結點
        slList[0].next = slList[cursor].next;
        
        //返回取出結點的下標
        return cursor;
    }

    //若cursor為0則分配失敗,返回ERROR
    return ERROR;
}
  • 從以上程式碼可以看到當取得備用連結串列第一個結點後,就將它的next值(即它的下一結點)賦值給第一個元素的next,也就是把它的下一結點變成了新的空閒結點,接下來可以繼續分配。

  • 接下來就可以實現插入操作了,對於在表中第pos個位置插入元素:先讓它先待在分配出來的結點裡,然後通過迴圈找到插入位置的前一個位置,再將該位置的next賦值給插入元素的next,即插入元素變成了剛剛找到的位置的下一結點的字首結點,最後再將插入元素的下標賦值給找到的位置的next,這樣插入的元素就插入到表中第pos個位置了,過程如下圖:

    image
  • 程式碼實現如下:

/** 向指定位置插入元素 */
Status InsertStaticLinkList(StaticLinkList slList, int pos, ElementType element)
{
    //判斷插入位置是否越界,若越界則返回ERROR
    if(pos < 1 || pos > GetStaticLinkList(slList) + 1)
    {
        return ERROR;
    }

    int cursor = MAX_SIZE - 1;  //取得最後一個元素的下標,可用於拿到第一個元素的下標

    //分配記憶體
    int newIndex = mallocSSL(slList);

    if(newIndex)
    {
        slList[newIndex].data = element;    //將插入元素賦給插入結點的資料域
        
        //找到插入位置的字首結點
        for(int i = 1; i <= pos - 1; i++)
        {
            cursor = slList[cursor].next;
        }
        
        //將該位置的next賦值給插入元素的next
        slList[newIndex].next = slList[cursor].next;
        
        //將插入元素的下標賦值給找到的位置的next
        slList[cursor].next = newIndex;
        
        return OK;
    }

    return ERROR;
}
  • 最後就到了靜態連結串列的刪除操作了,和插入一樣,我們需要先實現釋放(回收)結點的函式。

  • 對於回收結點,十分簡單明瞭,只需將指定位置的結點回收到備用連結串列第一個位置即可。首先,先令該位置的next為第一個元素的next,這樣這個結點就鏈結上了備用連結串列了,然後再將第一個元素的next變為該位置的下標,這樣這個位置的結點就回收到了備用連結串列的第一個位置了。

  • 實現程式碼如下:

/** 回收原始陣列中指定下標的空間 */
Status FreeStaticLinkList(StaticLinkList slList, int index)
{
    //將下標為index的空閒結點回收到備用連結串列
    slList[index].next = slList[0].next;
    
    //0號元素的next結點指向備用連結串列的第一個結點,表示index結點空閒
    slList[0].next = index;

    return OK;
}
  • 接下來就可以進行刪除操作了,對於刪除,先通過迴圈找到要刪除位置的字首結點,再生成一個整型變數delIndex用於儲存這個字首結點的next,即要刪除的位置,再令字首結點的next為delIndex的next,這樣就將要刪除位置的結點從表中斷開了,最後再用回收函式將該位置結點回收即可。

  • 實現程式碼如下:

/** 刪除連結串列中指定位置的元素 */
Status DeleteStaticLinkList(StaticLinkList slList, int pos)
{
    //判斷刪除位置是否越界
    if(pos < 1 || pos > GetStaticLinkList(slList))
    {
        return ERROR;
    }

    int cursor = MAX_SIZE - 1;  //取得最後一個元素的下標,可用於拿到第一個元素的下標

    //通過迴圈找到要刪除位置的字首結點
    for(int i = 1; i <= pos - 1; i++)
    {
        cursor = slList[cursor].next;
    }

    int delIndex = slList[cursor].next;             //要刪除的位置
    slList[cursor].next = slList[delIndex].next;    //將要刪除位置的結點從表中斷開

    //釋放空間
    FreeStaticLinkList(slList, delIndex);

    return OK;
}
  • 對於以上程式碼使用的獲取連結串列長度的函式GetStaticLinkList的實現程式碼如下:

/** 獲得靜態連結串列的長度 */
int GetStaticLinkList(StaticLinkList slList)
{
    int count = 0;  //計數器
    int cursor = slList[MAX_SIZE - 1].next; //獲得第一個元素的下標
    while(cursor)
    {
        //形如連結串列中的p = p->next
        cursor = slList[cursor].next;
        count++;
    }

    return count;
}
  • 到了這裡,靜態連結串列的基本操作就基本實現了,接下來進行一些測試。(測試用的MAX_SIZE的值為10)

  • 測試程式碼:

//測試函式
void Test_Fun();

int main()
{
    Test_Fun();
    return 0;
}

//測試函式
void Test_Fun()
{
   StaticLinkList slList;       //要操作的靜態連結串列

   InitStaticLinkList(slList);  //靜態連結串列的初始化

   printf("初始化後:\n");
   PrintStaticLinkList(slList); //列印連結串列

   ElementType element1;
   element1.id = 1;
   element1.name = "蝙蝠俠";
   InsertStaticLinkList(slList, 1, element1);

   ElementType element2;
   element2.id = 2;
   element2.name = "閃電俠";
   InsertStaticLinkList(slList, 1, element2);

   ElementType element3;
   element3.id = 3;
   element3.name = "蜘蛛俠";
   InsertStaticLinkList(slList, 1, element3);

   ElementType element4;
   element4.id = 4;
   element4.name = "AA俠";
   InsertStaticLinkList(slList, 1, element4);


   printf("\n\n插入元素後:\n");
   PrintStaticLinkList(slList);


   printf("\n刪除2號元素:\n");
   DeleteStaticLinkList(slList, 2);
   printf("\n刪除後:\n");
   PrintStaticLinkList(slList);

}
  • 測試結果:

image

  • 小結

  • 靜態連結串列的優缺點

    • 優點

      • 在插入和刪除操作時,只需要修改遊標,不需要移動元素,從而改進了在順序儲存結構中的插入和刪除操作需要移動大量元素的缺點
    • 缺點

      1. 沒有解決連續儲存分配帶來的表長難以確定的問題。
      2. 失去了順序儲存結構隨機存取的特性。
  • 總的來說,靜態連結串列其實就是為了給沒有指標的高階語言設計的一種能實現單鏈表能力的方法。

---------------------------------本文結束---------------------------------