鏈表是一種常見的基礎數據結構。所謂數據結構也就是數據對象在計算機中的組織方式。這裏的組織方式可以看成是物理存儲結構,也就是數據對象在計算機中是怎麼存儲的。
有一種我們平常比較熟悉的存儲方式,順序存儲結構。比如我們經常使用的數組。但是這種存儲方法使用之前得開闢存儲空間,又可能會造成上溢或者內存的浪費,同時也不便於我們進行數據的擴充;當然也有好處這種方法便於隨機存取。
對於鏈式存儲而言,克服了使用前需要確定其大小的缺點,可以充分使用計算機的內存;但是鏈表增加了每個結點的指針域,空間開銷會增大。
鏈表在存儲的時候將存儲空間劃分爲一小塊一小塊的存儲結點,因爲要確定每一個結點間的關係存儲結點中主要分爲兩部分,一部分是數據域,另一部分是指針域。
數據域 | 指針域 |
V(i) | NEXT(i) |
V(i+1) | NEXT(i+1) |
鏈表主要有單向鏈表、雙向鏈表、循環鏈表等。
對於鏈表而言主要操作有創建一個空鏈表、插入新元素、刪除一個元素、將兩個鏈表合成一個、逆轉鏈表、複製鏈表、鏈表的排序、鏈表的查找;本次主要學習了前三個。
(一)單向鏈表
只有一個指針,來記錄每一個結點的後繼結點的位置。其有一個頭結點HEAD和一個尾節點NULL,當HEAD=NULL 時表示空表
(1)創建一個鏈表
主要有如下步驟: 動態的分配內存 p = (LINK * ) malloc (sizeof(LINK));
創建頭結點
進行剩餘結點的創建
將最後一個結點的指針域指向NULL p->next = NULL;
返回頭指針
代碼實現如下:
//函數功能:創建鏈表
LINK *build(int num) //傳入創建結點的數量
{
int i;
LINK *pt,*head,*prept;
pt = (LINK*)malloc(sizeof(LINK)); //分配空間
if (pt != NULL) //判斷空間是否分配成功
{
scanf("%d%d",&pt->number,&pt->score);
head = pt; //建立頭節點
prept = pt;
}
else
{
printf("Failed\n");
exit(0);
}
for (i = 1; i < num; i ++) //存儲鏈表的信息
{
pt = (LINK*)malloc(sizeof(LINK));
if (pt != NULL)
{
scanf("%d%d",&pt->number,&pt->score);
prept->next = pt;
prept = pt;
}
else
{
printf("Failed\n");
exit(0);
}
}
pt->next = NULL; //處理尾節點
return head;
}
(2)查找結點
主要步驟如下: 先將指針指到頭結點
從頭節點開始查找,有兩種結果
若找到則將此結點返回
若至尾節點仍未找到則返回錯誤信息
代碼實現如下:
//函數功能:查找所要找的節點
LINK *FindNode(LINK *head,int index)
{
LINK *pt=head;
int i=1;
while(i<index&&pt!=NULL)
{
pt=pt->next;
i++;
}
return pt;
}
(3)插入結點
主要步驟如下:
找到正確的位置p
申請新結點t並對新結點的信息進行賦值
將新結點t插在p之後
t ->next = p ->next;
p ->next = t;
代碼實現如下:
bool Insert( List L, ElementType X, Position P )
{ /* 這裏默認L有頭結點 */
Position tmp, pre;
/* 查找P的前一個結點 */
for ( pre=L; pre&&pre->Next!=P; pre=pre->Next ) ;
if ( pre==NULL ) { /* P所指的結點不在L中 */
printf("插入位置參數錯誤\n");
return false;
}
else { /* 找到了P的前一個結點pre */
/* 在P前插入新結點 */
tmp = (Position)malloc(sizeof(struct LNode)); /* 申請、填裝結點 */
tmp->Data = X;
tmp->Next = P;
pre->Next = tmp;
return true;
}
}
注意:此處t ->next = p ->next; p ->next = t;順序不可調換
(4)刪除一個結點
主要步驟如下:
找到刪除結點的前一個結點p
刪除p之後的那個結點
釋放被刪除結點的內存
t = p ->next;
p ->next = t ->next;
free(t);
代碼實現如下:
/* 帶頭結點的刪除 */
bool Delete( List L, Position P )
{ /* 這裏默認L有頭結點 */
Position tmp, pre;
/* 查找P的前一個結點 */
for ( pre=L; pre&&pre->Next!=P; pre=pre->Next ) ;
if ( pre==NULL || P==NULL) { /* P所指的結點不在L中 */
printf("刪除位置參數錯誤\n");
return false;
}
else { /* 找到了P的前一個結點pre */
/* 將P位置的結點刪除 */
P = pre->Next;
pre->Next = P->Next;
free(P);
return true;
}
}
注意:刪除一個結點後必須釋放該結點所佔用的空間,爲此代碼中先將待刪除結點保存在了P中,最後再釋放P。
(4) 單向鏈表的逆轉
將P逆轉成Q。開始時P=L;Q=NULL。
循環的主體部分就是將P的第一個元素插入到Q的表頭處,同時更新P、Q的值。在把P的第一元素插入到Q的表頭時還需要知道P的新的頭節點在哪因而還需要一個變量t來記錄。
t = p->next;
p->next = q;
q = p;
p = t;
代碼實現如下:
struct Node *reverse(struct Node *L)
{
struct Node *p,*q,*t;
p = L, q = NULL;
while(p != NULL){
t = p->next;
p->next = q;
q = p;
p = t;
}
return q;
}
(二) 雙向鏈表
單向鏈表的構成使得找到某一單元的後繼單元十分方便,但是若要尋找某一單元的前驅單元則需要從頭開始尋找,十分麻煩。雙向鏈表犧牲了內存空間,來減少操作。其實雙向鏈表也就是在單向鏈表的基礎上增加了指向前驅結點的指針。
對於雙向鏈表而言插入、刪除、遍歷的基本思路和單向鏈表相似,但需要考慮前後兩個鏈
插入代碼實現如下:
t ->pre = p;
t ->next = p ->next;
p ->next ->pre = t;
p ->next = t;