[数据结构]单链表(C语言版)

在学习单链表之前我们已经学习了顺序表相关的基本操作,顺序表访问元素更加方便,物理地址是连续的;但是也有一些缺点:

1.在头部插入或者从中间插入或删除元素时需要搬移数据,效率较低

2.在插入数据时可能存在空间不足的情况,需要扩容

因此就会出现另一种线性表---链表

1.链表的概念

链表顾名思义就是链式的存储结构,元素的逻辑顺序是由指针来依次连接的。

链表中有多个节点,每一个节点里储存着数据,还有指向下一个节点的指针 。

2.单链表的定义

我们要实现的是不带头结点的非循环单向链表

单链表的结构包括数值域和指针域

typedef int DataType;
typedef struct SListNode
{
	DataType data;
	struct SListNode* next;//指向下一个节点
}SListNode;

3.单链表节点的创建

SListNode* BuySListNode(DataType x)
{
	SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));//这个动态内存空间是用来存放这个结构体类型的
	if (NULL == newNode)
	{
		printf("创建节点失败!!!!");
		exit(0);
	}
	newNode->data = x;
	newNode->next = NULL;
	return newNode;
}

4.单链表的尾插

void SListPushBack(SListNode** pplist,DataType x)
{
	assert(*pplist);
	//如果pplist为空则需要让pplist指向刚刚插入的节点
	if (NULL == *pplist)
	{
		*pplist = BuySListNode(x);
	}
	else
	{
		//此时链表不为空
		//1.构建一个新节点
		SListNode* NewNode = BuySListNode(x);
		//2.找出原链表中的最后一个节点
		SListNode* cur = *pplist;
		while (cur->next)
		{
			//获取cur的下一个节点
			cur = cur->next;
		}
		//3.插入新节点
		cur->next = NewNode;
	}
}

5.单链表的尾删

//单链表的尾删
void SListPopBack(SListNode** pplist)
{
	assert(pplist);//保证pplist指向实参
	//1.空链表
	if (NULL == *pplist)
	{
		return;
	}
	else if (NULL == (*pplist)->next)
	{
		//2.链表中只有一个节点
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		//3.链表中有多个节点
		//a.找倒数第一个节点
		SListNode* cur = *pplist;
		SListNode* prev = NULL;//保存cur前一个节点
		while (cur->next)
		{
			prev = cur;
			cur = cur->next;
		}
		prev->next = NULL;
		free(cur);
		//b.找倒数第二个节点
		/*
		SListNode* cur = *pplist;
		while(cur->next->next)
		{
			cur = cur->next;
		}
		free(cur->next);
		cur->next = NULL;
		*/
		//c.错误写法
		/*while (cur->next)
		{
			cur = cur->next;
		}
		free(cur);*/
		
	}
}

6.单链表打印

//单链表打印
void SListPrint(SListNode* plist)
{
	SListNode* cur = plist;
	while (cur)
	{
		printf("&d--->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");//单链表最后一个节点指向空
}

7.单链表头插

// 单链表的头插
//时间复杂度:O(1)
void SListPushFront(SListNode** pplist, SLTDataType x)
{
	//assert(pplist);
	//SListNode* newNode = BuySListNode(x);
	1.链表为空
	//if (NULL == *pplist)
	//{
	//	*pplist = newNode;
	//}
	//else
	//{
	//	//2.链表不空
	//	newNode->next = *pplist;
	//	*pplist = newNode;
	//}
	assert(pplist);
	//表面看上需要分情况讨论,但是在分析之后情况一可以与情况二合并
	SListNode* newNode = BuySListNode(x);
	newNode->next = *pplist;
	*pplist = newNode;
}

8.单链表头删

//单链表头删
void SListPopFront(SListNode** pplist)
{
	assert(pplist);
	//1.链表为空
	if (NULL == *pplist)
	{
		return;
	}
	//else if (NULL == (*pplist)->next)
	//{
	//	//2.链表只有一个节点
	//	free(*pplist);
	//	*pplist = NULL;
	//}
	else
	{
		//3.链表中有多个节点(分析后可知也包含一个节点的情况)
		SListNode* del = *pplist;//将要删除的节点保存起来
		*pplist = (*pplist)->next;
		free(del);
	}

}

9.单链表删除任意位置pos之后的值

// 单链表删除pos位置之后的值
//时间复杂度O(1)
void SListEraseAfter(SListNode* pos)
{
	SListNode* delNode = NULL;
	if (NULL == pos)
	{
		return;
	}
	delNode = pos->next;
	if (NULL == delNode)
	{
		return;
	}
	//pos->data = delNode->data;
	pos->next = delNode->next;
	free(delNode);
}

代码中加一句pos->data = delNode->data就可以实现删除任意位置的值

但是不能删除最后一个节点位置的元素

10.单链表在任意位置pos之后插入

//单链表在任意位置pos之后插入
//时间复杂度:O(1)
void SListInsertAfter(SListNode* pos, SLTDataType x)
{
	SListNode* newNode = NULL;
	if (NULL == pos)
	{
		return;
	}
	newNode = BuySListNode(x);
	newNode->next = pos->next;//必须先让新节点接入到链表内然后将pos位置的节点指向新节点
	pos->next = newNode;
}

新节点无法插入到pos之前,因为该方法未提供plist头节点

11.单链表在任意位置pos之前插入

//单链表在任意位置pos之前插入
void SListInsertBefore(SListNode** pplist, SListNode* pos, SLTDataType x)
{
	assert(pplist);
	assert(pos);
	SListNode* newNode = NULL;
	if (pos == *pplist)
	{
		newNode = BuySListNode(x);
	}
	else
	{	
		SListNode* cur = *pplist;//将头节点保存起来,**pplist是plist的地址,*plist才是解引用后的指针
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		newNode = BuySListNode(x);
		newNode->next = pos;
		cur->next = newNode;
	}
}

11.单链表的销毁

//单链表销毁
void SListDestroy(SListNode** pplist)
{
	assert(pplist);
	SListNode* cur = *pplist;
	while (cur)
	{
		*pplist = cur->next;
		free(cur);
		cur = *pplist;
	}
	*pplist = NULL;
}

单链表的销毁很简单,只需要用cur把链表遍历一遍,每次让pplist头指针指向cur->next,释放cur所指向的当前节点,然后更新pplist指针的值,最后将pplist指向NULL。

12.单链表操作的测试

void TestSList1()
{
	SListNode* plist = NULL;//单链表的起始位置保存

	//插入第一个节点时需要在此函数中通过形参来达到修改plist指向的修改
	SListPushBack(&plist, 1);//依次尾插1,2,3,4,5
	SListPrint(plist);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPushBack(&plist, 5);
	SListPrint(plist);

	SListPopBack(&plist);//尾删
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);

	SListPushFront(&plist, 1);//依次头插1,2,3,4,5
	SListPrint(plist);
	SListPushFront(&plist, 2);
	SListPrint(plist);
	SListPushFront(&plist, 3);
	SListPrint(plist);
	SListPushFront(&plist, 4);
	SListPrint(plist);
	SListPushFront(&plist, 5);
	SListPrint(plist);

	SListInsertAfter(SListFind(plist, 3), 7);//在3后面插入7
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
	SListInsertAfter(SListFind(plist, 1), 8);//在1后面插入8
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
	SListInsertAfter(SListFind(plist, 2), 9);//在2后面插入9
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));

	SListEraseAfter(SListFind(plist, 3));//删除3后面的元素
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
	SListEraseAfter(SListFind(plist, 1));//删除1后面的元素
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
	SListEraseAfter(SListFind(plist, 2));//删除2后面的元素
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));

	SListInsertBefore(&plist, SListFind(plist, 1), 6);//在1之前插入元素6
	SListPrint(plist);
	printf("节点数为 %d\n",SListSize(plist));

}
void TestSList2()
{
	SListNode* plist = NULL;//单链表的起始位置保存

//插入第一个节点时需要在此函数中通过形参来达到修改plist指向的修改
	SListPushBack(&plist, 1);//依次尾插1,2,3,4,5
	SListPrint(plist);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPushBack(&plist, 5);
	SListPrint(plist);

	SListPopBack(&plist);//尾删
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);

	SListPushFront(&plist, 1);//依次头插1,2,3,4,5
	SListPrint(plist);
	SListPushFront(&plist, 2);
	SListPrint(plist);
	SListPushFront(&plist, 3);
	SListPrint(plist);
	SListPushFront(&plist, 4);
	SListPrint(plist);
	SListPushFront(&plist, 5);
	SListPrint(plist);

	SListPopFront(&plist);//头删
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
	SListPopFront(&plist);
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
	SListPopFront(&plist);
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
	SListPopFront(&plist);
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
	SListPopFront(&plist);
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
}

 

本文全部代码:

SList.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SListNode;
//动态申请一个节点
SListNode* BuySListNode(SLTDataType x);
//单链表的尾插
void SListPushBack(SListNode** pplist, SLTDataType x);
//单链表的尾删
void SListPopBack(SListNode** pplist);
//链表打印
void SListPrint(SListNode* plist);

// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDataType x);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDataType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDataType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);
// 单链表的销毁
void SListDestroy(SListNode** plist);

SList.c 

#include "SList.h"
//动态申请一个节点
SListNode* BuySListNode(SLTDataType x)
{
	SListNode* newNode = (SListNode *) malloc(sizeof(SListNode));//这个动态内存空间是用来存放这个结构体类型的
	if (NULL == newNode)
	{
		printf("创建节点失败!!!!");
		exit(0);
	}
	newNode->data = x;
	newNode->next = NULL;
	return newNode;
}
//单链表的尾插
void SListPushBack(SListNode** pplist, SLTDataType x)
{
	//若为空链表则需要让pplist指向刚刚插入的节点
	if (NULL == *pplist)
	{
		*pplist = BuySListNode(x);
	}
	else
	{
		//此时链表不为空
		//1.构建一个新节点
		SListNode* newNode = BuySListNode(x);
		//2.找原链表中最后一个节点
		SListNode* cur = *pplist;
		while (cur->next)
		{
			//获取cur的下一个节点
			cur = cur->next;
		}
		//3.插入新节点
		cur->next = newNode;
	}
}
//单链表的尾删
void SListPopBack(SListNode** pplist)
{
	assert(pplist);
	     //1.空链表
	if (NULL == *pplist)
	{
		return;
	}
	else if(NULL == (*pplist)->next)
	{
		//2.链表中只有一个节点
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
        //3.链表中有多个节点

		SListNode* cur = *pplist;//*pplist为第一个节点
		while (cur->next->next)
		{
			cur = cur->next;
		}
		free(cur->next);
		cur->next = NULL;
		//方法2
		/*
		SListNode* cur = *pplist;//*pplist为第一个节点
		SListNode* prev = NULL;
		while (cur->next)
		{
		    prve = cur;
			cur = cur->next;
		}
		free(cur);
		prve->next = NULL;
		*/
	}
	    

}
//链表打印
void SListPrint(SListNode* plist)
{
	SListNode* cur = plist;
	while (cur)
	{
		printf("%d--->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
// 单链表的头插
//时间复杂度:O(1)
void SListPushFront(SListNode** pplist, SLTDataType x)
{
	assert(pplist);
	SListNode* newNode = BuySListNode(x);
	1.链表为空
	//if (NULL == *pplist)
	//{
	//	*pplist = newNode;
	//}
	//else
	//{
	//	//2.链表不空
	//	newNode->next = *pplist;
	//	*pplist = newNode;
	//}
	//表面看上需要分情况讨论,但是在分析之后情况一可以与情况二合并

	newNode->next = *pplist;
	*pplist = newNode;
}
//单链表查找
SListNode* SListFind(SListNode* plist, SLTDataType x)
{
	SListNode* cur = plist;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
//单链表中的有效节点数
int SListSize(SListNode* plist)
{
	int size = 0;
	SListNode* cur = plist;
	while (cur)
	{
		size++;
		cur = cur->next;
	}
	return size;
}
//单链表头删
void SListPopFront(SListNode** pplist)
{
	assert(pplist);
	SListNode* del = NULL;
	//1.链表为空
	if (NULL == *pplist)
	{
		return;
	}
	//else if (NULL == (*pplist)->next)
	//{
	//	//2.链表只有一个节点
	//	free(*pplist);
	//	*pplist = NULL;
	//}
	else
	{
		//3.链表中有多个节点(分析后可知也包含一个节点的情况)
		SListNode* del = *pplist;//将要删除的节点保存起来
		*pplist = (*pplist)->next;
		free(del);
	}

}
//单链表在任意位置pos之后插入
//新节点无法插入到pos之前,因为该方法未提供plist头节点
//时间复杂度:O(1)
void SListInsertAfter(SListNode* pos, SLTDataType x)
{
	SListNode* newNode = NULL;
	if (NULL == pos)
	{
		return;
	}
	newNode = BuySListNode(x);
	newNode->next = pos->next;//必须先让新节点接入到链表内然后将pos位置的节点指向新节点
	pos->next = newNode;
}
//单链表在任意位置pos之前插入
void SListInsertBefore(SListNode** pplist, SListNode* pos, SLTDataType x)
{
	assert(pplist);
	assert(pos);
	SListNode* newNode = NULL;
	if (pos == *pplist)
	{
		newNode = BuySListNode(x);
	}
	else
	{	
		SListNode* cur = *pplist;//将头节点保存起来,**pplist是plist的地址,*plist才是解引用后的指针
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		newNode = BuySListNode(x);
		newNode->next = pos;
		cur->next = newNode;
	}
}
// 单链表删除pos位置之后的值
//时间复杂度O(1)
void SListEraseAfter(SListNode* pos)
{
	SListNode* delNode = NULL;
	if (NULL == pos)
	{
		return;
	}
	delNode = pos->next;
	if (NULL == delNode)
	{
		return;
	}
	//pos->data = delNode->data;
	pos->next = delNode->next;
	free(delNode);
}
//单链表销毁
void SListDestroy(SListNode** pplist)
{
	assert(pplist);
	SListNode* cur = *pplist;
	while (cur)
	{
		*pplist = cur->next;
		free(cur);
		cur = *pplist;
	}
	*pplist = NULL;
}


void TestSList1()
{
	SListNode* plist = NULL;//单链表的起始位置保存

	//插入第一个节点时需要在此函数中通过形参来达到修改plist指向的修改
	SListPushBack(&plist, 1);//依次尾插1,2,3,4,5
	SListPrint(plist);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPushBack(&plist, 5);
	SListPrint(plist);

	SListPopBack(&plist);//尾删
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);

	SListPushFront(&plist, 1);//依次头插1,2,3,4,5
	SListPrint(plist);
	SListPushFront(&plist, 2);
	SListPrint(plist);
	SListPushFront(&plist, 3);
	SListPrint(plist);
	SListPushFront(&plist, 4);
	SListPrint(plist);
	SListPushFront(&plist, 5);
	SListPrint(plist);

	SListInsertAfter(SListFind(plist, 3), 7);//在3后面插入7
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
	SListInsertAfter(SListFind(plist, 1), 8);//在1后面插入8
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
	SListInsertAfter(SListFind(plist, 2), 9);//在2后面插入9
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));

	SListEraseAfter(SListFind(plist, 3));//删除3后面的元素
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
	SListEraseAfter(SListFind(plist, 1));//删除1后面的元素
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
	SListEraseAfter(SListFind(plist, 2));//删除2后面的元素
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));

	SListInsertBefore(&plist, SListFind(plist, 1), 6);//在1之前插入元素6
	SListPrint(plist);
	printf("节点数为 %d\n",SListSize(plist));

}
void TestSList2()
{
	SListNode* plist = NULL;//单链表的起始位置保存

//插入第一个节点时需要在此函数中通过形参来达到修改plist指向的修改
	SListPushBack(&plist, 1);//依次尾插1,2,3,4,5
	SListPrint(plist);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPushBack(&plist, 5);
	SListPrint(plist);

	SListPopBack(&plist);//尾删
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);

	SListPushFront(&plist, 1);//依次头插1,2,3,4,5
	SListPrint(plist);
	SListPushFront(&plist, 2);
	SListPrint(plist);
	SListPushFront(&plist, 3);
	SListPrint(plist);
	SListPushFront(&plist, 4);
	SListPrint(plist);
	SListPushFront(&plist, 5);
	SListPrint(plist);

	SListPopFront(&plist);//头删
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
	SListPopFront(&plist);
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
	SListPopFront(&plist);
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
	SListPopFront(&plist);
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
	SListPopFront(&plist);
	SListPrint(plist);
	printf("节点数为 %d\n", SListSize(plist));
}



int main()
{
	//TestSList1();
	TestSList2();
	return 0;
}

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值