學習數據結構--第二章:線性表(鏈式存儲、單鏈表、雙鏈表、循環鏈表)

第二章:線性表(鏈式表示)

學習數據結構–第二章:線性表(順序存儲、插入、刪除) 這篇文章講到線性表的順序表示也就是順序表,順序表雖然可以隨機存儲,但是在初始化的時候需要申請一大塊連續的存儲空間,且在執行插入和刪除操作時,也需要大量的移動元素,時間複雜度比較高,下面講線性表的另一種存儲結構:鏈式存儲

1.單鏈表的定義

線性表的鏈式存儲又稱:單鏈表 ,通過一組任意的存儲單元來存儲線性表中的數據元素。

數據元素存儲在任意位置,不一定連續,通過指針實現線性邏輯關係。

我們把單鏈表中這樣 數據加地址的組合 叫做單鏈表的一個結點,一個結點存儲數據元素的數據域和下一個結點(數據元素)的地址的指針域組成。

在這裏插入圖片描述
單鏈表有兩種創建方式

  • 無頭結點的單鏈表
  • 有頭節點的單鏈表

有頭節點的單鏈表,它的頭節點的數據域一般不存儲數據,它的指針域存儲第一個結點的地址。
優點:

  • 鏈表的第一個位置和其他位置的操作統一(比如插入操作:無頭節點的鏈表,在表中插入結點的時候兩邊都有結點,而在表頭插入結點的時候左邊是沒有結點的,而有頭節點就都是一樣的。)
  • 空表和非空表的操作統一

在這裏插入圖片描述

2.單鏈表的基本操作

2.1頭插法建立

在這裏插入圖片描述

//頭插法
LinkList List_HeadInsert (LinkList &L){
      LNode *s;
      int x;
      L=(LinkList)malloc(sizeof(LNode));
      L->next=NULL;
      scanf("%d",&x);
      while(x!=9999){
         s=(LNode*)malloc(sizeof(LNode));
         s->data=x;
         s->next=L->next;
         L->next=s;
         scanf("%d",&x);
      }
      return L;
}

在這裏插入圖片描述
時間複雜度:O(n)

2.2尾插法建立

在這裏插入圖片描述

LinkList List_TailInsert (LinkList &L){
     int x;
     L=(LinkList)malloc(sizeof(LNode));
     LNode *s,*r=L; //注意這裏重新定義一個指針r,作爲尾指針,同時初始化爲了頭節點
     scanf("%d",&x);
     while(x!=999){
        s=(LNode*)malloc(sizeof(LNode));
        s->data=x;
        r->next=s;
        r=s; //修改尾指針,指向新插入的結點
        scanf("%d",&x);
     }
	 r->next=NULL;
	 return L;
}

時間複雜度:O(n)

2.3按序號查找&按值查找

按照和按序號查找都要遍歷單鏈表。

按序號查找

LNode *GetElem(LinkList L,int i){
    int j=1; //標識當前結點的序號
    LNode *p=L->next; //當前所查找的結點,初始化爲頭節點的下一個結點,因爲頭節點不保存數據
    if(i==0){ //序號不合法
       return L;
    }
    if(i<1){ //序號不合法
       return NULL;
    }
    while(p&&j<i){ //當結點不爲空,且序號小於j的時候,繼續循環
      p=p->next;
      j++;
    }
    return p;
}

時間複雜度:O(n)

按序值查找

LNode *LocateElem(LinkList L,ElemType e){
    //初始化一個指針指向頭節點的下一個結點
    LNode *p=L->next;
    //判斷結點不爲空,且數據不爲e
    while(p!=NULL&&p->data!=e){
     //如果結點不爲空,且值不爲e,則指針繼續向下移動
      p=p->next;
    }
    return p;
}

時間複雜度:O(n)

2.4插入結點

插入有兩種方式,前插法和後插入法,比如插入位置爲 i ,前插法就是在 i 的位置之前插入, 後插法就是在 i 的位置之後插入,所以前插法要找 i-1 位置,而後插法要找 i 的位置。所以如果 i 的位置是已知的,這樣兩種方法就會產生區別,前插法仍然需要遍歷鏈表O(n),而後插法直接使用這個位置即可O(1)。後插法是可以實現前插法的,插入之後交換兩個結點的位置即可。下面演示前插法:

插入結點首先要知道插入的位置,假如插入位置爲 i ,則需要知道 i-1 結點的位置。接着修改新結點的指針指向 i-1結點的下一個位置,然後修改i-1結點的指針指向新插入結點。

在這裏插入圖片描述
注意 下面的代碼,不能交換位置。

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

why???
這個順序是不能交換的,如果交換會出現i結點地址丟失的問題。
p->next=s;這時已經將p結點中存儲的i結點的地址給覆蓋了,成了新結點的地址,接着再s->next=p->next;這相當於講s結點的指針指向了他自己。這樣就把後面的鏈表給丟棄了。

2.5刪除結點

結點的位置未知

假如要刪除鏈表中第 i 號結點的位置,修改第 i-1號結點的指針,讓其指向第 i+1號結點的位置。這是要注意要使用一個指針指向第i個結點,因爲修改之後我們會失去第 i 個結點的位置,這樣後面就無法釋放i結點的空間。

在這裏插入圖片描述
結點的位置已知*p

這時可以先交換p結點和後一個結點的數據,然後刪除後一個結點即可,注意要有一個指針指向後一個結點,方便之後釋放空間。

在這裏插入圖片描述

2.6求表長

有頭節點和無頭節點的鏈表判斷是不一樣的。
在這裏插入圖片描述

3.特殊鏈表

3.1雙鏈表

在使用單鏈表的時候,我們知道當前結點i的指針,在執行插入,刪除等需要知道它的前驅結點的操作,我們需要通過按序號查找的方式查找到他的前驅結點。這樣時間複雜度是O(n)。

在這裏插入圖片描述

如果節點中有一個直接指向它的前驅結點的指針,那麼我們就可以直接找到它的前驅節點了。所以這樣就出現了雙鏈表。

在這裏插入圖片描述
在這裏插入圖片描述

3.1雙鏈表插入操作

在這裏插入圖片描述
前插法和後插法的時間複雜度都是O(1)。這個插入順序是可以調的,但是第一個和第二步,必須在第四步之前,因爲第二步我們需要i+1結點的位置。

在單鏈表中,在表頭、表中和表尾的插入步驟相同。但是注意在雙鏈表中,在表頭和表中跟在表尾插入是不一樣的步驟。在雙鏈表的表尾進行插入的時候要注意表尾後面沒有下一個結點,所以不能修改下一個結點指向前驅的指針,否則會出現錯誤。

3.1雙鏈表刪除操作

首先找到要刪除的結點(q)的前驅結點的指針,設爲p,接着直接修改指針,釋放空間即可。時間複雜度爲O(1)。同樣在表尾進行刪除的時候也是不一樣的。
在這裏插入圖片描述

3.2循環鏈表

3.2.1循環單鏈表

假設我們使用單鏈表,我們只知道尾指針,但是需要知道頭指針,這時候是無法知道的。
這是如果將單鏈表的最後一個結點的指針指向頭節,這樣就可以找到的頭指針,這樣的鏈表形成一個環,叫做循環單鏈表。
這樣就只設置一個尾指針就行了,而且效率更高:因爲如果只有頭指針,我們想找到尾指針,需要遍歷單鏈表,但是如果有一個尾指針,我們找頭指針直接就可以找到。
在循環單鏈表插入和刪除操作,在每一個位置都是一樣。

在這裏插入圖片描述

3.2.1循環雙鏈表

我們需把鏈表最後一個結點的指針修改爲頭節點,且需要修改頭節點的前驅指針指向最後一個結點。這時每一個位置的插入和刪除操作都是一樣。在這裏插入圖片描述

3.2.3循環鏈表判空

我們發現在循環鏈表中,我們利用了每一個結點的指針,也就是說在循環鏈表中,沒有空指針了,這時該怎麼判空呢??請看下圖:
在這裏插入圖片描述

3.3靜態鏈表

靜態鏈表:就是使用數組來實現的鏈式存儲結構的鏈表

單鏈表:
在這裏插入圖片描述
靜態鏈表:

在這裏插入圖片描述
靜態鏈表中每個結點既有自己的數據部分,還需要存儲下一個結點的位置,所以靜態鏈表的存儲實現使用的是結構體數組,包含兩部分: 數據域遊標(存放的是下一個結點在數組中的位置下標)。

#define MaxSize 50
typedef struct DNode{
       ElemType data;
       int next;
}SLinkList[MaxSize];
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章