1. 程式人生 > >學習筆記——單鏈表的基本操作(C語言實現)

學習筆記——單鏈表的基本操作(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)。