鏈式存儲結構思想
我們需要讓相鄰元素之間留有足夠餘地,那乾脆不考慮相鄰位置,哪裏有空位置就到哪裏,只是讓每個元素記住它下一個元素的位置在哪裏,這樣,我們就可以在第一個元素時,知道第二個元素的位置(內存地址)找到它;在第二個元素時,再找到第三個元素的位置,這樣所有的元素都可以通過遍歷找到。
鏈式存儲結構定義
爲了表示每個數據元素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個數據的算法思路:
- 聲明一個結點p指向鏈表的第一個結點,初始化j從1開始;
- 當j< i 時,就遍歷鏈表,讓p的指針向後移動,不斷指向下一個結點,j累加1;
- 若到鏈表末尾p爲空,則說明第i個元素不存在;
- 否則,查找成功,返回結點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個數據插入結點的算法思路:
- 聲明一個結點p指向鏈表第一個結點,初始化j從1開始;
- 當j < i 時,就遍歷鏈表,讓p的指針向後移動,不斷指向下一個結點,j累加i;
- 若到鏈表末尾p爲空,則說明第i個元素不存在;
- 否則查找成功,在系統中生成一個空結點s;
- 將數據元素e賦值給s->data;
- 單鏈表中的插入標準語句爲:s->next=p->next;p->next=s;
- 返回成功。
實現代碼如下:
/*初始條件:單鏈表已經存在,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個數據刪除結點的算法思路爲:
- 聲明一個結點p指向鏈表第一個結點,初始化j從1開始;
- 當j < i 時,就遍歷鏈表,讓p的指針向後移動,不斷指向下一個結點,j累加i;
- 若到鏈表末尾p爲空,則說明第i個元素不存在;
- 否則查找成功,將欲刪除的結點p->next賦值給q;
- 單鏈表的刪除標準語句爲:p->next=q->next;
- 將q結點中的數據賦值給e,作爲返回;
- 釋放q結點;
- 返回成功。
實現代碼如下:
/*初始條件:單鏈表已經存在,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;
}