C语言单向链表(完整工程。增删查)

2020-7-26记
在写双向链表的时候考虑了更多的边界条件,再回来看单向链表的时候发现删除函数有一些边界条件没有考虑,这里修改一下

 

单向链表一般都是会有一个头结点,尾结点的指针域是指向NULL的。链表的结构一般如下:

typedef struct list
{
	struct list *next;
	int data;
	
}st_List;

事实上这个是最简单的链表结构,一般来说数据部分还会有很多其他的类型,但是这里只是讲最简单的结构,只要能把链表连接起来成为一条链子,数据部分就会比较容易处理的。

看一下单链表的结构

首先是头结点,头结点一般是不包含数据的,之后就是包含数据的节点,最后一个节点的指针域next是会指向NULL的,有些博客上写头结点包含数据。我个人是这样理解的,创建头结点的时候头结点确实是包含了数据域,但是头结点的数据域的值一般都是省缺的,就上面这个例子来说,头结点head的数据域head->data=0。这个是个人理解。

接下来说一下链表的一些操作吧。主要包括创建节点,插入节点,删除节点,查找节点。当然插入的方式有很多,接下来我会详细说明。

1、创建节点

这个接口可以用来创建头结点,但是我把节点数据域初始化为0了。

//只是创建一个节点,更具有通用性,甚至可以用来创建头结点
st_List *Create_List_Node(void)
{
	st_List *pNode = (st_List *)malloc(sizeof(st_List));
	
	if (pNode == NULL)
	{
		printf("create node error!\n");
		return NULL;
	}
	
	pNode->next = NULL;
	pNode->data = 0;
	
	
	return pNode;
}

2、插入节点

插入节点主要有三种,1)头插法、2) 尾插法、3)任意位置插法

1)头插法

所谓头插法就是在头结点之后插入一个节点,个人觉得可以扩展一下,只要在一个节点P1(这个节点可以看做是头结点)的后面插入一个 节点都可以称为头插法。头插法需要记住的一个点就是要先把新加入的节点的指针域指向头结点的指针域,最后再把头节点的指针域指向这个节点。函数的设计根据自己需求,返回值可以是int或者void等等的,不规定的

st_List *Add_Node_Head(st_List *pHead, st_List *pNode)
{
	if (pHead == NULL || pNode == NULL)
	{
		printf("head is null or node is null\n");
		return NULL;
	}
	
	//头插法
	pNode->next = pHead->next;
	pHead->next = pNode;
	
	return pHead;
}

2)尾插法

尾插法很简单,这里就不上图了。要注意的就是要先遍历到最后一个节点,然后把最后一个节点指针域指向新的节点,然后新的节点的指针域指向NULL就可以了

st_List *Add_Node_Tail(st_List *pHead, st_List *pNode)
{
	if (pHead == NULL || pNode == NULL)
	{
		printf("head is null or node is null\n");
		return NULL;
	}
	
	while (pHead->next != NULL)    //遍历到最后一个节点
	{
		pHead = pHead->next;
	}
	
	//尾插法
	pHead->next = pNode;
	pNode->next = NULL;       //在创建节点的时候已经指向NULL,为了代码完整性还是再写一遍
	
	return pHead;
}

3)插入到任意一个位置

这个写法就很多了,我只是提供我自己的一种思路给大家参考

nPosition就是插入的位置,注意这里需要是从0开始的。另外我这里采用头插法。假设我传入的nPosition=0,那么就会在原本nPosition=0的前面插入新的节点,因为我要把我新插入的节点作为新链表的首节点。同理,假设nPosition=3,新插入的节点就会成为新链表的第3个节点(序号从0开始)

//可以插入到任意一个位置更具有通用性
st_List *Insert_List_Node(st_List *pHead, st_List *pNode,int nPosition)
{
	st_List *pTemp = pHead;
	int n;
	
	if (pHead == NULL || pNode == NULL)	
	{
		printf("list is null or new node is null\n");
		return NULL;
	}
	
	
	if (nPosition == 0)				//在头结点后面插入第一个节点,但是这个链表不是空链表
	{
		pNode->next = pHead->next;
		pHead->next = pNode;
		return pHead;
	}
	
	
	if (pTemp->next == NULL)			//只有头结点,是一个空链表
	{
		pHead->next = pNode;
		pNode->next = NULL;
		return pHead;
	}
	
	for (n = 0; n < nPosition; n++)	//需要考虑链表本身的长度没有达到nPosition
	{
		pTemp = pTemp->next;
		
		if (pTemp == NULL)	 //链表本身没有达到nPosition的长度
		{
			printf("no position\n");
			return pHead;
		}
	
	}
	
	//头插法
	pNode->next = pTemp->next;
	pTemp->next = pNode;
	

	return pHead;
}

3、查找链表

查找节点一般是根据数据域的内容进行查找的,只要遍历链表找到对应节点数据域并把该节点传出来就好

我这里没有定义一个临时的节点来做遍历偏移,这个对新手来说可能需要点时间理解

st_List *Search_Node(st_List *pHead, int nData)			//查找节点
{
	if (pHead == NULL)
	{
		printf("head is null or node is null\n");
		return NULL;
	}
	
	while (pHead->next != NULL)
	{
		pHead = pHead->next;
		if (pHead->data == nData)
		{
			return pHead;
		}
	}
	
	printf("no node\n");	//没有找到节点
	return NULL;
}

4、删除链表

我这里提供根据位置和数据域的内容两种方法来删除节点,但是原理基本都是一样的。看一下函数原型

//删除节点的函数可以根据节点内容也可以根据节点的位置,
//这里用flag来做区分,flag=1根据节点内容来删除,flag=0根据位置来删除
void Delete_List_Node(st_List *pHead, st_List *pNode, int nPosition, int flag)

这个是最开始的删除函数,但是当时没有考虑要删除的节点时首节点(不是头结点,头结点没有数据),也没有考虑要删除的节点是最后一个节点。这里保持原来的写法,下面做修改。

//删除节点的函数可以根据节点内容也可以根据节点的位置,
//这里用flag来做区分,flag=1根据节点内容来删除,flag=0根据位置来删除
void Delete_List_Node(st_List *pHead, st_List *pNode, int nPosition, int flag)
{
	int i=0;
	if (pHead == NULL)
	{
		printf("head is null\n");
		return;
	}
	
	if (flag)    //这个分支要加边界处理2020-7-26
	{
		if (pNode == NULL)
		{
			printf("node is null\n");
			return;
		}
		
		while (pHead->next != NULL)		//遍历到下一个节点
		{
			if (pHead->next->data == pNode->data)	//下一个节点的数据是不是想要删除的
			{
				pHead->next = pNode->next;//当前节点的指针域指向要删除的节点的指针域指向的内存
			}
			
			pHead = pHead->next;
		}	
		
		free(pNode);
	}
	else
	{
		for (i = 0; i < nPosition; i++)
		{
			pTemp = pHead;
			printf("111%p\n",pTemp);
			pHead = pHead->next;
			printf("222%p\n",pTemp);
			if (pHead == NULL)
			{
				printf("no node\n");	//超过链表原本的长度,找不到节点
				return;
			}
		}
		
		pTemp->next = pHead->next;
		
		free(pHead);
		
	}
	
	return;
}

下面是增加边界处理的最新的代码,主要是添加了一个临时节点pTemp来做中间转换,考虑到要删除的节点是最后一个节点的话,我们需要保存倒数第二个节点(最后一个节点的前一个节点),这里就用pTemp来保存

//删除节点的函数可以根据节点内容也可以根据节点的位置,
//这里用flag来做区分,flag=1根据节点内容来删除,flag=0根据位置来删除
void Delete_List_Node(st_List *pHead, st_List *pNode, int nPosition, int flag)
{
	int i=0;
	st_List *pTemp;
	if (pHead == NULL)
	{
		printf("head is null\n");
		return;
	}
	
	if (flag)
	{
		if (pNode == NULL)
		{
			printf("node is null\n");
			return;
		}
		
		while (pHead->next != NULL)		//遍历到下一个节点
		{
			pTemp = pHead;    //每次都要记住要删除的节点的前一个节点
			pHead = pHead->next;
			if (pHead->data == pNode->data)	//下一个节点的数据是不是想要删除的
			{
				pTemp->next = pNode->next;//当前节点的指针域指向要删除的节点的指针域指向的内存
			}
		}	
		
		if (pHead->data == pNode->data)    //这里考虑是最后一个节点
		{
			printf("hhhh\n");
			pTemp->next = NULL;
		}
		
		free(pNode);
	}
	else
	{
		for (i = 0; i < nPosition; i++)
		{
			pTemp = pHead;
			printf("111%p\n",pTemp);
			pHead = pHead->next;
			printf("222%p\n",pTemp);
			if (pHead == NULL)
			{
				printf("no node\n");	//超过链表原本的长度,找不到节点
				return;
			}
		}
		
		pTemp->next = pHead->next;
		
		free(pHead);
		
	}
	
	return;
}

 

最后来看一下main函数,在看main函数之前, 为了方便测试, 再写一个创建链表的函数,这个函数是直接复制其他博主的

https://blog.csdn.net/qq_41481924/article/details/85340787

st_List *Create_List(int nNodeNum)    //nNodeNum是节点数
{
	st_List *pHead, *pNode;
	int i;
	pHead = (st_List *)malloc(sizeof(st_List));
	pHead->next = NULL;
	
	
	for (i = 0; i < nNodeNum; i++)
	{
		pNode = (st_List *)malloc(sizeof(st_List));
		scanf("%d",&pNode->data);
		
		//头插法
		pNode->next = pHead->next;
		pHead->next = pNode;
	}
	
	return pHead;
}

同时为了方便 可以查看,就再添加一个打印的函数

void ShowList(st_List *pHead)
{
	st_List *pTemp = pHead;
	
	if (pHead == NULL)
	{
		return;
	}
	
	while (pTemp->next != NULL)
	{
		pTemp = pTemp->next;
		printf("pTemp->data = %d\n",pTemp->data);
	}
		
		return;
}

最后是main函数了,可以直接拷贝过去使用,根据自己需要做调整就好

int main()
{
	st_List *pHead;
	st_List *pNew;
	st_List *pNew1;
	st_List *pNodeTail;
	st_List *pNodeHead;
	st_List *pDel;
	//pHead = Create_List_Node();			//创建头结点
	pHead = Create_List(5);					//创建一个含有5个节点的链表
	
	pNew = Create_List_Node();			//新建一个节点
	pNew->data = 10;
	
	Insert_List_Node(pHead,pNew,3);
	
	pNodeHead = Create_List_Node();
	pNodeHead->data = 100;
	Add_Node_Head(pHead,pNodeHead);
	
	pNodeTail = Create_List_Node();
	pNodeTail->data = 120;
	Add_Node_Tail(pHead,pNodeTail);
	
	pDel = Search_Node(pHead,65);
	if (pDel != NULL)
	{
		printf("pDel->data = %d\n",pDel->data);
	}
	/*
	pNew1 = Create_List_Node();			//新建一个节点
	pNew1->data = 12;
	Insert_List_Node(pHead,pNew1,1);
	*/
	
	ShowList(pHead);
	printf("\n\n----------------------------\n\n");
	
	Delete_List_Node(pHead,pDel,2,0);
	ShowList(pHead);
	return 0;
}
 

以上链表操作函数是没有考虑效率的,网上有更多的大佬提供更精简代码量更少的实现方式,这里只是提供我自己的简单的理解,只要考虑得周全,理解链表操作,写出高效精简的代码应该不是难事!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值