数据结构之链表详解

目录

前言

1.链表的概念及结构

 2.链表的实现

2.1 准备工作

2.2 链表结构声明

2.3 动态节点的申请

2.4 单链表尾插

 2.5 打印函数的实现

 2.6 单链表尾删

2.7 单链表的头插

 2.8 单链表头删

 2.9  单链表查找

2.10  单链表在pos位置之后插入x

 2.11 单链表删除pos位置之后的值

2.12  单链表的销毁

总代码


前言

即顺序表之后,今天给大家带来另外一个线性结构——链表。


1.链表的概念及结构

链表是一种 物理存储结构上非连续 、非顺序的存储结构,数据元素的 逻辑顺序 是通过链表
中的 指针链接 次序实现的 。
虽然链表在存储未位置上是不连续的,但在逻辑上其又是连续的,具体结构如下:

 2.链表的实现

2.1 准备工作

和顺序表一样,这里我们准备三个文件

Slist.c——对链表相关功能的实现

Slist.h——进行有关函数的声明

Text.c——对有关功能的实现

2.2 链表结构声明

typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;

这里我们用data存储相关内存,用结构体指针next存储下一个节点的位置。

2.3 动态节点的申请

由于链表是通过一个一个节点在逻辑上连续实现的,所以创建节点是我们必须需要的操作,这里我们就将其封装成一个函数,以便每次的使用

SListNode* BuySListNode(SLTDateType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

2.4 单链表尾插

void SListPushBack(SListNode** pplist, SLTDateType x)
{
	assert(pplist);
	SListNode*newnode= BuySListNode(x);
	if (*pplist == NULL)
	{
		*pplist = newnode;
	}
	else
	{
		SListNode* tail=*pplist;
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

这里我们采用的是无哨兵位的单链表,所以头指针为空时我们直接将头指针指向新节点,如果头指针后有节点我们就用tail指针找到尾节点,然后让尾节点的next指向新节点实现插入,还有一点是这里我们要传二级指针,由于形参是对实参的复制,所以我们要改变头节点时,只传一级指针时,并不可以改变头节点的指向,比如

 此时头指针s还是指向NULL,我们只是把形参这个指针指向了新节点而已,并没有改变链表结构。

对于具体的尾插操作这里我给大家实际演示一下:

 2.5 打印函数的实现

实现打印函数,以便我们更好的测试我们所实现的函数

void SListPrint(SListNode* plist)
{
	SListNode* cur = plist;
	while (cur)
	{
		printf("%d-> ", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

这里解释一下为什么传的是一级指针,由于我们这里只是打印链表,对链表的头节点不进行改变,所以这里我们直接传一级指针即可。

接下来我们就对我们的后插进行测试:

void text3()
{
	SListNode* s = NULL;
	SListPushBack(&s, 1);
	SListPushBack(&s, 2);
	SListPushBack(&s, 3);
	SListPushBack(&s, 4);
	SListPrint(s);
}
int main()
{
	text3();
	return 0;
}

结果展示:

 2.6 单链表尾删

  

void SListPopBack(SListNode** pplist)
{
	assert(pplist);
	assert(*pplist);
	if ((*pplist)->next==NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		SListNode* tail = *pplist;
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

这里我给大家解释一下上面的两个断言,首先我们二级指针存储的是单链表头节点的地址,所以该不可能为空,其次由于是进行删除操作,所以单链表不可能为空,所以解引用后,该是不可能为空指针的,所以我们要对该进行断言操作。

其次这里的删除一共有两种情况:

第一是头指针后只有一个节点:

 这里我们只需要把头指针给释放掉就行。

第二种情况就是头指针后不止一个节点,这里我们如果只使用一个指针对其进行删除操作,那么我们只需要找到尾节点的头一个节点,然后通过next释放掉尾节点,将尾节next置空,具体如下

还有一种我们可以使用两个指针,一个保存尾节点,一个保存尾节点的头一个节点 ,然后释放尾节点,对尾节点头一个节点的next进行置空,具体如下:

 

这里代码就不给大家演示,大家可以自己写一写看。

测试如下:

void text3()
{
	SListNode* s = NULL;
	SListPushBack(&s, 1);
	SListPushBack(&s, 2);
	SListPushBack(&s, 3);
	SListPushBack(&s, 4);
	SListPrint(s);
	SListPopBack(&s);
	SListPopBack(&s);
	SListPrint(s);
}
int main()
{
	text3();
	return 0;
}

结果演示:

 

2.7 单链表的头插

void SListPushFront(SListNode** pplist, SLTDateType x)
{   
	assert(pplist);
	SListNode* newnode = BuySListNode(x);
	newnode->next = *pplist;
	*pplist = newnode;
}

对于头插这里我们进行的操作就比较简单了,首先向内存创建一个节点,然后新节点的next指向头节点,然后把头指针指向新节点,具体如下:

第一步:

 第二步:

 测试如下:

void text2()
{
  SListNode* s = NULL;
  SListPushFront(&s, 1);
  SListPushFront(&s, 2);
  SListPushFront(&s, 3);
  SListPushFront(&s, 4);
  SListPrint(s);
}
int main()
{
	text2();
	return 0;
}

结果如下:

 2.8 单链表头删

void SListPopFront(SListNode** pplist)
{
	assert(pplist);
	assert(*pplist);
	SListNode* first = *pplist;
	*pplist = first->next;
	free(first);
	first = NULL;
}

对于头删,这里我们需要用一个指针记录头节点位置,然后把头指针指向头节点的下一个节点,然后把头节点释放了即可,具体如下:

 这样我们就完成了头删。

测试如下:

void text2()
{
  SListNode* s = NULL;
  SListPushFront(&s, 1);
  SListPushFront(&s, 2);
  SListPushFront(&s, 3);
  SListPushFront(&s, 4);
  SListPrint(s);
  SListPopFront(&s);
  SListPrint(s);
  SListPopFront(&s);
  SListPrint(s);
}
int main()
{
	text2();
	return 0;
}

结果如下:

 2.9  单链表查找

SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	if (plist == NULL)
	{
		return NULL;
	}
	SListNode* dest=plist;
	while (dest)
	{
		if (dest->data == x)
		{
			return dest;
		}
		dest = dest->next;
	}
	return NULL;
}

这里我们还是以内容查找,思路很简单,就是遍历,找到后返回找到的那个节点,没找到返回空指针即可。由于这里我们不好测试,我们配合下面的在某个位置插入给大家演示。

2.10  单链表在pos位置之后插入x

void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	SListNode* newnode=BuySListNode(x);
	assert(pos);
	newnode->next = pos->next;
	pos->next = newnode;
	
}

这里我们需要配合查找找到需要插入的元素,这里的逻辑也比较简单,我给大家用演示一下

 测试如下:

void text1()
{
	SListNode* s = NULL;
	SListPushBack(&s, 1);
	SListPushBack(&s, 2);
	SListPushBack(&s, 3);
	SListPushBack(&s, 4);
	SListPrint(s);
	SLTDateType x = 0;
	scanf("%d", &x);
	SListNode* pos = SListFind(s, x);
	SListInsertAfter(pos, 6);
	SListPrint(s);
}
int main()
{
	text1();
	return 0;
}

结果如下:

 2.11 单链表删除pos位置之后的值

void SListEraseAfter(SListNode* pos)
{
	if (pos == NULL&&pos->next)
	{
		return;
	}
	else
	{
		SListNode* find;
		find = pos->next;
		pos->next = pos->next->next;
		free(find);
		find = NULL;
	}
}

首先判断该位置和下一个位置是不是空,为空直接退出程序,这里我们用指针find记录pos下一个节点,然后将pos的next指向下下个节点即可。

具体如下:

 测试如下:

void text1()
{
	SListNode* s = NULL;
	SListPushBack(&s, 1);
	SListPushBack(&s, 2);
	SListPushBack(&s, 3);
	SListPushBack(&s, 4);
	SListPrint(s);
	SLTDateType x = 0;
	scanf("%d", &x);
	SListNode* pos = SListFind(s, x);
	SListInsertAfter(pos, 6);
	SListPrint(s);
	SListNode*pos1= SListFind(s, x);
	SListEraseAfter(pos1);
	SListPrint(s);
}
int main()
{
	text1();
	return 0;
}

结果如下:

2.12  单链表的销毁

void SListDestroy(SListNode* plist)
{
	SListNode* p = plist;
	SListNode* q;
	while (p)
	{
		q = p->next;
		free(p);
		p = q;
	}
	plist = NULL;
}

对于销毁操作,我们需要使用双指针,用q记录要销毁节点的下一个,p用来销毁指针,然后不断对链表进行遍历。

总代码

Slist.c

#include"slist.h"
SListNode* BuySListNode(SLTDateType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
void SListPrint(SListNode* plist)
{
	SListNode* cur = plist;
	while (cur)
	{
		printf("%d-> ", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
void SListPushBack(SListNode** pplist, SLTDateType x)
{
	assert(pplist);
	SListNode*newnode= BuySListNode(x);
	if (*pplist == NULL)
	{
		*pplist = newnode;
	}
	else
	{
		SListNode* tail=*pplist;
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}
void SListPushFront(SListNode** pplist, SLTDateType x)
{   
	assert(pplist);
	SListNode* newnode = BuySListNode(x);
	newnode->next = *pplist;
	*pplist = newnode;
}
void SListPopBack(SListNode** pplist)
{
	assert(pplist);
	assert(*pplist);
	if ((*pplist)->next==NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		SListNode* tail = *pplist;
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}
void SListPopFront(SListNode** pplist)
{
	assert(pplist);
	assert(*pplist);
	SListNode* first = *pplist;
	*pplist = first->next;
	free(first);
	first = NULL;
}
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	if (plist == NULL)
	{
		return NULL;
	}
	SListNode* dest=plist;
	while (dest)
	{
		if (dest->data == x)
		{
			return dest;
		}
		dest = dest->next;
	}
	return NULL;
}
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	SListNode* newnode=BuySListNode(x);
	assert(pos);
	newnode->next = pos->next;  
	pos->next = newnode;
	
}
void SListEraseAfter(SListNode* pos)
{
	if (pos == NULL&&pos->next)
	{
		return;
	}
	else
	{
		SListNode* find;
		find = pos->next;
		pos->next = pos->next->next;
		free(find);
		find = NULL;
	}
}
void SListDestroy(SListNode* plist)
{
	SListNode* p = plist;
	SListNode* q;
	while (p)
	{
		q = p->next;
		free(p);
		p = q;
	}
	plist = NULL;
}

Slist.h

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);
// 单链表的销毁
void SListDestroy(SListNode* plist);

Text.c

#include"slist.h"

void text3()
{
	SListNode* s = NULL;
	SListPushBack(&s, 1);
	SListPushBack(&s, 2);
	SListPushBack(&s, 3);
	SListPushBack(&s, 4);
	SListPrint(s);
	SListPopBack(&s);
	SListPopBack(&s);
	SListPrint(s);
}
void text2()
{
  SListNode* s = NULL;
  SListPushFront(&s, 1);
  SListPushFront(&s, 2);
  SListPushFront(&s, 3);
  SListPushFront(&s, 4);
  SListPrint(s);
  SListPopFront(&s);
  SListPrint(s);
  SListPopFront(&s);
  SListPrint(s);
}
void text1()
{
	SListNode* s = NULL;
	SListPushBack(&s, 1);
	SListPushBack(&s, 2);
	SListPushBack(&s, 3);
	SListPushBack(&s, 4);
	SListPrint(s);
	SLTDateType x = 0;
	scanf("%d", &x);
	SListNode* pos = SListFind(s, x);
	SListInsertAfter(pos, 6);
	SListPrint(s);
	SListNode*pos1= SListFind(s, x);
	SListEraseAfter(pos1);
	SListPrint(s);
}
int main()
{
	text1();
	return 0;
}

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

半只牛马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值