数据结构——单链表的增加、删除、查找、修改,详细解析

链表的概念:链表是一种物理存储结构上非连续非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

单链表是链表很重要的一种,如果理解了它,那么对于其他链表的理解,就会事半功倍。


目录

一、单链表的处理

1、节点的设置

2、节点(结构体)内存空间的开辟

二、单链表功能的实现

1、整体框架

2、主菜单

3、功能的实现

3.1 打印单链表数据功能的实现

 3.2 从单链表尾部插入节点功能的实现

 3.3 从单链表头部插入节点功能的实现

  3.4 从单链表头部删除节点功能的实现

 3.5 从单链表尾部删除节点功能的实现

 3.6 查找数据函数:给一个数据,返回这个数据所在节点的地址的函数

 3.7 随即插入节点:在返回的节点地址前插入一个节点的函数

 3.7 随即删除节点:给数据返回其所在节点地址,删除这个节点的函数

 3.8 修改数据函数:给一个数据,找到这个数据所在的节点,并用新数据修改

 三、总代码

         1、源函数代码test.c

2、头文件SList.h代码

3、函数功能实现源文件 SList.c

四、代码运行实例展示 


一、单链表的处理

1、节点的设置

因为单链表里面每个节点,都会存储一个数据一个指向下一个节点的指针,所以用结构体来表示节点。

typedef int SLdatetype;//重定义类型名,这样可以修改想要的类型
typedef struct SListNode SLNode;//重定义结构体类型的名字

struct SListNode //单链表节点
{
	SLdatetype date;
	SLNode* next;
};

2、节点(结构体)内存空间的开辟

因为每个节点都是结构体,为了不会导致内存空间的浪费,需要用一个节点,就开辟一个节点。所以用动态内存开辟函数malloc即可,每次开辟一个结构体大小的内存空间。但是要注意,每个节点创建的时候,其里面的指针都要先置为空指针,方便后面的修改。

SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
newnode->date = x;//将需要的数据x放入节点里
newnode->next = NULL;//创建一个新的节点

二、单链表功能的实现

1、整体框架

int main()
{
	menu();
	int input = 0;
	do
	{
	fprintf(stdout, "请输入:>");
	fscanf(stdin,"%d", &input);
		switch (input)
		{
		case 1:
			testSList1();//进入尾插数据操作
			fprintf(stdout, "尾插数据成功\n");
			break;
		case 2:
			testSList2();//进入头插数据操作
			fprintf(stdout, "头插数据成功\n");
			break;
		case 3:
			testSList3();//进入头删数据操作
			fprintf(stdout, "头删数据成功\n");
			break;
		case 4:
			testSList4();//进入尾删数据操作
			fprintf(stdout, "尾删数据成功\n");
			break;
		case 5:
			testSList5();//进入随机插入数据操作
			fprintf(stdout, "随机插入数据成功\n");
			break;
			//testSList6();
		case 6:
			testSList7();//进入随机删除数据操作
			fprintf(stdout, "随机删除数据成功\n");
			break;
		case 7:
			testSList8();//进入修改数据操作
			break;
		case 0:
			printf("退出\n");
			break;
		default:
			fprintf(stdout, "选择错误,请重新选择\n");
		}
	} while (input);
}

do......while循环语句swith分支语句来进入相应的功能。 


2、主菜单

void menu()
{
	printf("*************************************\n");
	printf("*****    1、尾插     2、头插    *****\n");
	printf("*****    3、头删     4、尾删    *****\n");
	printf("*****    5、随机插   6、随机删  *****\n");
	printf("*****    7、修改     0、退出    *****\n");
	printf("*************************************\n");
}

3、功能的实现

3.1 打印单链表数据功能的实现

//打印单链表
void SListPrint(SLNode* phead)//实参是单链表的第一个节点的地址
{
	while (phead != NULL)
	{
		printf("%d->", phead->date);
		phead = phead->next;
	}
	printf("NULL\n");
}

形参phead接受的是单链表第一个节点的地址(即plist),通过每次循环,打印对应节点的数据 


 3.2 从单链表尾部插入节点功能的实现

//尾插函数
void SListPushBack(SLNode** pphead, SLdatetype x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	newnode->date = x;
	newnode->next = NULL;//创建一个新的节点

	if (*pphead == NULL)//当没有节点的时候,这时候开辟的内存就给plist
	{
		*pphead = newnode;
	}
	else
	{
		SLNode* cru = *pphead;
		while (cru->next != NULL)//这里必须改变节点里面的指针next存放的地址
		{
			cru = cru->next;
		}
		cru->next = newnode;//所以这里必须用cru->next访问这个指针才可以改变next存放的地址
	}
}

 3.3 从单链表头部插入节点功能的实现

//头插函数
void SListPushFront(SLNode** pphead, SLdatetype x)
{
	SLNode* newcode = (SLNode*)malloc(sizeof(SLNode));
	newcode->date = x;
	newcode->next = NULL;

	//if (*pphead == NULL)//当单链表里没有节点的时候
	//{
	//	*pphead = newcode;
	//}
	//else
	//{
		newcode->next = *pphead;//这里已经包括了单链表没有节点的时候,所以可以不用上面if
		*pphead = newcode;
	//}
}

从单链表头部和尾部插入节点函数都需要先创建一个新的节点


  3.4 从单链表头部删除节点功能的实现

//头删函数 因为节点是动态开辟的,所以直接用free函数就能删除,
//一般来说释放空间后,指向这块空间的指针要置为空指针
void SListPopFront(SLNode** pphead)
{
	if (*pphead != NULL)//如果空指针,即没有节点,那么就不需要删除
	{
		SLNode* next0 = (*pphead)->next;//这里也满足只有一个节点的情况
		free(*pphead);
		*pphead = next0;
	}
}

 3.5 从单链表尾部删除节点功能的实现

//尾删函数 因为同样节点是动态开辟的,所以直接用free函数释放空间
void SListPopBack(SLNode** pphead)
{
	if (*pphead)//如果单链表没有节点,即plist是空指针,就不进入尾删
	{
		SLNode* cru = *pphead;
		SLNode* prev = NULL;
		if((*pphead)->next==NULL)
		{
			free(*pphead);
			*pphead = NULL;
		}
		while (cru->next != NULL)//新定义一个指针,得到尾删节点的前一个节点的地址
		{
			prev = cru;
			cru = cru->next;
		}
		prev->next = NULL;
		free(cru);
		cru = NULL;
	}
}

 3.6 查找数据函数:给一个数据,返回这个数据所在节点的地址的函数

//给一个数据,返回这个数据所在节点的地址的函数
SLNode* SListFindDate(SLNode* phead, SLdatetype x)
//因为不需要改变指向第一个节点的指针的值,
//即不需要改变这个指针存放的地址
{
	while (phead != NULL)//如果没有节点,根本不会进入循环去找
	{
		if (phead->date == x)
		{
			return phead;
		}
		else
		{
			phead = phead->next;
		}
	}
	return NULL;
}

 3.7 随即插入节点:在返回的节点地址前插入一个节点的函数

//在返回的节点地址pos前插入一个数据的函数
void SListPushpos(SLNode** pphead,SLdatetype x,SLdatetype y)
{
    SLNode* prev = NULL;
	SLNode* cru = *pphead;
	SLNode* pos = SListFindDate(*pphead, x);
	
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	newnode->date = y;
	newnode->next = NULL;

	if (pos != NULL)//如果是没找到这个数据,或者没有节点,就不随机插
	{
		while (cru != pos)
		{
			prev = cru;
			cru = cru->next;
		}
		if (prev != NULL)
		{
			newnode->next = pos;
			prev->next = newnode;
		}
		else
		{
			newnode->next = pos;
			*pphead = newnode;
		}
	}
}

随机插入一个节点函数:是先通过查找函数返回提供的数据其所在节点的地址,这里用指针pos接收,再将这个字节的前面插入一个新的节点;想在哪个节点前插入,就通过查找函数返回那个节点的地址。


 3.7 随即删除节点:给数据返回其所在节点地址,删除这个节点的函数

//给数据返回其所在节点地址,删除这个节点的函数
void SListPopdate(SLNode** pphead, SLdatetype x)
{
	SLNode* cru = *pphead;
	SLNode* prev = NULL;
	SLNode* pos = SListFindDate(*pphead, x);

	if (pos != NULL)//如果是没找到这个数据,或者没有节点,就不随机删
	{
		while (cru != pos)
		{
			prev = cru;
			cru = cru->next;
		}
		if (prev != NULL)
		{
			prev->next = pos->next;
			free(pos);
			pos = NULL;
		}
		else
		{
			*pphead = pos->next;
			free(cru);
			cru = NULL;
		}
	}
}

 随机删除一个节点函数:同样,是先通过查找函数,返回一个节点的地址,这里用指针pos接收,再将这块空间释放掉,因为是动态开辟的,所以就删除该节点了;想删除哪个节点,就通过查找函数返回那个节点的地址。


 3.8 修改数据函数:给一个数据,找到这个数据所在的节点,并用新数据修改

//给一个数据,找到这个数据所在的节点,并用新数据修改
void SListChangeDate(SLNode* phead, SLdatetype x,SLdatetype y)
//因为不需要改变节点的地址,所以值传递即可 
//x是查找的数据,y是新数据,用来修改查找的数据                                              
{
	SLNode* cru = phead;
	while (cru != NULL)//如果没有节点,根本不会进入循环去找
	{
		if (cru->date == x)
		{
			cru->date = y;
			break;//修改完数据后,就跳出循环
		}
		else
		{
			cru = cru->next;
		}
	}

	if (cru == NULL)//如果循环完单链表,没有找到要修改的那个数据
	{
		fprintf(stdout, "要修改的数据不存在,请重新修改数据\n");
	}
	else
	{
		fprintf(stdout, "修改成功\n");
	}
}

 修改数据函数:同样,是先通过查找函数,返回一个节点的地址,这里用指针pos接收,再用新的数据y修改之前的数据x;想修改哪个节点里的数据,就通过查找函数返回那个节点的地址。


 三、总代码

1、放置主框架和菜单等代码的源函数test.c

2、防止函数声明和结构体声明等代码的头文件SList.h

3、实现各种功能函数设计的代码的源文件SList.c

1、源函数代码test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"SList.h"

//主菜单
void menu()
{
	printf("*************************************\n");
	printf("*****    1、尾插     2、头插    *****\n");
	printf("*****    3、头删     4、尾删    *****\n");
	printf("*****    5、随机插   6、随机删  *****\n");
	printf("*****    7、修改     0、退出    *****\n");
	printf("*************************************\n");
}

//验证尾插函数
void testSList1()
{
	SLNode* plist = NULL;//指针一定要初始化,不然会变成野指针
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPrint(plist);
}

//验证头插函数
void testSList2()
{
	SLNode* plist = NULL;
	SListPushFront(&plist, 5);
	SListPushFront(&plist, 6);
	SListPushFront(&plist, 7);
	SListPushFront(&plist, 8);
	SListPrint(plist);
}

//验证头删函数
void testSList3()
{
	SLNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPopFront(&plist);
	SListPopFront(&plist);
	SListPrint(plist);
}

//验证尾删函数
void testSList4()
{
	SLNode* plist = NULL;
	SListPushFront(&plist, 5);
	SListPushFront(&plist, 6);
	SListPushFront(&plist, 7);
	SListPushFront(&plist, 8);
	SListPopFront(&plist);
	SListPopFront(&plist);
	SListPrint(plist);

}

//验证给数据返回其所在节点地址的函数,和删除数据节点函数或插入数据节点之前的函数配合使用
void testSList5()
{
	SLNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListFindDate(plist,2);
}

//验证在返回的节点地址pos前插入一个数据的函数
void testSList6()
{
	SLNode* plist = NULL;
	SListPushFront(&plist, 5);
	SListPushFront(&plist, 6);
	SListPushFront(&plist, 7);
	SListPushFront(&plist, 8);
	SListPushpos(&plist, 8, 1);
	SListPrint(plist);
}

//验证给数据返回其所在节点地址,删除这个节点的函数
void testSList7()
{
	SLNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPopdate(&plist, 1);
	SListPopdate(&plist, 4);
	SListPrint(plist);
}

//验证给一个数据,找到这个数据所在的节点,并用新数据修改的函数
void testSList8()
{
	SLNode* plist = NULL;
	SListPushFront(&plist, 5);
	SListPushFront(&plist, 6);
	SListPushFront(&plist, 7);
	SListPushFront(&plist, 8);
	SListChangeDate(plist, 6, 9);
	SListPrint(plist);
}

int main()
{
	menu();
	int input = 0;
	do
	{
	fprintf(stdout, "请输入:>");
	fscanf(stdin,"%d", &input);
		switch (input)
		{
		case 1:
			testSList1();//进入尾插数据操作
			fprintf(stdout, "尾插数据成功\n");
			break;
		case 2:
			testSList2();//进入头插数据操作
			fprintf(stdout, "头插数据成功\n");
			break;
		case 3:
			testSList3();//进入头删数据操作
			fprintf(stdout, "头删数据成功\n");
			break;
		case 4:
			testSList4();//进入尾删数据操作
			fprintf(stdout, "尾删数据成功\n");
			break;
		case 5:
			testSList6();//进入随机插入数据操作
			fprintf(stdout, "随机插入数据成功\n");
			break;
		case 6:
			testSList7();//进入随机删除数据操作
			fprintf(stdout, "随机删除数据成功\n");
			break;
		case 7:
			testSList8();//进入修改数据操作
			break;
		case 0:
			printf("退出\n");
			break;
		default:
			fprintf(stdout, "选择错误,请重新选择\n");
		}
	} while (input);
}

2、头文件SList.h代码

#include<stdio.h>
#include<stdlib.h>

typedef int SLdatetype;//重定义类型名,这样可以修改想要的类型
typedef struct SListNode SLNode;//重定义结构体类型的名字

struct SListNode //单链表节点
{
	SLdatetype date;
	SLNode* next;
};

void SListPrint(SLNode* phead);//打印单链表

void SListPushBack(SLNode** pphead, SLdatetype x);//尾插函数

void SListPushFront(SLNode** pphead, SLdatetype x);//头插函数

void SListPopFront(SLNode** pphead);//头删函数

void SListPopBack(SLNode** pphead);//尾删函数

SLNode* SListFindDate(SLNode* phead, SLdatetype x);//验证给数据返回其所在节点地址的函数

void SListPushpos(SLNode** pphead, SLdatetype x, SLdatetype y);//在返回的节点地址pos前插入一个数据的函数,x是找的数据,y是插入的数据

void SListPopdate(SLNode** pphead, SLdatetype x);//给数据返回其所在节点地址,删除这个节点的函数

void SListChangeDate(SLNode* phead, SLdatetype x,SLdatetype y);//因为不需要改变节点的地址,所以值传递即可
                                                                //x是查找的数据,y是新数据,用来修改查找的数据

3、函数功能实现源文件 SList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"


//打印单链表
void SListPrint(SLNode* phead)//实参是单链表的第一个节点的地址
{
	while (phead != NULL)
	{
		printf("%d->", phead->date);
		phead = phead->next;
	}
	printf("NULL\n");
}

//尾插函数
void SListPushBack(SLNode** pphead, SLdatetype x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	newnode->date = x;
	newnode->next = NULL;//创建一个新的节点

	if (*pphead == NULL)//当没有节点的时候,这时候开辟的内存就给plist
	{
		*pphead = newnode;
	}
	else
	{
		SLNode* cru = *pphead;
		while (cru->next != NULL)//这里必须改变节点里面的指针next存放的地址
		{
			cru = cru->next;
		}
		cru->next = newnode;//所以这里必须用cru->next访问这个指针才可以改变next存放的地址
	}
}


//头插函数
void SListPushFront(SLNode** pphead, SLdatetype x)
{
	SLNode* newcode = (SLNode*)malloc(sizeof(SLNode));
	newcode->date = x;
	newcode->next = NULL;

	//if (*pphead == NULL)//当单链表里没有节点的时候
	//{
	//	*pphead = newcode;
	//}
	//else
	//{
		newcode->next = *pphead;//这里已经包括了单链表没有节点的时候,所以可以不用上面if
		*pphead = newcode;
	//}
}


//头删函数 因为节点是动态开辟的,所以直接用free函数就能删除,
//一般来说释放空间后,指向这块空间的指针要置为空指针
void SListPopFront(SLNode** pphead)
{
	if (*pphead != NULL)//如果空指针,即没有节点,那么就不需要删除
	{
		SLNode* next0 = (*pphead)->next;//这里也满足只有一个节点的情况
		free(*pphead);
		*pphead = next0;
	}
}



//尾删函数 因为同样节点是动态开辟的,所以直接用free函数释放空间
void SListPopBack(SLNode** pphead)
{
	if (*pphead)//如果单链表没有节点,即plist是空指针,就不进入尾删
	{
		SLNode* cru = *pphead;
		SLNode* prev = NULL;
		if((*pphead)->next==NULL)
		{
			free(*pphead);
			*pphead = NULL;
		}
		while (cru->next != NULL)//新定义一个指针,得到尾删节点的前一个节点的地址
		{
			prev = cru;
			cru = cru->next;
		}
		prev->next = NULL;
		free(cru);
		cru = NULL;
	}
}


//给一个数据,返回这个数据所在节点的地址的函数
SLNode* SListFindDate(SLNode* phead, SLdatetype x)
//因为不需要改变指向第一个节点的指针的值,
//即不需要改变这个指针存放的地址
{
	while (phead != NULL)//如果没有节点,根本不会进入循环去找
	{
		if (phead->date == x)
		{
			return phead;
		}
		else
		{
			phead = phead->next;
		}
	}
	return NULL;
}


//在返回的节点地址pos前插入一个数据的函数
void SListPushpos(SLNode** pphead,SLdatetype x,SLdatetype y)
{
    SLNode* prev = NULL;
	SLNode* cru = *pphead;
	SLNode* pos = SListFindDate(*pphead, x);
	
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	newnode->date = y;
	newnode->next = NULL;

	if (pos != NULL)//如果是没找到这个数据,或者没有节点,就不随机插
	{
		while (cru != pos)
		{
			prev = cru;
			cru = cru->next;
		}
		if (prev != NULL)
		{
			newnode->next = pos;
			prev->next = newnode;
		}
		else
		{
			newnode->next = pos;
			*pphead = newnode;
		}
	}
}


//给数据返回其所在节点地址,删除这个节点的函数
void SListPopdate(SLNode** pphead, SLdatetype x)
{
	SLNode* cru = *pphead;
	SLNode* prev = NULL;
	SLNode* pos = SListFindDate(*pphead, x);

	if (pos != NULL)//如果是没找到这个数据,或者没有节点,就不随机删
	{
		while (cru != pos)
		{
			prev = cru;
			cru = cru->next;
		}
		if (prev != NULL)
		{
			prev->next = pos->next;
			free(pos);
			pos = NULL;
		}
		else
		{
			*pphead = pos->next;
			free(cru);
			cru = NULL;
		}
	}
}


//给一个数据,找到这个数据所在的节点,并用新数据修改
void SListChangeDate(SLNode* phead, SLdatetype x,SLdatetype y)
//因为不需要改变节点的地址,所以值传递即可 
//x是查找的数据,y是新数据,用来修改查找的数据                                              
{
	SLNode* cru = phead;
	while (cru != NULL)//如果没有节点,根本不会进入循环去找
	{
		if (cru->date == x)
		{
			cru->date = y;
			break;//修改完数据后,就跳出循环
		}
		else
		{
			cru = cru->next;
		}
	}

	if (cru == NULL)//如果循环完单链表,没有找到要修改的那个数据
	{
		fprintf(stdout, "要修改的数据不存在,请重新修改数据\n");
	}
	else
	{
		fprintf(stdout, "修改成功\n");
	}
}

四、代码运行实例展示 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值