数据结构-链表(C语言+动态内存管理)

目录

前言

代码分析

数据类型

动态申请一个结点

单链表打印

单链表的尾插

单链表的头插

​单链表的尾删

单链表的头删

单链表查找

单链表在pos位置插入x

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

单链表删除pos位置的值

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

单链表的销毁

完整代码

slist.h

slist.c

test.c



前言

编译环境:VS2022

编程语言:C

内存管理(动态/静态):动态内存管理

是否带哨兵位:否

链表类型:单链表


代码分析

数据类型

typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;    //数据域
	struct SListNode* next;    //指针域
}SListNode;

动态申请一个结点

//申请后返回该结点的地址,所以函数的类型为结构体指针类型 SListNode*
//参数为结构体成员 数据域的类型 SLTDateType,作为新结点的数据域
SListNode* BuySListNode(SLTDateType x)
{
	//动态内存开辟
    SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
    //检查申请结点是否成功
	if (newnode == NULL)
	{
		perror("malloc");
		return;
	}
    
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

单链表打印

void SListPrint(SListNode* plist)
{
	//此处需要进行断言吗?
    //无需断言,空的链表也是可以打印的

    //拷贝头指针,防止我们需要再次用到头指针的时候找不到了
	SListNode* cur = plist;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
        //每输出一个结点的数据域,指针就往后走一步
		cur = cur->next;
	}
	printf("NULL\n");
}

单链表的尾插

1.函数第一个参数传一级指针还是二级指针?为什么?
2.断言 pplist 还是 *pplist ?为什么?
3.找尾结点是判断 tail==NULL 还是 tail->next==NULL

void SListPushBack(SListNode** pplist, SLTDateType x)
{
	//断言
    assert(pplist);
	//创建新节点
	SListNode* newnode = BuySListNode(x);
    //判断链表是否为空
	if ((*pplist)->next == NULL)
	{
		*pplist = newnode;
	}
	else
	{
		SListNode* tail = *pplist;
        //找尾结点
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

//
1.二级指针,要想改变结构体指针,就需要传结构体指针的地址
2.断言 pplist 因为即使链表为空,指向链表的头指针也不为空
如果断言 *pplist 会导致链表为空的时候无法进行尾插
3.找尾结点判断 tail->next==NULL 如果tail已经为空了,我们就没有办法找到指向tail的那个结点了

单链表的头插

void SListPushFront(SListNode** pplist, SLTDateType x)
{
	assert(pplist);//链表为空,pplist也不为空,pplist是头指针的地址
	//assert(*pplist);//不能断言,链表为空,也需要能插入
	SListNode* newnode = BuySListNode(x);

	newnode->next = *pplist;
	*pplist = newnode;
}

注意:
pplist 指向头结点的地址(表示头结点的地址的地址)
*pplist 指向头结点(表示头结点的地址)

单链表的尾删

void SListPopBack(SListNode** pplist)
{
	//需要断言吗?可以断言吗?
    assert(pplist);
    //需要断言吗?可以断言吗?
	assert(*pplist);
	//链表为空
	if ((*pplist)->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	//链表非空
	else
	{
		//找尾
        SListNode* tail = *pplist;
        //while循环的结束条件是 (tail->next) 还是 (tail->next->next) ?
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

必须断言
assert(pplist);    //即使链表为空,pplist也不为空
可以断言(也可以不断言,如果不断言就需要用if语句判断链表是否为空)
assert(*pplist);    //链表为空,无需进行删除

while循环的结束条件必须是 (tail->next->next)
如果循环结束为 (tail->next),循环结束时 tail->next = NULL
虽然我们可以成功删除尾结点,但是上一结点的next又指向了哪里呢?
所以我们需要找到尾结点的上一结点,释放尾结点,并将尾结点的上一结点的next置为NULL

单链表的头删

void SListPopFront(SListNode** pplist)
{
	//需要断言吗?
    assert(pplist);//必须断言
    //需要断言吗?
	assert(*pplist);//可以断言
    //
	SListNode* del = *pplist;
	*pplist = (*pplist)->next;
	free(del);
}

单链表查找

我们可以根据需要返回的数据类型调整函数类型
此处返回一个结点的地址,所以函数类型为 SListNode*

此处传参为一级指针,没有必要使用二级指针(虽然也可以使用)

SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	//assert(plist);无需断言
	SListNode* cur = plist;
    
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

代码并不是很难懂,就不作图说明了

        

单链表在pos位置插入x

void SListInsert(SListNode** pplist, SListNode* pos, SLTDateType x)
{
	assert(pplist);
	assert(pos);//判断插入的位置是否非法
    //头插
	if (*pplist == pos)
	{
		SListPushFront(pplist, x);
	}
	else
	{
		SListNode* prev = *pplist;
        //找到pos的前一个结点
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SListNode* newcode = BuySListNode(x);
		prev->next = newcode;
		newcode->next = pos;
	}
}

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

如果限制参数的个数,我们也可以选择这样的插入方式

void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	assert(pos);
	SListNode* newcode = BuySListNode(x);
	newcode->next = pos->next;
	pos->next = newcode;
    //上述两个语句是否可以交换顺序?
    //不能,如果先执行第二条语句,链表会找不到pos后面的结点了
}

单链表删除pos位置的值

void SListErase(SListNode** pplist, SListNode* pos)
{
	assert(pplist);
	assert(pos);
    //头删
	if (*pplist == pos)
	{
		SListPopFront(pplist);
	}
	else
	{
		SListNode* prev = *pplist;
		while (prev->next != NULL)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

此处删除操作与插入操作类似,都是先查找,再进行 删除/插入 

        

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

void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	assert(pos->next);
	SListNode* next = pos->next;
	pos->next = next->next;
	free(next);
}

此处删除操作与插入操作类似,都是借用一个额外的结点直接对pos位置的结点进行处理

        

单链表的销毁

两种单链表的销毁方式都可以

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

void SListDestroy(SListNode* plist)
{
	SListNode* cur = plist;
    //遍历单链表
	while (cur)
	{
		SListNode* next = cur->next;
		free(cur);
		cur = next;
	}
    //记得置空头结点
	plist = NULL;
}


完整代码

slist.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// slist.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 SListInsert(SListNode** pplist, SListNode* pos, SLTDateType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置的值
void SListErase(SListNode** pplist, SListNode* pos);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);
// 单链表的销毁
//void SListDestroy(SListNode** pplist);
void SListDestroy(SListNode* plist);

slist.c

#include "slist.h"

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

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

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

void SListPushFront(SListNode** pplist, SLTDateType x)
{
	assert(pplist);//链表为空,pplist也不为空,pplist是头指针的地址
	//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* del = *pplist;
	*pplist = (*pplist)->next;
	free(del);
}

SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	//assert(plist);无需断言
	SListNode* cur = plist;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

void SListInsert(SListNode** pplist, SListNode* pos, SLTDateType x)
{
	assert(pplist);
	assert(pos);
	if (*pplist == pos)
	{
		SListPushFront(pplist, x);
	}
	else
	{
		SListNode* prev = *pplist;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SListNode* newcode = BuySListNode(x);
		prev->next = newcode;
		newcode->next = pos;
	}
}

//在pos之前插入
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	assert(pos);
	SListNode* newcode = BuySListNode(x);
	newcode->next = pos->next;
	pos->next = newcode;
}

void SListErase(SListNode** pplist, SListNode* pos)
{
	assert(pplist);
	assert(pos);
	if (*pplist == pos)
	{
		SListPopFront(pplist);
	}
	else
	{
		SListNode* prev = *pplist;
		while (prev->next != NULL)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	assert(pos->next);
	SListNode* next = pos->next;
	pos->next = next->next;
	free(next);
}

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

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

test.c

#include "slist.h"

void test1()
{
	SListNode* plist = NULL;
	SListPushFront(&plist, 1);
	SListPushFront(&plist, 2);
	SListPushFront(&plist, 3);
	SListPushBack(&plist, 4);
	SListPushBack(&plist, 5);
	SListPushBack(&plist, 6);
	SListFind(plist, 6);
	SListPrint(plist);

	SListPopFront(&plist);
	SListPopBack(&plist);
	SListFind(plist, 6);
	SListPrint(plist);

	SListNode* pos;
	pos = SListFind(plist, 2);
	if (pos)
	{
		//SListInsert(&plist, pos, 10);
		SListInsertAfter(pos, 20);
	}
	SListPrint(plist);
	pos = SListFind(plist, 4);
	if (pos)
	{
		//SListErase(&plist, pos);
		SListEraseAfter(pos);
	}
	SListPrint(plist);

	SListDestroy(plist);
	//SListPrint(plist);
}

int main()
{
	test1();
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

φ冰霰ξ

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

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

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

打赏作者

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

抵扣说明:

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

余额充值