【数据结构】模拟实现无头单向非循环链表(内含对指针的详细理解)

1.链表概念
链表在逻辑上是线性的,但是在物理存储的过程中,不一定是线性的,它通过指针实现连续存储数据元素。
结构:
在这里插入图片描述
2.代码展示

SList.h

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

// 1、无头+单向+非循环链表增删查改实现
typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;


// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
//单链表的销毁
void SListDestory(SListNode** pplist);
// 单链表打印
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);

SList.c

#include"SList.h"


// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{
	SListNode* psl = (SListNode*)malloc(sizeof(SListNode));
	psl->data = x;
	psl->next = NULL;
	return psl;
}

// 单链表打印
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)
{	
	//1.空链表
	//2.非空链表
	SListNode* psl = (SListNode*)malloc(sizeof(SListNode));
	psl->data = x;
	psl->next = NULL;
	if (*pplist == NULL)
	{
		*pplist = psl;
	}
	else
	{
		SListNode* tail = *pplist;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = psl;
	}
}
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
	//先存头指针,再更新头部。
	SListNode* psl = (SListNode*)malloc(sizeof(SListNode));
	psl->data = x;
	psl->next = NULL;
	psl->next = *pplist;
	*pplist = psl;
	
}
// 单链表的尾删
void SListPopBack(SListNode** pplist)
{	//1.空链表
	//2.单个链表
	//3.多个链表
	if (*pplist == NULL)
	{
		return;
	}
	if ((*pplist)->next == NULL)
	{
		free(*pplist);//一定要先释放再置NULL,防止野指针问题。
		*pplist = NULL;
	}

	else
	{
		SListNode* prev = NULL;
		SListNode* tail = *pplist;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		prev->next = NULL;
	}
}
// 单链表头删
void SListPopFront(SListNode** pplist)
{
	//1.空链表
	//2.多个链表
	if (*pplist == NULL)
	{
		return;
	}
	SListNode* cur = (*pplist)->next;
	free(*pplist); //这里也要注意释放空间
	*pplist = cur;

}
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
		while (plist !=NULL)
		{
			if (plist->data == x)
			{
				return plist;
			}
			plist = plist->next;
		}
		return plist;
}

// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	SListNode* cur = (SListNode*)malloc(sizeof(SListNode));
	cur->data = x;
	cur->next = NULL;
	cur->next = pos->next;//一定要先把后面的指针存起来,再改变指向。
	pos->next = cur;
}
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
	if (pos == NULL || pos->next == NULL)
	{
		return;
	}
	
	SListNode* next = pos->next->next;
	free(pos->next);
	pos->next = next;
}


//单链表的销毁
void  SListDestory(SListNode** pplist)
{
	if (*pplist == NULL)
	{
		return ;
	}
	while ((*pplist)->next != NULL)
	{
		SListNode* cur = (*pplist)->next;
		free(*pplist);
		*pplist = cur;
	}
	free(*pplist);
	*pplist = NULL;//释放完成要置空,不然会存在野指针的问题
}

void test()
{

	SListNode* psl = NULL;/* BuySListNode(1);*/
	SListPushBack(&psl, 4);
	SListPushBack(&psl, 5);
	SListPushBack(&psl, 6);
	SListPushBack(&psl, 7);
	SListPrint(psl);
	SListPushFront(&psl, 3);
	SListPushFront(&psl, 2);
	SListPushFront(&psl, 1);
	SListPrint(psl);
	SListPopBack(&psl);
	SListPrint(psl);
	SListPopFront(&psl);
	SListPrint(psl);
	SListNode* ret=SListFind(psl, 2);
	SListInsertAfter(ret, 0);
	SListPrint(psl);
	SListEraseAfter(ret);
	SListPrint(psl);
	SListDestory(&psl);
	SListPrint(psl);
	return;
}

test.c

#include"SList.h"
int main()
{

	test();
	system("pause");
	return 0;
}

3.结果展示
在这里插入图片描述
4.心得体会
首先需要注意的是,在对链表实现增删查改的过程中,需要传递二级指针,其次要注意删除某个数时,要先将它的下一个指针保存下来,然后再释放当前空间,然后在连接两个指针。最后就要考虑空链表,单个链表,多个链表这种边界情况。
5.存在问题
为什么在pos前后删除的时候,传递指针就可以改变链表的值?而头插,头删,尾插,尾删时就要传递二级指针才能改变呢?一级指针为什么头删的时候也不行呢?
在这里插入图片描述
同样的是改变了链表的结构,查看地址也没找出答案。

找到答案了!!!

这个是二级指针。
在这里插入图片描述
这个是一级指针解法(错误)
在这里插入图片描述
通过对比,我们来解释一下两个过程,首先来说直接传递指针来进行头删除,头删的时候,我们通过查看ppsl psl &ppsl &psl,发现只是将指针重新临时拷贝到一个空间,然后对这个指针进行操作,但是执行完成返回的时候,还是返回了之前指针的地址。临时变量消失,原来的指针还是指向了原位置,我执行的过程中释放了原来指针指向的空间,所以会出现指向随机值,我将空间不释放,得到下面的图,也确实证明了我们想法。

再来看看二级指针,二级指针,我们通过查看地址发现,这个是实实在在的传递了地址过去,回来接收的也是原地址,所以这个删除时,是对指针的地址进行了操作,完成了数据的改变。
在这里插入图片描述
下来我来查看一下Pos为什么能实现插入。
在这里插入图片描述
通过地址查看,我们可以看到,即使是创建了一个临时的变量,去存储当前的指针的值,我们要改变的是,当前位置的下一个所以对临时变量指向的地址的下一个地址,和实际原来ret指向的空间是一个空间,所以这里能完成数据的更新,即使是临时变量释放之后。

在这里插入图片描述
要改变数据一定要去操作指针,当然操作指针的位置一定要和临时变量指向的地址相同,否则就要传二级指针,去达到访问同一块的地址的目的,不然临时变量释放之后,原来的指针指向还没有改变。

发布了79 篇原创文章 · 获赞 6 · 访问量 3777

猜你喜欢

转载自blog.csdn.net/qq_41152046/article/details/105073510