一、鏈表
在鏈表存儲中,每個結點不僅包含所存元素的信息,還包含元素之間邏輯關係的信息,如單鏈表中前驅結點包含後繼結點的地址信息,這樣就可以通過前驅結點中的地址信息找到後繼結點的位置。
鏈表的特性:
- 不支持隨機訪問
- 支持存儲空間的動態分配
- 鏈表中進行插入操作無須移動元素
二、 單鏈表
在每個結構中除了包含數據域外,還包含一個指針域,用以指向其後繼結點。
- 帶頭結點的單鏈表中,頭指針 head 指向頭結點,頭結點的值域不包含任何信息,從頭結點的後繼結點開始存儲數據信息。頭指針 head 始終不等於 NULL,head -> next 等於 NULL 的時候,鏈表爲空。
- 不帶頭結點的單鏈表中的頭指針 head 直接指向開始結點,當 head 等於 NULL 的時候,鏈表爲空。
兩者最明顯的區別是,帶頭結點的的單鏈表中有一個結點不存儲信息,只是作爲標誌,而不帶頭結點的單鏈表的所有結點都存儲信息。
結構體定義
// 結構體定義
typedef struct LNode
{
int data;
struct LNode *next;
}LNode;
查找算法
// 查找算法:在單鏈表L中查找值爲e的元素(帶頭結點)
LNode* findElem(LNode *C,int e)
{
LNode *p=C->next; // 結構體是指針,訪問時用->
while(p!=NULL)
{
if(p->data==e)
return p; // 返回結點指針
p=p->next;
}
return p; // 返回NULL
}
插入算法
// 插入算法:往結點p的後面插入結點s
void insertElem(LNode *&p,LNode *&s)
{
s->next=p->next;
p->next=s;
}
刪除算法
// 刪除算法:刪除結點p後面的結點
void delElem(LNode *&p)
{
q=p->next;
p->next=q->next;
free(q);
}
尾插法建立鏈表
// 尾插法建立鏈表:假設有n個元素存儲在數組a中,用尾插法建立鏈表C
void createListR(LNode *&C,int a[],int n) // 因爲需要對鏈表C進行修改,故要加上&表示引用
{
C=(LNode *)malloc(sizeof(LNode)); // 申請C的頭結點空間
C->next=NULL;
int i;
LNode *s,*r; // s用來指向新申請的結點,r用來指向鏈表C的尾結點
r=C;
for(i=0;i<n;i++)
{
s=(LNode *)malloc(sizeof(LNode));
s->data=a[i];
r->next=s;
r=r->next; // 由於r是指針(地址),故修改r也相當於修改了C
}
r->next=NULL;
}
頭插法建立鏈表
// 頭插法建立鏈表:假設有n個元素存儲在數組a中,用頭插法建立鏈表C
void createListR(LNode *&C,int a[],int n) // 因爲需要對鏈表C進行修改,故要加上&表示引用
{
C=(LNode *)malloc(sizeof(LNode)); // 申請C的頭結點空間
C->next=NULL;
int i;
LNode *s; // s用來指向新申請的結點
for(i=0;i<n;i++)
{
s=(LNode *)malloc(sizeof(LNode));
s->data=a[i];
s->next=C->next; // s所指新結點的指針域 next 指向C中的開始結點
C->next=s; // 頭結點的指針域 next 指向s結點,使得s成爲新的開始結點
}
}
單鏈表的歸併
// 單鏈表的歸併:A和B是兩個單鏈表(帶表頭結點),其中元素遞增有序。設計一個算法,將A和B歸併成一個按元素值
// 非遞減有序的鏈表C,C由A和B中的結點組成
void merge(LNode *A,LNode *B,LNode *&C)
{
LNode *p=A->next; // 指針p指向鏈表A的開始結點(最小值結點)
LNode *q=B->next;
LNode *r; // r始終指向C的終端結點
C=A; // 用A的頭結點作爲C的頭結點
C->next=NULL;
free(B);
r=C;
while(p!=NULL&&q!=NULL )
{
if(q->data>=p->data)
{
r->next=p;
p=p->next;
r=r->next;
}
else
{
r->next=q;
q=q->next;
r=r->next;
}
}
r->next=NULL;
if(p!=NULL)r->next=p;
if(q!=NULL)r->next=q;
}
二、雙鏈表
在每個結構中除了包含數據域外,還包含兩個指針域,用以分別指向其後繼結點和前驅結點。同樣,雙鏈表也分爲帶頭結點的雙鏈表和不帶頭結點的雙鏈表,情況類似於單鏈表。
- 帶頭結點的雙鏈表,當 head ->next 爲 NULL 時鏈表爲空
- 不帶頭結點的雙鏈表,當 head 爲 NULL 時鏈表爲空
結構體定義
// 結構體定義
typedef struct DLNode
{
int data;
struct DLNode *next;
struct DLNode *prior;
}DLNode;
查找算法
// 查找算法:在雙鏈表L中查找值爲e的元素(帶頭結點)
DLNode* findElem(DLNode *C,int e)
{
DLNode *p=C->next; // 結構體是指針,訪問時用->
while(p!=NULL)
{
if(p->data==e)
return p; // 返回結點指針
p=p->next;
}
return p; // 返回NULL
}
插入算法
// 插入算法:往結點p的後面插入結點s
void insertElem(DLNode *&p,DLNode *&s)
{
s->next=p->next;
s->prior=p;
p->next->prior=s;
p->next=s;
}
刪除算法
// 刪除算法:刪除結點p後面的結點
void delElem(DLNode *&p)
{
q=p->next;
p->next=q->next;
q->next->prior=p;
free(q);
}
尾插法建立鏈表
// 尾插法建立鏈表:假設有n個元素存儲在數組a中,用尾插法建立鏈表C
void createDlistR(LNode *&C,int a[],int n) // 因爲需要對鏈表C進行修改,故要加上&表示引用
{
C=(LNode *)malloc(sizeof(LNode)); // 申請C的頭結點空間
C->next=NULL;
int i;
DLNode *s,*r; // s用來指向新申請的結點,r用來指向鏈表C的尾結點
r=C;
for(i=0;i<n;i++)
{
s=(DLNode *)malloc(sizeof(DLNode));
s->data=a[i];
r->next=s;
s->prior=r; // 與單鏈表不同之處
r=r->next; // 由於r是指針(地址),故修改r也相當於修改了C
}
r->next=NULL;
}
三、循環單鏈表
只要將單鏈表的終端結點的 next 指針指向鏈表中的第一個結點(頭結點或開始結點)即可。
- 循環單鏈表可以實現從任意一個結點出發訪問鏈表中的任何結點,而單鏈表從任一結點出發後只能訪問這個結點本身及其後邊的所有結點。
- 帶頭結點的循環單鏈表,當 head ->next 爲 NULL 時鏈表爲空
- 不帶頭結點的雙鏈表,當 head 爲 NULL 時鏈表爲空
四、循環雙鏈表
將雙鏈表的終端結點的 next 指針指向鏈表中的第一個結點(頭結點或開始結點),將雙鏈表的第一個結點的 prior 指針指向鏈表中的終端結點。
- 帶頭結點的循環單鏈表,當 head ->next == head 或 head ->prior == head 時鏈表爲空
- 不帶頭結點的雙鏈表,當 head 爲 NULL 時鏈表爲空
五、靜態鏈表
-
一般鏈表結點空間來自於整個內存,靜態鏈表則來自於一個結構體數組。
-
數組中的每一個結點含有兩個分量:一個是數據元素分量 data ,另一個是指針分量,指示了當前結點的直接後繼結點在數組中的位置。
-
靜態鏈表中的指針不是我們通常所說的C語言中用來存儲內存地址的指針型變量,而是一個存儲數組下標的整型變量,通過它可以找到後繼結點在數組中的位置,其功能類似於真實的指針,因此稱其爲指針。