線性表的鏈式存儲結構是用一組任意的存儲單元存儲線性表的數據元素,而非連續的存儲單元。每個結點中既包含數據元素又包含指向後繼結點的指針。通常我們會設置一個頭指針指向單鏈表,有時爲了算法的一致性,我們會在第一個結點之前附加一個頭結點,頭結點的數據域內可以不存放任何信息,也可存放諸如線性表長度之類的附加信息。
1、存儲結構
typedef struct LNode
{
ElemType data; //
數據域
struct LNode
*next; // 指針域,指向下一個結點,next爲結構體變量
}LNode, *LinkList; //
LinkList爲指向結構體LNode的指針
另一種定義
typedef struct LNode
*Link; // Link爲指向結構體LNode的指針,即Link爲 LNode* 的別名
struct LNode
{
ElemType data; //
數據域
Link next; //
指針域
};
注意:typedef的用法。
2、單鏈表的取數據元素操作
取第i個結點的數據,L爲帶頭結點的單鏈表的頭指針。
Status GetElem_L( LinkList L, int i,
ElemType &e )
{
LinkList p; int j;
p = L -> next; j = 1; //
p指向第一個數據結點,而非頭結點。j作爲計數器
while(
p && j<i ) // 向後查找,直到p指向第i個元素或p爲空才跳出循環
{
p = p -> next;
++ j;
}
if( !p
|| j>i) return ERROR; // 第i個元素不存在,返回ERROR
e = p -> data; //
取得第i個元素的數據
return OK;
}
注意:(1)單鏈表並非隨機存取的數據結構,每次取數據都要從前向後查找,這種操作效率低於順序表。
(2)時間複雜度O(n)。
3、單鏈表的插入操作
在第i個結點前插入新結點。首先生成一個新結點,然後找到第i-1個結點,將新節點的指向第i個結點,最後修改第i-1個結點的指針域,使其指向新節點。第i-1個結點的指針域要最後改,否則會丟失後面的結點。
Status ListInsert_L( LinkList L, int i,
ElemType e ) // L帶頭結點
{
LinkList p, s; int j;
p = L; j = 0; //
p指向頭結點
while(
p && j<i-1 ) // 尋找第i-1個結點
{
p = p -> next;
++ j;
}
if( !p
|| j>i-1) return ERROR; // 未找到第i-1個節點
s = ( LinkList )malloc( sizeof(LNode) ); //
生成新節點
s -> data = e;
s -> next = p -> next; //
新節點指向第i個結點
p -> next = s; // 第i-1個結點指向新節點
return OK;
}
注意:(1)加粗的兩句話不能寫顛倒,否則會丟失結點。
(2)時間複雜度是O(n)。時間浪費在查找第i-1個結點上了。
4、單鏈表的刪除操作
刪除單鏈表的第i個結點。首先查找第i-1個結點,然後讓他指向第i+1個結點,最後釋放第i個結點。
Status ListDelete_L( LinkList L, int i,
ElemType &e) // L帶頭結點
{
LinkList p, q; int j;
p = L; j = 0;
while(
p -> next && j < i-1 ) // 查找第i個結點,令p指向其前驅
{
p = p -> next;
++ j;
}
if(!(p
-> next)||j > i-1 ) return ERROR; // 刪除位置不合理
q = p -> next; //
令q指向第i個結點
p -> next = q -> next; //
令第i-1個結點指向第i+1個結點
e = q -> data; free(q); //
釋放第i個結點的內存
return OK;
}
注意:(1)先用p指針找第i-1個結點,然後令q指針指向第i個結點。
(2)時間複雜度是O(n)。
5、逆向建立帶頭結點的單鏈表(頭插法)
從表尾到表頭逆向插入節點。所建立的的單鏈表與原序列相反。
void CreateList_L_H( LinkList
*L, int n) // 建立n個數據元素的單鏈表
{
LinkList p; int i;
*L = ( LinkList )malloc( sizeof(LNode) ); //
建立頭結點
(*L) -> next = NULL;
for(
i = n; i > 0; --i )
{
p = ( LinkList )malloc( sizeof(LNode) ); //
生成新節點
scanf( &p->data ); //
輸入數據
p -> next = (*L) -> next; //
插入到表頭
(*L) -> next = p;
}
}
注意:(1)這種方法建立的單鏈表與給定的序列相反。
(2)由於L的內容變化了,所以參數用了指針的指針*L。使用時可寫成:CreateList_L_H(&L, 10)。
(3)時間複雜度:O(n)。
6、尾插法建立帶頭結點的單鏈表
每次都從表尾插入新節點。所建單鏈表與原序列順序相同。
void CreateList_L_R(
LinkList *L, int n) // 建立n個數據元素的單鏈表
{
LinkList p, r; int i;
*L = ( LinkList )malloc( sizeof(LNode) ); //
建立頭結點
(*L) -> next = NULL;
r = *L;
for(
i = n; i > 0; --i )
{
p = ( LinkList )malloc( sizeof(LNode) ); //
生成新節點
scanf( &p->data ); //
輸入數據
r -> next = p; //
插入到表尾
r = p;
}
}
注意:(1)這種方法建立的單鏈表與給定的序列相同。
(2)時間複雜度:O(n)。
7、兩個有序單鏈表的歸併操作
將兩個有序的單鏈表歸併爲一個有序的單鏈表。原有單鏈表是非遞減的且都有頭結點。
void MergeList_L( LinkList La, LinkList Lb, LinkList *Lc)
{
LinkList pa, pb, pc;
pa = La -> next; pb = Lb -> next; // pa指向La的第1個數據節點,pb指向Lb的第1個數據節點
*Lc = pc = La; //
La的頭結點作爲Lc的頭結點,pc指向頭結點
while( pa && pb ) //
pa,pb都不空
{
if( pa -> data < pb -> data)
{
pc -> next = pa; // pc的後繼設爲pa所指結點
pc = pa; //
pc指向pa所指結點
pa = pa -> next; // pa指向下一結點
}
else
{
pc -> next = pb; // pc的後繼設爲pb所指結點
pc = pb; //
pc指向pb所指結點
pb = pb -> next; // pb指向下一結點
}
}
pc -> next = pa ? pa : pb;
// 如果pa指針不爲空,說明La還有剩餘結點,則pc指向pa所指結點;反之,Lb不爲空,pc指向pb所指結 // 點
free(Lb); // 釋放Lb頭指針
}
注意:(1)與順序表的歸併算法思想相同,但是鏈表的歸併算法不需要申請Lc的空間,空間複雜度較低。
(2)這個算法中La,Lb的內容沒變,Lc的內容變了,使用時可寫成:MergeList_L(La, Lb, &Lc)。
(3)時間複雜度:O(n)。
8、銷燬單鏈表
從前向後,依次將結點釋放,最後釋放頭結點。
void DestroyList_L(
LinkList *L )
{
LinkList p, q;
q = NULL; p = (*L) -> next;
while(
p ) // 如果p不爲空,則循環
{
q = p -> next; //
q指向p的後繼結點
free(p); //
釋放p所指結點
p = q; //
p指向q所指的結點
}
free(L); *L = NULL; //
釋放頭結點
}
注意:(1)從前向後遍歷鏈表,依次刪除。
(2)時間複雜度:O(n)。
9、刪除區間操作
假設單鏈表的數據爲實數,且無序。刪除數據爲某區間(a, b)的結點。
Status RemoveRange_L(LinkList L, int lower, int upper)
// 帶頭結點的情況
{
LinkList t, x;
if(!L
-> next) return REEOR; // L爲空表
for(t
= L, x = t -> next; t && x = t -> next;)
{
if( x -> data < lower || x -> data > upper) //
範圍之外的結點保留
{
t = t -> next;
continue;
}
t -> next = x -> next; free(x); //
範圍值內的結點刪除
}
return OK;
}
Status RemoveRange_L(LinkList L, int lower, int upper)
// 不帶頭結點的情況
{
LinkList t, x;
if(!L) return ERROR;
for( t = L, x = t -> next; t && ( x = t -> next );
) // 考察第2至n個結點
{
if( x -> data < lower || x -> data
> upper)
{
t = t -> next;
continue;
}
t -> next = x -> next; free(x);
}
if( L -> data >= lower && L -> data <= upper) //
考察第1個結點
{
x = L;
L = L -> next;
free(x);
}
return OK;
}
注意:(1)帶頭結點的鏈表操作起來比較方便,對於不帶頭結點的鏈表要多考慮一步,即第一個結點的情況。
(2)時間複雜度:O(n)。