學習筆記——單鏈表的基本操作(C語言實現)
線性表的儲存結構有順序儲存結構(順序表)和鏈式儲存結構(連結串列)兩種。順序表在之前的部落格有介紹過,不明白的朋友可檢視:靜態分配順序表的基本操作及動態分配順序表的基本操作。相對於順序表來說,連結串列稍微難一些,本人花了兩天的時間認真查看了一些資料,終於大致明白了一些東西。現在做一些總結,分享給大家,有錯誤的地方歡迎大家指正。
一、相關概念術語
1、連結串列結點由資料域(存放本身資訊)和指標域(指向後繼結點的指標)構成。如下圖所示:
圖1 結點的構成
其C語言定義可表示為:
typedef struct LNode { int data; //data中存放結點資料域(預設是int型) struct LNode *next; //指向後繼結點的指標 }LNode;
2、頭結點:在開始結點之前的結點(可有可無)。其值域不包含任何資訊。
3、開始結點:第一個元素所在的結點。
4、頭指標:永遠指向連結串列中第一個結點的位置(如果連結串列有頭結點,頭指標指向頭結點;否則,頭指標指向開始結點)。
5、單鏈表
圖2 帶頭結點的單鏈表
(1)帶頭結點的單鏈表:頭指標head指向頭結點。頭指標head始終不等於NULL,head->next等於NULL的時候連結串列為空。
(2)不帶頭結點的單鏈表:頭結點head指向開始結點,即圖2中的a1,當head等於NULL時連結串列為空。
6、頭結點和頭指標的區別:頭指標是一個指標,頭指標指向連結串列的第一個結點(如果連結串列有頭結點,
頭指標指向開始結點);頭結點是一個實際存在的點,它包含有資料域和指標域。兩者在程式上的直接體現就是:頭指標至宣告
而沒有分配儲存空間,頭結點進行了宣告並分配了一個結點的實際實體記憶體。
二、程式碼部分
1、單鏈表結點定義
typedef struct LNode
{
int data; //data中存放結點資料域(預設是int型)
struct LNode *next; //指向後繼結點的指標
}LNode;
2、單鏈表的建立
單鏈表的建立有兩種方法,即頭插法和尾插法。原理如下圖所示:
圖3 頭插法與尾插法
(1)頭插法建立單鏈表(生成的連結串列是逆序的)的程式碼如下:
LNode *HeadCreateList(int len)
{
LNode *L = (LNode*)malloc(sizeof(LNode)); //建立一個頭結點
LNode *temp = L;//宣告一箇中間變數,指向頭結點,用於遍歷連結串列(曾因沒有這個中間變數而出錯)
temp->next = NULL; //該連結串列此刻只帶頭結點
for(int i=1;i<=len;i++) //迴圈申請len個結點來接收scanf得到的元素
{
LNode *p = (LNode*)malloc(sizeof(LNode)); //生成新結點
scanf("%d",&p->data); //用新申請的結點來接收scanf得到的元素
/* 以下兩條語句是頭插法的關鍵步驟,與本工程"Insert"函式原理一樣 */
p->next = temp->next; //新結點的next指標指向開始結點
temp->next = p; //頭結點的next指標指向新結點
}
return (LNode*)L;
}
程式執行結果如下:
(2)尾插法建立單鏈表(生成的連結串列是順序的)的程式碼如下:
LNode *TailCreateList(int len)
{
LNode *L = (LNode*)malloc(sizeof(LNode)); //建立一個頭結點
LNode *temp = L;//宣告一箇中間變數,指向頭結點,用於遍歷連結串列(曾因沒有這個中間變數而出錯)
temp->next = NULL;//該連結串列此刻只帶頭結點
for(int i=1;i<=len;i++) //迴圈申請len個結點來接收scanf得到的元素
{
LNode *p = (LNode*)malloc(sizeof(LNode)); //生成新結點
scanf("%d",&p->data); //用新申請的結點來接收scanf得到的元素
/* 以下兩條語句是尾插法的關鍵步驟 */
temp->next = p; //用來接納新結點
temp = p; //指向終端結點,以便於接納下一個到來的結點,此語句也可以改為"L = L->next"
}
temp->next = NULL; //此刻所有元素已經全裝入連結串列L中,L的終端結點的指標域置為NULL
return (LNode*)L;
}
程式執行結果如下:
2、連結串列中查詢某結點
因為連結串列不支援隨機訪問,即連結串列的存取方式是順序存取的(注意“儲存”與“存取”是兩個不一樣的概念),所以要查詢某結點,必須通過遍歷的方式查詢。例如:如果想查詢第5個結點,必須先遍歷走過第1~4個結點,才能得到第5個結點。程式碼如下:
int Serch(LNode *L, int elem)
{
LNode *temp = L;
int pos = 0;
int i = 1;
while(temp->next)
{
temp = temp->next;
if(elem==temp->data)
{
pos = i;
printf("The %d position in the list is %d\n",elem,pos);
return pos; //返回elem元素在順序表中的位置
}
i++;
}
printf("Serch error!\n");
return ERROR; //查詢失敗
}
程式執行結果如下:
3、修改某結點的資料域
要修改某結點的資料域,首先通過遍歷的方法找到該結點,然後直接修改該結點的資料域的值。程式碼如下:
LNode *Replace(LNode *L, int pos, int elem)
{
LNode *temp = L; //引入一箇中間變數,用於迴圈變數連結串列
temp = temp->next; //在遍歷之前,temp指向開始結點
for(int i=1;i<pos;i++)
{
temp = temp->next;
}
temp->data = elem; //找到要替換的結點並替換其資料域的值為elem
return (LNode*)L; //注意!!不能寫為 "return (LNode*)temp;"
}
程式執行結果如下:
4、往連結串列中插入結點
插入結點的位置有三種:
(1)插入到連結串列的首部,也就是頭結點和開始結點之間;
(2)插入到連結串列中間的某個位置;
(3)插入到連結串列的末端。
圖4 連結串列中插入結點5
雖然插入的位置有區別,都使用相同的插入手法。分為兩步,如圖4所示:
(1)將新結點的next指標指向插入位置的後一個結點;
(2)將插入位置的前一個結點的next指標指向插入結點。
程式碼如下:
LNode *Insert(LNode *L, int pos, int elem)
{
LNode *temp = L; //引入一箇中間變數,用於迴圈變數連結串列
int i = 0;
/* 首先找到插入結點的上一結點,即第pos-1個結點 */
while( (temp!=NULL)&&(i<pos-1) )
{
temp = temp->next;
++i;
}
/* 錯誤處理:連結串列為空或插入位置不存在 */
if( (temp==NULL)||(i>pos-1) )
{
printf("%s:Insert false!\n",__FUNCTION__);
return (LNode*)temp;
}
LNode *new = (LNode*)malloc(sizeof(LNode)); //建立新結點new
new->data = elem; //插入的新結點的資料域
new->next = temp->next; //新結點的next指標指向插入位置後的結點
temp->next = new; //插入位置前的結點的next指標指向新結點
return (LNode*)L; //注意!!不能寫為 "return (LNode*)temp;"
}
程式執行結果如下:
5、刪除連結串列結點
圖5 刪除結點
當需要從連結串列中刪除某結點時,需要進行兩部操作:
(1)將結點從連結串列中摘下來,即修改指標指向為:被刪除結點的前一個結點的next指標指向被刪除結點之後的結點。
(2)手動釋放掉被刪除結點所佔用的記憶體。
程式碼如下:
LNode *Delete(LNode *L, int pos, int *elem)
{
LNode *temp = L; //引入一箇中間變數,用於迴圈變數連結串列
int i = 0;
/* 首先找到刪除結點的上一結點,即第pos-1個結點 */
while( (temp!=NULL)&&(i<pos-1) )
{
temp = temp->next;
++i;
}
/* 錯誤處理:連結串列為空或刪除位置不存在 */
if( (temp==NULL)||(i>pos-1) )
{
printf("%s:Delete false!\n",__FUNCTION__);
return (LNode*)temp;
}
LNode *del = temp->next; //定義一個del指標指向被刪除結點
*elem = del->data; //儲存被刪除的結點的資料域
temp->next = del->next; /*刪除結點的上一個結點的指標域指向刪除結點的下一個結點,
也可寫為“temp->next = temp->next->next”*/
free(del); //手動釋放該結點,防止記憶體洩露
del = NULL; //防止出現野指標
return (LNode*)L; //注意!!不能寫為 "return (LNode*)temp;"
}
程式執行結果如下:
以下是完整的程式碼:
/*----------------------------------------------------------------------------------------
Program Explain:單鏈表的基本操作
Create Date:2018.2.13 by lzn
----------------------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#define ERROR 1
#define OK 0
/* 單鏈表結點定義 */
typedef struct LNode
{
int data; //data中存放結點資料域(預設是int型)
struct LNode *next; //指向後繼結點的指標
}LNode;
//操作函式的宣告
LNode *HeadCreateList(int len); //頭插法建立單鏈表
LNode *TailCreateList(int len); //尾插法建立單鏈表
int Serch(LNode *L, int elem); //在表中尋找元素的位置
LNode *Replace(LNode *L, int pos, int elem); //替換第pos個位置的元素成elem
LNode *Insert(LNode *L, int pos, int elem); //在表中插入新結點
LNode *Delete(LNode *L, int pos, int *elem); //刪除表中的結點
void PrintfList(LNode *L); //列印連結串列
int MenuSelect(void); //選單
//測試函式的宣告
void Test1(LNode *L); //測試"Serch"函式
void Test2(LNode *L); //測試"Replace"函式
void Test3(LNode *L); //測試"Insert"函式
void Test4(LNode *L); //測試"Delete"函式
LNode *Test5(void); //測試"TailCreateList"函式
LNode *Test6(void); //測試"HeadCreateList"函式
/*********************************************************************************
* Function Name : main主函式
* Parameter : NULL
* Return Value : 0
* Function Explain :
* Create Date : 2018.2.13 by lzn
**********************************************************************************/
int main(void)
{
int len = 0;
int cmd;
LNode *L;
/* 初始預設為尾插法建立單鏈表 */
printf("Please input list length:");
scanf("%d",&len);
L = TailCreateList(len);
PrintfList(L);
while(1)
{
cmd = MenuSelect();
switch(cmd)
{
case 1: Test1(L); break; //測試"Serch"函式
case 2: Test2(L); break; //測試"Replace"函式
case 3: Test3(L); break; //測試"Insert"函式
case 4: Test4(L); break; //測試"Delete"函式
case 5: L=Test5(); break; //測試"TailCreateList"函式
case 6: L=Test6(); break; //測試"HeadCreateList"函式
case 7: system("cls"); break; //清屏
case 8: exit(0); break; //退出
}
}
return 0;
}
/*********************************************************************************
* Function Name : HeadCreateList, 頭插法建立單鏈表(逆序)
* Parameter : len:連結串列長度
* Return Value : 建立好的連結串列
* Function Explain : 從一個空表開始,不斷讀入資料,生成新結點,將讀入資料存放到新
結點的資料域中,然後將新結點插入到當前連結串列的表頭結點之後。
* Create Date : 2018.2.13 by lzn
**********************************************************************************/
LNode *HeadCreateList(int len)
{
LNode *L = (LNode*)malloc(sizeof(LNode)); //建立一個頭結點
LNode *temp = L;//宣告一箇中間變數,指向頭結點,用於遍歷連結串列(曾因沒有這個中間變數而出錯)
temp->next = NULL; //該連結串列此刻只帶頭結點
for(int i=1;i<=len;i++) //迴圈申請len個結點來接收scanf得到的元素
{
LNode *p = (LNode*)malloc(sizeof(LNode)); //生成新結點
scanf("%d",&p->data); //用新申請的結點來接收scanf得到的元素
/* 以下兩條語句是頭插法的關鍵步驟,與本工程"Insert"函式原理一樣 */
p->next = temp->next; //新結點的next指標指向開始結點
temp->next = p; //頭結點的next指標指向新結點
}
return (LNode*)L;
}
/*********************************************************************************
* Function Name : TailCreateList, 尾插法建立單鏈表(順序)
* Parameter : len:連結串列長度
* Return Value : 建立好的連結串列
* Function Explain : 從一個空表開始,不斷讀入資料,生成新結點,將讀入資料存放到新
結點的資料域中,然後將新結點插入到當前連結串列的表尾結點之後。
* Create Date : 2018.2.13 by lzn
**********************************************************************************/
LNode *TailCreateList(int len)
{
LNode *L = (LNode*)malloc(sizeof(LNode)); //建立一個頭結點
LNode *temp = L;//宣告一箇中間變數,指向頭結點,用於遍歷連結串列(曾因沒有這個中間變數而出錯)
temp->next = NULL;//該連結串列此刻只帶頭結點
for(int i=1;i<=len;i++) //迴圈申請len個結點來接收scanf得到的元素
{
LNode *p = (LNode*)malloc(sizeof(LNode)); //生成新結點
scanf("%d",&p->data); //用新申請的結點來接收scanf得到的元素
/* 以下兩條語句是尾插法的關鍵步驟 */
temp->next = p; //用來接納新結點
temp = p; //指向終端結點,以便於接納下一個到來的結點,此語句也可以改為"L = L->next"
}
temp->next = NULL; //此刻所有元素已經全裝入連結串列L中,L的終端結點的指標域置為NULL
return (LNode*)L;
}
/*********************************************************************************
* Function Name : Serch, 查詢結點
* Parameter : L:連結串列 elem:所查詢的結點的資料域
* Return Value : pos:搜尋到的元素的位置 ERROR:elem不在順序表L中
* Function Explain :
* Create Date : 2018.2.13 by lzn
**********************************************************************************/
int Serch(LNode *L, int elem)
{
LNode *temp = L;
int pos = 0;
int i = 1;
while(temp->next)
{
temp = temp->next;
if(elem==temp->data)
{
pos = i;
printf("The %d position in the list is %d\n",elem,pos);
return pos; //返回elem元素在順序表中的位置
}
i++;
}
printf("Serch error!\n");
return ERROR; //查詢失敗
}
/*********************************************************************************
* Function Name : Replace, 替換第pos個位置的元素成elem
* Parameter : L:連結串列 pos:要替換的位置 elem:要替換的元素
* Return Value : 替換某結點之後生成的新連結串列
* Function Explain :
* Create Date : 2018.2.13 by lzn
**********************************************************************************/
LNode *Replace(LNode *L, int pos, int elem)
{
LNode *temp = L; //引入一箇中間變數,用於迴圈變數連結串列
temp = temp->next; //在遍歷之前,temp指向開始結點
for(int i=1;i<pos;i++)
{
temp = temp->next;
}
temp->data = elem; //找到要替換的結點並替換其資料域的值為elem
return (LNode*)L; //注意!!不能寫為 "return (LNode*)temp;"
}
/*********************************************************************************
* Function Name : Insert, 向連結串列中插入結點
* Parameter : L:連結串列 pos:要插入的位置 elem:要插入的結點的資料域
* Return Value : 插入新結點之後生成的新連結串列
* Function Explain :
* Create Date : 2018.2.13 by lzn
**********************************************************************************/
LNode *Insert(LNode *L, int pos, int elem)
{
LNode *temp = L; //引入一箇中間變數,用於迴圈變數連結串列
int i = 0;
/* 首先找到插入結點的上一結點,即第pos-1個結點 */
while( (temp!=NULL)&&(i<pos-1) )
{
temp = temp->next;
++i;
}
/* 錯誤處理:連結串列為空或插入位置不存在 */
if( (temp==NULL)||(i>pos-1) )
{
printf("%s:Insert false!\n",__FUNCTION__);
return (LNode*)temp;
}
LNode *new = (LNode*)malloc(sizeof(LNode)); //建立新結點new
new->data = elem; //插入的新結點的資料域
new->next = temp->next; //新結點的next指標指向插入位置後的結點
temp->next = new; //插入位置前的結點的next指標指向新結點
return (LNode*)L; //注意!!不能寫為 "return (LNode*)temp;"
}
/*********************************************************************************
* Function Name : Delete, 刪除連結串列中的結點
* Parameter : L:連結串列 pos:要刪除的位置 elem:被刪除的結點的資料域
* Return Value : 刪除結點之後生成的新連結串列
* Function Explain :
* Create Date : 2018.2.13 by lzn
**********************************************************************************/
LNode *Delete(LNode *L, int pos, int *elem)
{
LNode *temp = L; //引入一箇中間變數,用於迴圈變數連結串列
int i = 0;
/* 首先找到刪除結點的上一結點,即第pos-1個結點 */
while( (temp!=NULL)&&(i<pos-1) )
{
temp = temp->next;
++i;
}
/* 錯誤處理:連結串列為空或刪除位置不存在 */
if( (temp==NULL)||(i>pos-1) )
{
printf("%s:Delete false!\n",__FUNCTION__);
return (LNode*)temp;
}
LNode *del = temp->next; //定義一個del指標指向被刪除結點
*elem = del->data; //儲存被刪除的結點的資料域
temp->next = del->next; /*刪除結點的上一個結點的指標域指向刪除結點的下一個結點,
也可寫為“temp->next = temp->next->next”*/
free(del); //手動釋放該結點,防止記憶體洩露
del = NULL; //防止出現野指標
return (LNode*)L; //注意!!不能寫為 "return (LNode*)temp;"
}
/*********************************************************************************
* Function Name : PrintfList,列印連結串列
* Parameter : L:要列印的連結串列
* Return Value : NULL
* Function Explain :
* Create Date : 2018.2.13 by lzn
**********************************************************************************/
void PrintfList(LNode *L)
{
LNode *temp = L;
int count = 0; //計數器
printf("List:\n");
while(temp->next)
{
temp = temp->next;
printf("%d\t",temp->data);
count++;
if(count%5==0) //每5個元素作為一行
{
printf("\n");
}
}
printf("\n");
}
/*********************************************************************************
* Function Name : MenuSelect,選單
* Parameter : void
* Return Value : cmd
* Function Explain :
* Create Date : 2018.2.13 by lzn
**********************************************************************************/
int MenuSelect(void)
{
int cmd;
printf("1.Serch test\n");
printf("2.Replace test\n");
printf("3.Insert test\n");
printf("4.Delete test\n");
printf("5.TailCreateList test\n");
printf("6.HeadCreateList test\n");
printf("7.Clear\n");
printf("8.Exit\n");
do
{
printf("Enter your choice: ");
scanf("%d",&cmd);
}while(cmd<0||cmd>8);
return cmd;
}
/* ====================================以下是測試函式============================== */
/*********************************************************************************
* Function Name : Test1, 測試"Serch"函式
* Parameter : L:連結串列
* Return Value : void
* Function Explain :
* Create Date : 2018.2.13 by lzn
**********************************************************************************/
void Test1(LNode *L)
{
int serchElem = 0; //儲存要搜尋的元素
printf("--------------------%s start!--------------------\n",__FUNCTION__);
PrintfList(L);
printf("Please input the element you want to serch:");
scanf("%d",&serchElem);
Serch(L,serchElem);
printf("--------------------%s end!--------------------\n",__FUNCTION__);
printf("\n");
}
/*********************************************************************************
* Function Name : Test2, 測試"Replace"函式
* Parameter : L:連結串列
* Return Value : void
* Function Explain :
* Create Date : 2018.2.13 by lzn
**********************************************************************************/
void Test2(LNode *L)
{
int replacePos = 0, replaceElem = 0; //儲存替換的位置,替換的元素
printf("--------------------%s start!--------------------\n",__FUNCTION__);
PrintfList(L);
printf("Please input the position and the element you want replace(example:10,33):");
scanf("%d,%d",&replacePos,&replaceElem);
L = Replace(L,replacePos,replaceElem);
printf("After replace by position,list is:\n");
PrintfList(L);
printf("--------------------%s end!--------------------\n",__FUNCTION__);
printf("\n");
}
/*********************************************************************************
* Function Name : Test3, 測試"Insert"函式
* Parameter : L:連結串列
* Return Value : void
* Function Explain :
* Create Date : 2018.2.13 by lzn
**********************************************************************************/
void Test3(LNode *L)
{
int insertPos = 0, insertElem = 0; //儲存插入的位置,插入的元素
printf("--------------------%s start!--------------------\n",__FUNCTION__);
PrintfList(L);
printf("Please input the position and the element you want insert(example:10,33):");
scanf("%d,%d",&insertPos,&insertElem);
L = Insert(L,insertPos,insertElem);
printf("After insert,list is:\n");
PrintfList(L);
printf("--------------------%s end!--------------------\n",__FUNCTION__);
printf("\n");
}
/*********************************************************************************
* Function Name : Test4, 測試"Delete"函式
* Parameter : L:連結串列
* Return Value : void
* Function Explain :
* Create Date : 2018.2.13 by lzn
**********************************************************************************/
void Test4(LNode *L)
{
int deletePos = 0; //儲存要刪除的位置
int elem = NULL;
printf("--------------------%s start!--------------------\n",__FUNCTION__);
PrintfList(L);
printf("Please input the position of the element you want to delete(example:10):");
scanf("%d",&deletePos);
L = Delete(L,deletePos,&elem);
printf("Delete node data is:%d\n",elem);
printf("After delete,list is:\n");
PrintfList(L);
printf("--------------------%s end!--------------------\n",__FUNCTION__);
printf("\n");
}
/*********************************************************************************
* Function Name : Test5, 測試"TailCreateList"函式
* Parameter : void
* Return Value : 初始化成功的連結串列
* Function Explain :
* Create Date : 2018.2.13 by lzn
**********************************************************************************/
LNode *Test5(void)
{
LNode *L;
int len = 0;
printf("--------------------%s start!--------------------\n",__FUNCTION__);
printf("Please input list length:");
scanf("%d",&len);
L = TailCreateList(len);
PrintfList(L);
printf("--------------------%s end!--------------------\n",__FUNCTION__);
printf("\n");
return (LNode*)L;
}
/*********************************************************************************
* Function Name : Test6, 測試"HeadCreateList"函式
* Parameter : void
* Return Value : 初始化成功的連結串列
* Function Explain :
* Create Date : 2018.2.13 by lzn
**********************************************************************************/
LNode *Test6(void)
{
LNode *L;
int len = 0;
printf("--------------------%s start!--------------------\n",__FUNCTION__);
printf("Please input list length:");
scanf("%d",&len);
L = HeadCreateList(len);
PrintfList(L);
printf("--------------------%s end!--------------------\n",__FUNCTION__);
printf("\n");
return (LNode*)L;
}
三、參考資料
3、連結串列的操作
歡迎掃描左側二維碼關注我的微信公眾號(zhengnian-2018)。