線性表(二) 單鏈表的增刪改查及源碼 (修改)


<!--author--Kang-->

<!--time--2020/8/22-->

線性表的鏈式存儲結構

鏈表

單鏈表的特點:

順序表中,只要單單存放數據元素,而鏈表中,不僅要存放數據元素,還要存放下一個數據元素在內存中的位置。因此,存放數據元素的域稱為數據域,存放后繼位置的域為指針域,兩者組成一個模塊,稱為結點,鏈表就是由n個節點組成的。鏈表具有以下幾個特點:

1)將數據封裝成結點,由結點組成的線性表;

2)通過結點的指針域將結點連接到一起;

3)結點的內存空間不一定時連續的,但邏輯上是連續的

4)有一個頭指針指向第一個數據元素;

5)鏈表是線性表的一種,是線性表的鏈式存儲結構的實現。

其中,鏈表中每個結點只有一個指針域指向后繼結點,這種鏈表稱為單鏈表線性鏈表

單鏈表的結構:

 

頭結點:為了處理方便,會在鏈表的第一個元素前增加一個頭結點,該結點的數據域中不存儲線性表的數據元素,其作用是為了對鏈表進行操作時,可以對空表、非空表的情況以及對首元結點進行統一處理。

尾結點:最后一個結點的指針域為空指針

首元結點:鏈表中存儲第一個數據元素的結點。如上圖中data1結點。

 

頭指針:通常用head名稱定義,指向鏈表中第一個結點的指針,有頭結點時指向頭結點,若鏈表設計時不設頭結點,則指向首元結點。

單鏈表的定義

通過單鏈表的頭指針可以完成單鏈表的所有操作,而單鏈表的結點有兩個必須的屬性:

1)數據元素信息:節點中的數據元素基本信息;

2)指針域:結點之間通過指針進行邏輯關聯。用來存儲下一個結點的地址。

由此可定義出線性表的鏈式存儲的結構體類型:

typedef int ElementType;//鏈表中元素的類型,根據實際情況而定,此處以int為例

//單鏈表結構體定義
typedef struct link_node{
   ElementType data;//結點的數據域
   stuct link_node *next;//結點的指針域:(此時結構體尚未定義完整,不能用 NODE *next; )用該結構體類型定義一個指針,用來指向下一個結點的地址
}LINK_NODE,*LINK_LIST;

注意這里的定義,按照前面的分析,實際上只需要有LINK_NODE類型定義就行了,我們可以通過定義LINK_NODE *head 來定義一個頭指針,但為了提高程序的可讀性,在用typedef定義單鏈表時起了兩個別名,分別為LINK_NODE和LINK_LIST,而實際上LINK_LIST就是struct link_node * ,因此在定義單鏈表時可以定義為:

LINK_LIST list;

或者

LINK_NODE *list

當然,這里的list其實就是頭指針head;

單鏈表的初始化操作:

該步驟主要是為了完成頭結點的創建以及相關指針的置空(下一結點)

1、先通過malloc給頭結點分配空間

2、判斷內存是否分配失敗,失敗返回0

3、頭結點的next指針置空:(*L)->next = NULL;

4、操作結束,返回1

int init_list(LINK_LIST *L){//在函數內部去修改函數外部數據時要用指針
*L = (LINK_NODE *)malloc(sizeof(LINK_NODE));//分配頭結點空間
if (*L ==NULL)//如果內存分配失敗
{
return 0;
}
(*L)->next = NULL;//頭結點的next置為空
return 1;
}

獲取單鏈表的長度:

計算單鏈表中有效數據結點的個數(不包含頭結點)

1、定義變量length,用來統計有效結點個數;

2、定義tmp指針指向首元結點,首元結點為頭結點的下一結點:LINK_NODE *tmp = L->next;

3、通過while循環計算有效結點個數,循環條件:tmp指針為非空結點:tmp!=NULL

4、循環體內為length自增,tmp指針指向下一結點

5、返回長度length

int get_length(LINK_LIST L) {//這里不需要修改鏈表,直接用形參操作
int length = 0;
LINK_NODE *tmp = L->next;//從首元結點開始,而非頭結點
while (tmp!=NULL)
{
length++;
tmp = tmp->next;//指針指向下一個結點
}
return length;
}

單鏈表的遍歷操作:

1、先定義一個指針指向首元結點

2、通過while循環實現遍歷,循環條件為 tmp!=NULL,即鏈表為非空或tmp指針沒有超出鏈表范圍

3、打印數據域中tmp->data;

4、將tmp指針指向下一結點,實現循環,當tmp指向尾結點時,即tmp->next==NULL,在下一個循環開始時跳出循環

void show(LINK_LIST L) {
LINK_NODE *tmp = L->next;//從首元結點開始,而非頭結點
while (tmp != NULL)
{
printf("%d", tmp->data);//注意輸出數據類型,這里以int為例
if (tmp->next!=NULL)
{
printf(",");
}
else {
printf("\n");//對最后一個結點數據輸出后不加逗號處理
}
tmp = tmp->next;//將指針指向下一個結點
}
}

單鏈表插入操作

1、定義一個p_insert指針指向頭結點,用來存儲待插入結點的前一個結點

2、判斷插入位置 i 是否合理:即1< i < get_length(L) +1 (這里長度+1是將新結點插在末尾)

3、通過for循環移動p_insert指針,將p_insert指針指向插入位置 i 的前一個結點

4、創建新結點p_new,並給新結點分配空間。這里可以添加一個判斷,如果空間分配失敗,則返回0

5、給新結點的數據域賦值:p_new->data = e;

6、將第 i -1個結點所在地址(即 p_insert->next)賦值給新結點的next指針:p_new->next = p_ins->next;

7、將新結點的地址賦值給前一結點的next指針:p_insert->next = p_new;

//單鏈表的插入操作
int insert(LINK_LIST L,int i,ElementType e) {
LINK_NODE *p_ins = L;//先定義一個指針指向頭結點
//
if (i<1||i>get_length(L)+1)
{

return 0;
}
for (int j = 0; j < i-1; j++)//循環中可能會使得指針指向空,則意味着插入位置將會超過鏈表長度+1,使得操作失敗,
{
p_ins = p_ins->next;//將p_ins指向要插入結點的前一個結點
}
LINK_NODE *p_new = (LINK_NODE *)malloc(sizeof(LINK_NODE));
if (p_new ==NULL)//分配空間失敗,返回0
{
return 0;
}
p_new->data = e;//給數據域賦值
p_new->next = p_ins->next;//將新結點的next指向插入位置的原結點
p_ins->next = p_new;//將新結點鏈接到插入位置上

return 1;
}

單鏈表的刪除操作:

1、判斷刪除位置是否合理,不合理則返回0 :1<i<get_length(L)

2、定義一個指針指向頭結點,用來存儲刪除位置的前一個結點的地址:LINK_NODE *p_prev = L;

3、定義待刪除結點的指針:LINK_NODE *p_del;

4、移動p_prev 指針,將指針指向刪除節點 i 的前一個結點所在位置

5、通過待刪除結點的前一結點 p_prev 給待刪除結點 p_del 賦值地址

6、跳過刪除結點,將待刪除結點的下一結點指針地址p_del->next賦值給前一個結點的下一結點指針p_prev->next

7、釋放待刪除結點的空間:free(p_del),返回1,表示操作完成

//單鏈表的刪除操作
int delt(LINK_LIST L,int i) {
if (i<1||i>get_length(L))
{
printf("操作失敗,刪除位置不存在!");
return 0;//刪除失敗
}
LINK_NODE *p_prev = L;//previous 定義 前一個結點
LINK_NODE *p_del;//定義待刪除結點指針
for (int j = 0; j < i-1; j++)//移動i-1次,將prev指針指向要刪除結點 i 的前一個結點;
{
p_prev = p_prev->next;
}
p_del = p_prev->next;//通過待刪除結點的前一結點給待刪除結點賦值地址
p_prev->next = p_del->next;//跳過刪除結點,將待刪除結點的指針域賦值給前一結點的指針域
free(p_del);//釋放待刪除結點的空間

return 1;
}

單鏈表的修改操作

兩種方法,方法一:先創建一個新的結點,再將新結點覆蓋到需要需要修改的結點上,最后刪除舊結點;方法二:先找到要修改的結點,再直接修改該結點
int updata(LINK_LIST L,int i,ElementType e) {
	if (i<1||i>get_length(L))//判斷是否空表或位置是否合理
	{
		printf("操作失敗,該位置不存在!");
		return 0;
	}
	LINK_NODE *p_mod = L;//modify 定義指針指向頭結點。也可以指向首元結點,但要注意下面循環次數會發生變化
	for (int j = 0; j < i;j++) {//因為頭指針指向頭結點,所以移動i次。若指向首元結點,則移動i-1次,條件改為j<i-1
		p_mod = p_mod->next;//將指針指向需要修改的位置
	}
	p_mod->data = e;//直接修改目標結點的數據域
	return 1;
}

單鏈表的查找操作

1、創建p_find指針指向起始節點

2、通過循環變臉查找,循環條件為p_find!=NULL;

3、判斷,如果當前指針指向結點的數據域與查找目標一致,返回當前結點的地址

4、p_find指針向后移一位,保證循環

5、跳出循環,返回NULL,說明未查找到數據

LINK_NODE *search(LINK_NODE *p_from,ElementType e) {//返回一個結點的指針
	LINK_NODE *p_find = p_from;//指向起始結點
	
	while (p_find!=NULL)//當前指向結點為有效結點
	{
		if(p_find->data==e){//當前指針指向結點的數據域與查找目標一致
	
			//printf("已查找到目標數據!該數據地址為:0x%p\n",p_find);
			return p_find;
		}
		p_find = p_find->next;//指針后移
	}
	printf("未找到該數據!\n");
	return NULL;
}

至此,單鏈表的增刪改查結束。下面為調試的源碼:

源碼:

#include <stdio.h>
#include <malloc.h>
typedef int ElementType;//鏈表中元素的類型,根據實際情況而定,這里以int為例
						
//單鏈表結構體定義:
typedef struct link_node {
	ElementType data;//結點的數據域
	struct link_node* next;//結點的指針域
}LINK_NODE,*LINK_LIST;//這里LINK_LIST和 next 類型一致,都是struct link_node*類型
/*(LINK_LIST本身就是指針,因此定義頭指針時可以直接這樣  LINK_LIST head;)
也可以這樣定義:LINK_NODE * head;
*/

//單鏈表的初始化操作:
	//主要時為了完成頭結點的創建以及相關指針的置空
int init_list(LINK_LIST *L){//在函數內部去修改函數外部數據時要用指針
	*L = (LINK_NODE *)malloc(sizeof(LINK_NODE));//分配頭結點空間
	if (*L ==NULL)//如果內存分配失敗
	{
		return 0;
	}
	(*L)->next = NULL;//頭結點的next置為空
	return 1;
}

//獲取單鏈表長度:計算單鏈表中有效數據結點個數(不包含頭結點)
int get_length(LINK_LIST L) {//這里不需要修改鏈表,直接用形參操作
	int length = 0;
	LINK_NODE *tmp = L->next;//從首元結點開始,而非頭結點
	while (tmp!=NULL)
	{
		length++;
		tmp = tmp->next;//指針指向下一個結點
	}
	return length;
}


//單鏈表的遍歷操作
void show(LINK_LIST L) {
	LINK_NODE *tmp = L->next;//從首元結點開始,而非頭結點
	while (tmp != NULL)
	{
		printf("%d", tmp->data);//注意輸出數據類型,這里以int為例
		if (tmp->next!=NULL)
		{
			printf(",");
		}
		else {
			printf("\n");//對最后一個結點數據輸出后不加逗號處理
		}
		tmp = tmp->next;//將指針指向下一個結點
	}
}


//單鏈表的插入操作
int insert(LINK_LIST L,int i,ElementType e) {
	LINK_NODE *p_ins = L;//先定義一個指針指向頭結點
	//
	if (i<1||i>get_length(L)+1)
	{

		return 0;
	}
	for (int j = 0; j < i-1; j++)//循環中可能會使得指針指向空,則意味着插入位置將會超過鏈表長度+1,使得操作失敗,
	{
		p_ins = p_ins->next;//將p_ins指向要插入結點的前一個結點
	}
	LINK_NODE *p_new = (LINK_NODE *)malloc(sizeof(LINK_NODE));
	if (p_new ==NULL)//分配空間失敗,返回0
	{
		return 0;
	}
	p_new->data = e;//給數據域賦值
	p_new->next = p_ins->next;//將新結點的next指向插入位置的原結點
	p_ins->next = p_new;//將新結點鏈接到插入位置上
	
	return 1;
}

//單鏈表的刪除操作
int delt(LINK_LIST L,int i) {
	if (i<1||i>get_length(L))
	{
		printf("操作失敗,刪除位置不存在!");
		return 0;//刪除失敗
	}
	LINK_NODE *p_prev = L;//previous 定義 前一個結點
	LINK_NODE *p_del;//定義待刪除結點指針
	for (int j = 0; j < i-1; j++)//移動i-1次,將prev指針指向要刪除結點 i 的前一個結點;
	{
		p_prev = p_prev->next;
	}
	p_del = p_prev->next;//通過待刪除結點的前一結點給待刪除結點賦值地址
	p_prev->next = p_del->next;//跳過刪除結點,將待刪除結點的指針域賦值給前一結點的指針域
	free(p_del);//釋放待刪除結點的空間

	return 1;
}

//單鏈表的修改操作
/*一:先刪除,再插入	
		1、先新建一個新結點
		2、給新結點賦值
		3、刪除要修改的結點
		4、將新結點插入到刪除結點的位置上

二:	
		1、找到要修改的結點
		2、直接修改該結點
*/


int updata(LINK_LIST L,int i,ElementType e) {
	if (i<1||i>get_length(L))//判斷是否空表或位置是否合理
	{
		printf("操作失敗,該位置不存在!");
		return 0;
	}
	LINK_NODE *p_mod = L;//modify 定義指針指向頭結點。也可以指向首元結點,但要注意下面循環次數會發生變化
	for (int j = 0; j < i;j++) {//因為頭指針指向頭結點,所以移動i次。若指向首元結點,則移動i-1次,條件改為j<i-1
		p_mod = p_mod->next;//將指針指向需要修改的位置
	}
	p_mod->data = e;//直接修改目標結點的數據域
	return 1;
}

//單鏈表的查找操作

LINK_NODE *search(LINK_NODE *p_from,ElementType e) {//返回一個結點的指針
	LINK_NODE *p_find = p_from;//指向起始結點
	
	while (p_find!=NULL)//當前指向結點為有效結點
	{
		if(p_find->data==e){//當前指針指向結點的數據域與查找目標一致
	
			//printf("已查找到目標數據!該數據地址為:0x%p\n",p_find);
			return p_find;
		}
		p_find = p_find->next;//指針后移
	}
	printf("未找到該數據!\n");
	return NULL;
}



int main() {
	LINK_LIST list = NULL;//list為指針
	//創建單鏈表
	init_list(&list);

	//調用插入函數
	//int rlt = insert(list,2,1);//rlt:0 第一個位置沒有元素,不能插入到第二個位置,
	insert(list,1,1);
	insert(list, 2, 2);
	insert(list, 3, 3);
	insert(list, 1, 4);
	insert(list, 3, 5);
	insert(list, 2, 99);
	insert(list, 3, 99);
	show(list);

	//調用刪除函數
	int rlt1 = delt(list,6);
	printf("rlt = %d\n",rlt1);
	show(list);

	int rlt2 = delt(list, 4);
	printf("rlt2 = %d\n", rlt2);
	show(list);

	//調用修改函數

	int rlt3 = updata(list,0,100);
	printf("rlt3 = %d\n",rlt3);

	int rlt4 = updata(list, 5, 100);
	printf("rlt4 = %d\n", rlt4);

	updata(list,1,99);
	show(list);
	updata(list, 2, 100);
	show(list);

	//調用查詢函數
	LINK_NODE *p_rlt1 = search(list, 88);
	LINK_NODE *p_rlt2 = search(list,100);

	LINK_NODE *p_rlt3 = list;
	int index = 0;
	while (1)//循環查找所有重復數據
	{
		p_rlt3 = search(p_rlt3->next,99);//從上一個找到的結點開始查詢
		if (p_rlt3==NULL)
		{
			break;
		}
		index++;
		printf("已查找到第%d個數據的地址為0x%p\n",index,p_rlt3);
	}
	index = 0;

	return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM