線性表的鏈式存儲結構

鏈式存儲結構思想

我們需要讓相鄰元素之間留有足夠餘地,那乾脆不考慮相鄰位置,哪裏有空位置就到哪裏,只是讓每個元素記住它下一個元素的位置在哪裏,這樣,我們就可以在第一個元素時,知道第二個元素的位置(內存地址)找到它;在第二個元素時,再找到第三個元素的位置,這樣所有的元素都可以通過遍歷找到。

鏈式存儲結構定義

爲了表示每個數據元素ai與其後繼元素ai+1之間的邏輯關係,對數據元素ai來說,除了存儲其本身的信息之外,還需要存儲一個指示其直接後繼的信息(即直接後繼的存儲位置)。我們把存儲數據元素信息的域稱爲數據域,把存儲直接後繼位置的域稱爲指針域。指針域中存儲的信息稱作指針或鏈。這兩部分組成元素ai的存儲映像,稱爲結點(Node)。

這裏寫圖片描述

頭指針與頭結點的異同

頭指針:

  • 頭指針是指鏈表指向第一個結點的指針,若鏈表有頭結點,則是指向頭結點的指針;
  • 頭指針具有標識作用,所以常用頭指針冠以鏈表的名字;
  • 無論鏈表是否爲空,頭指針均不爲空。頭指針是鏈表的必要元素。

頭結點:

  • 頭結點是爲了操作的統一和方便而設立的,放在第一元素的結點之前,其數據域一般無意義(也可存放鏈表的長度);
  • 有了頭結點,對在第一元素結點前插入結點和刪除第一結點,其操作與其它結點的操作就統一了;
  • 頭結點不一定是鏈表必須要素。

單鏈表爲空即:頭指針指向空

單鏈表中,我們在C語言中可用結構指針來描述。

/*線性表的單鏈表存儲結構*/
typedef struct Node
{
   ElemType data;
   struct Node *next;
}
typedef struct Node *LinkList; /*定義LinkList*/

在這個結構定義中,我們知道,結點由存放數據元素的數據域和存放後繼結點地址的指針域組成。假設p是指向線性表第i個元素的指針,則該結點ai的數據域我們可以用
p->data來表示,p->data的值是一個數據元素,結點ai的指針域可以用p->next來表示,p->next的值是一個指針。p->next指向第i+1個元素,即指向ai+1的指針。也就是說p->data=ai,那麼p->next->data=ai+1。

單鏈表讀取

在線性表中要知道任意元素的存儲位置是很容易的,但是對於單鏈表中要實現獲取第i個元素的數據操作GetElem,在算法上要麻煩一些。

獲取鏈表第i個數據的算法思路:

  1. 聲明一個結點p指向鏈表的第一個結點,初始化j從1開始;
  2. 當j< i 時,就遍歷鏈表,讓p的指針向後移動,不斷指向下一個結點,j累加1;
  3. 若到鏈表末尾p爲空,則說明第i個元素不存在;
  4. 否則,查找成功,返回結點p的數據。

實現代碼如下:

/*初始條件:單鏈表已經存在,1<=i<=ListLength(L)*/
/*操作結果:用e返回L中第i個數據元素的值*/
Status GetElem(LinkList L,int i,ElemType *e)
{
   int j=1;     /*j爲計數器*/
   LinkList p;  /*聲明一個結點p*/
   p=L->next;   /*讓p指向鏈表L的第一個結點*/
   while(p && j<i)
   {
      p=p->next;  /*讓p指向下一個結點*/
      ++j;
   }
   if(!p || j>i)
      return ERROR;    /*第i個元素不存在*/
   *e=p->data;         /*獲取第i個元素的數據*/
   return OK;
}

因此該算法最壞的情況就是i=n時,此時需要遍歷n-1次,此時的時間複雜度爲O(n)。但是鏈式存儲結構的數據插入與刪除則要容易很多。

單鏈表插入與刪除

單鏈表插入

先來看看單鏈表插入。假設存儲元素e的結點爲s,要實現結點p,p->next和s之間的邏輯關係變化,只需將結點s插入到結點p和結點p->next之間即可。根本不用動其它結點,只需要讓s->next和p->next的指針做一點改變即可。

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

單鏈表第i個數據插入結點的算法思路:

  1. 聲明一個結點p指向鏈表第一個結點,初始化j從1開始;
  2. 當j < i 時,就遍歷鏈表,讓p的指針向後移動,不斷指向下一個結點,j累加i;
  3. 若到鏈表末尾p爲空,則說明第i個元素不存在;
  4. 否則查找成功,在系統中生成一個空結點s;
  5. 將數據元素e賦值給s->data;
  6. 單鏈表中的插入標準語句爲:s->next=p->next;p->next=s;
  7. 返回成功。

實現代碼如下:

/*初始條件:單鏈表已經存在,1<=i<=ListLength(L)*/
/*操作結果:在L中第i個位置之前插入新的數據元素e,L的長度加1*/
Status ListInsert(LinkList *L,int i,ElemType e)
{
   int j=1;        /*j爲計數器*/
   LinkList p,s;  /*聲明兩個結點p,s*/
   p = *L;        /*讓p指向鏈表L的第一個結點*/
   while(p && j<i)
   {
      p=p->next;  /*讓p指向下一個結點*/
      ++j;
   }
   if(!p || j>i)
      return ERROR;    /*第i個元素不存在*/
   s=(LinkList)malloc(sizeof(Node));/*生成新結點(C標準函數)*/
   s->data=e;
   s->next=p->next;/*將p的後繼結點賦值給s的後繼結點*/
   p->next=s;      /*將s賦值給p的後繼*/
   return OK;
}

單鏈表刪除

設存儲元素ai的結點爲q,要實現將結點q刪除單鏈表的操作,其實就是將它的前驅結點的指針繞過,指向它的後繼結點即可。其實就只有一步,即:
p->next=p->next->next,用q來取代p->next。

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

其中單鏈表第i個數據刪除結點的算法思路爲:

  1. 聲明一個結點p指向鏈表第一個結點,初始化j從1開始;
  2. 當j < i 時,就遍歷鏈表,讓p的指針向後移動,不斷指向下一個結點,j累加i;
  3. 若到鏈表末尾p爲空,則說明第i個元素不存在;
  4. 否則查找成功,將欲刪除的結點p->next賦值給q;
  5. 單鏈表的刪除標準語句爲:p->next=q->next;
  6. 將q結點中的數據賦值給e,作爲返回;
  7. 釋放q結點;
  8. 返回成功。

實現代碼如下:

/*初始條件:單鏈表已經存在,1<=i<=ListLength(L)*/
/*操作結果:在L中第i個位置之前插入新的數據元素e,L的長度減1*/
Status ListDelete(LinkList *L,int i,ElemType *e)
{
   int j=1;        /*j爲計數器*/
   LinkList p,q;  /*聲明兩個結點p,s*/
   p = *L;        /*讓p指向鏈表L的第一個結點*/
   while(p && j<i)
   {
      p=p->next;  /*讓p指向下一個結點*/
      ++j;
   }
   if(!p || j>i)
      return ERROR;    /*第i個元素不存在*/
   q=p->next;
   p->next=q->next;
   *e=p->data;
   free(q);      /*讓系統回收此結點,釋放內存*/
   return OK;
}
發佈了44 篇原創文章 · 獲贊 1 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章