数据结构之单链表[详解]

一、单链表的概念及结构

1.概念

链表是一种物理结构非线性的,但链表在逻辑结构上是线性的,而数据元素的逻辑顺序是通过链表中的指针连接次序实现的。也就是说,链表中的每一个数据元素都是独立存储的,当需要存储数据元素时就申请一块空间用来存储当前数据,每一个数据元素又通过指针串联在一起,形成逻辑上的线性。

2.结构

链表是由一个个结点组成的数据结构,每一个结点都包含了两块区域**(数据域,指针域)**,当前结点的指针域存储的是下一个结点的地址,即指向下一个结点,而最后一个结点的指针域则是指向NULL。一个一个的结点连接起来就组成了链表。

在这里插入图片描述

每一个结点都是通过malloc申请的,两次申请得到的空间可能是连续的,也可能不是连续的。之所以结点之间可以用箭头连接,并不是表示物理结构上连续,而是因为当前结点的指针域存储的是下一个结点的地址,即可以通过当前结点的指针域找到下一个结点。

二、顺序表和单链表的区别

1.结构

顺序表:逻辑结构线性,物理结构线性,通过数组遍历的方式对数据进行操作。

链表:逻辑结构线性,物理结构非线性,通过一个个关系紧密的指针域对数据进行操作。

2.优缺点

①顺序表:[1]优点:可以通过下标直接访问所需要的数据;[2]缺点:容易发生频繁扩容,导致内存的浪费

②链表:[1]优点:可以按照实际需求创建结点,更充分地使用内存空间,且链表头部的插入、删除时间复杂度为O(1),而顺序表为O(N);[2]缺点:链表尾部的插入、删除时间复杂度为O(N),而顺序表为O(1);
因此当头部的插入、删除较多时用链表较好;当尾部的插入较多时用顺序表较好。

三、单链表的实现

头指针:plist存放头结点的地址,plist解引用后就是头结点的值。pphead储存plist的地址,pphead解引用后是plist的值

断言:①空链表可以打印,不用断言

②空链表可以尾插、头插,即plist可以为空,因此*pphead不用断言,而pphead是&plist,必定非空,需要断言

③空链表不能够尾删、头删,即plist不能为空,*pphead需要断言,pphead也需要断言

总之,pphead就是&plist,必定非空,需要断言,*pphead就情况而论

1.代码分区

创建3个文件:①头文件SList.h ②源文件(实现文件)SList.c ③源文件(测试文件)test.c

其中头文件SList.h进行单链表的定义和函数的声明;实现文件SList.c进行函数的实现;测试文件test.c进行测试。

2.1结点的定义

//定义链表的结构--结点的结构
typedef int SLTDataType;
typedef struct SListNode {
	SLTDataType data;	   //储存的数据
	struct SListNode* next;//下一个节点的地址
}SLTNode;

2.2结点的创建

 SLTNode* SLTbuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTDataType)); //申请一个结点空间
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

3.链表的打印

链表和顺序表有所不同,顺序表传过来的指针肯定不会为空,而链表传过来的指针可能为空,不过空链表可以打印,因此不用断言

 void SLTPrint(SLTNode* phead)
 {
	 SLTNode* pcur = phead;    //phead存储的是头结点的地址,因此我们不能改变phead
	 while (pcur)
	 {
		 printf("%d -> ", pcur->data);
		 pcur = pcur->next;
	 }
	 printf("\n");
 }

4.尾插

 void SLTPushBack(SLTNode** pphead, SLTDataType x)
 {
 	 assert(pphead);
	 SLTNode* newnode=SLTbuyNode(x);
	 if (*pphead == NULL)  //链表为空
	 {
		 *pphead = newnode;
	 }
	 else                  //链表非空
	 {
		 SLTNode* ptail = *pphead;
		 while (ptail->next)
		 {
			 ptail = ptail->next;
		 }
		 ptail->next = newnode;
	 }
 }

==为什么形参要传二级指针?==①链表为空的情况下,尾插相当于是:将新结点直接当作头结点,即要改变plist中存储的地址,那么应该使出传址调用,因此实参应传入&plist,所以形参应该是二级指针去接收。②每一个结点里都有一级指针next,想要改变next,就必须使用二级指针。(插入,删除,销毁都会涉及next的改变,因此都传二级指针SLTNode** pphead)。

5.头插

只需要 让新结点与原来的头结点产生链接,然后更新plist就好了。

 void SLTPushFront(SLTNode** pphead, SLTDataType x)
 {
	 assert(pphead);
	 SLTNode* newnode = SLTbuyNode(x);   //创建新结点
	 newnode->next = *pphead;
	 *pphead = newnode;
 }

6.尾删

 void SLTPopBack(SLTNode** pphead)
 {
	 assert(pphead && *pphead);
	 if ((*pphead)->next == NULL)  //只有一个结点的情况
	 {
		 free(*pphead);  //直接释放
		 *pphead = NULL;  
	 }
	 else    //有多个结点的情况
	 {
		 SLTNode* prev = NULL;
		 SLTNode* ptail = *pphead;
		 while (ptail->next)
		 {
			 prev = ptail;
			 ptail = ptail->next;  //prev永远在ptail前的那一个位置
		 }
		 prev->next = NULL;
		 free(ptail);
		 ptail = NULL;
	 }
 }

7.头删

void SLTPopFront(SLTNode** pphead)
{
 assert(*pphead);
 SLTNode* next = (*pphead)->next; //不能直接free,否则就无法通过*pphead找到第二个结点,即无法更新plist
 free(*pphead);   
 *pphead = next;
}

8.查找

采用while循环遍历,函数返回的是一个结构体指针

 SLTNode* SLTFind(SLTNode* phead, SLTDataType* x)
 {
	 SLTNode* pcur = phead;
	 while (pcur)
	 {
		 if (pcur->data == x)
		 {
			 return pcur;
		 }
		 pcur = pcur->next;
	 }
	 return NULL;
 }

9.在pos之前插入结点

 void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
 {
	 assert(pphead && pos);
	 if (pos == *pphead)   //pos指向第一个位置,相当于头插
	 {
		 SLTPushFront(pphead, x);
	 }
	 else
	 {
		 SLTNode* newnode = SLTbuyNode(x);
		 //找pos的前一个结点
		 SLTNode* prev = *pphead;
		 while (prev->next != pos)
		 {
			 prev = prev->next;
		 }
		 prev->next = newnode;
		 newnode->next = pos;  //让新结点与前后结点形成链接
	 }
 }

10.在pos之后插入结点

 void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x)
 {
	 assert(pos);
	 SLTNode* newnode = SLTbuyNode(x);
	 newnode->next = pos->next;
	 pos->next = newnode;
 }

11.删除pos结点

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && pos);
 	if (pos == *pphead)    //pos指向第一个位置,相当于头删
 	{
		 SLTPopFront(pphead);
	 }
 	else
	 {
		 SLTNode* prev = *pphead;
		 while (prev->next != pos)  //找到pos的前一个结点
		 {
			 prev = prev->next;
		 }
		 prev->next = pos->next;    //让pos的前一个结点与pos的后一个结点链接
		 free(pos);
		 pos = NULL;
 	}
}

12.删除pos之后的结点

void SLTEraseAfter(SLTNode* pos)
{
 assert(pos && pos->next);//诺pos->next==NULL,那后续free就会报错
 SLTNode* del = pos->next;
 pos->next = del->next;  //语句3
 free(del);
 del = NULL;
}

临时指针del用来记录原先的pos-next,方便执行完语句3后找到位置进行free。如果不使用del,在执行完语句3后,pos->next将发生改变,想删掉的结点就不容易找到位置去删除了。

13.销毁链表

 void SListDestroy(SLTNode** pphead)
 {
	 SLTNode* pcur = *pphead;
	 while (pcur)
	 {
		 SLTNode* next = pcur->next;
		 free(pcur);
		 pcur = next;
	 }
	 *pphead = NULL;
 }

链表在物理结构上是不连续存储的,销毁链表必须要一个结点一个结点去销毁,最后把phead设置为NULL

四、总代码

①SList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;
//定义链表的结构--结点的结构
typedef struct SListNode {
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

//结点的创建
SLTNode* SLTbuyNode(SLTDataType x);
//链表的打印
void SLTPrint(SLTNode* phead);
//链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//链表的尾删
void SLTPopBack(SLTNode** pphead);
//链表的头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType* x);
//在pos之前插入结点
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在pos之后插入结点
void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDestroy(SLTNode** pphead);

②SList.c

#include "SList.h"
//结点的创建
 SLTNode* SLTbuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTDataType));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
 //链表的打印
 void SLTPrint(SLTNode* phead)
 {
	 SLTNode* pcur = phead;
	 while (pcur)
	 {
		 printf("%d -> ", pcur->data);
		 pcur = pcur->next;
	 }
	 printf("\n");
 }
 //链表的尾插
 void SLTPushBack(SLTNode** pphead, SLTDataType x)
 {
	 assert(pphead);
	 SLTNode* newnode=SLTbuyNode(x);
	 if (*pphead == NULL)
	 {
		 *pphead = newnode;
	 }
	 else
	 {
		 SLTNode* ptail = *pphead;
		 while (ptail->next)
		 {
			 ptail = ptail->next;
		 }
		 ptail->next = newnode;
	 }
 }
 //链表的头插
 void SLTPushFront(SLTNode** pphead, SLTDataType x)
 {
	 assert(pphead);
	 SLTNode* newnode = SLTbuyNode(x);
	 newnode->next = *pphead;
	 *pphead = newnode;
 }
 //链表的尾删
 void SLTPopBack(SLTNode** pphead)
 {
	 assert(pphead && *pphead);
	 if ((*pphead)->next == NULL)  //只有一个结点
	 {
		 free(*pphead);
		 *pphead = NULL;  
	 }
	 else    //有多个结点
	 {
		 SLTNode* prev = NULL;
		 SLTNode* ptail = *pphead;
		 while (ptail->next)
		 {
			 prev = ptail;
			 ptail = ptail->next;  //prev永远在ptail前的那一个位置
		 }
		 prev->next = NULL;
		 free(ptail);
		 ptail = NULL;
	 }
 }
 //链表的头删
 void SLTPopFront(SLTNode** pphead)
 {
	 assert(*pphead);
	 SLTNode* next = (*pphead)->next;
	 free(*pphead);
	 *pphead = next;
 }
 //查找
 SLTNode* SLTFind(SLTNode* phead, SLTDataType* x)
 {
	 SLTNode* pcur = phead;
	 while (pcur)
	 {
		 if (pcur->data == x)
		 {
			 return pcur;
		 }
		 pcur = pcur->next;
	 }
	 return NULL;
 }
 //在pos之前插入结点
 void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
 {
	 assert(pphead && pos);
	 if (pos == *pphead)   //pos指向第一个位置,相当于头插
	 {
		 SLTPushFront(pphead, x);
	 }
	 else
	 {
		 SLTNode* newnode = SLTbuyNode(x);
		 //找pos的前一个结点
		 SLTNode* prev = *pphead;
		 while (prev->next != pos)
		 {
			 prev = prev->next;
		 }
		 prev->next = newnode;
		 newnode->next = pos;
	 }
 }
 //在pos之后插入结点
  void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x)
 {
	 assert(pos);
	 SLTNode* newnode = SLTbuyNode(x);
	 newnode->next = pos->next;
	 pos->next = newnode;
 }
 //删除pos结点
 void SLTErase(SLTNode** pphead, SLTNode* pos)
 {
	 assert(pphead && pos);
	 if (pos == *pphead)
	 {
		 SLTPopFront(pphead);
	 }
	 else
	 {
		 SLTNode* prev = *pphead;
		 while (prev->next != pos)
		 {
			 prev = prev->next;
		 }
		 prev->next = pos->next;
		 free(pos);
		 pos = NULL;
	 }
 }
 //删除pos之后的结点
 void SLTEraseAfter(SLTNode* pos)
 {
	 assert(pos && pos->next);//诺pos->next==NULL,那后续free就会报错
	 SLTNode* del = pos->next;
	 pos->next = del->next;
	 free(del);
	 del = NULL;
 }
 //销毁链表
 void SListDestroy(SLTNode** pphead)
 {
	 SLTNode* pcur = *pphead;
	 while (pcur)
	 {
		 SLTNode* next = pcur->next;
		 free(pcur);
		 pcur = next;
	 }
	 *pphead = NULL;
 }

③test.c

#include "SList.h"
void test()
{
	SLTNode* plist = NULL;   //创建空链表
	SLTPushBack(&plist, 1);	 //尾插,变成1234
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);
	SLTPopBack(&plist);      //尾删,变成123
	SLTPrint(plist);
	SLTPushFront(&plist,5); //头插,变成5123
	SLTPrint(plist);
	SLTPopFront(&plist);	 //头删,变成123
	SLTPrint(plist);
	SLTNode* Find = SLTFind(plist, 2);		 //查找2
	if (Find)
		printf("找到了\n");
	else
		printf("没找到\n");
	SLTInsert(&plist, Find, 6); //在pos前插入结点,变成1623
	SLTPrint(plist);
	SLTInsertAfter(&plist, Find, 7); //在pos前插入结点,变成16273
	SLTPrint(plist);
	SLTEraseAfter(Find);		//删除pos后的结点,变成1623
	SLTPrint(plist);
	SLTErase(&plist,Find);		//删除pos结点,变成163
	SLTPrint(plist);
	SListDestroy(&plist);		//销毁链表
}
int main()
{
	test();
	return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值