单链表的实现

初识链表

目录

初识链表

1.链表的概念及结构

2.单链表

打印

后插

前插

动态申请节点

前删

后删

查找

1.链表的概念及结构

概念:链表是一种 物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
例:
typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

定义这样一个结构体,就可以完成链表的连接:

 从定义和例子中间我们不难总结出:

1、链式结构在逻辑上是连续的,但是在物理上是不连续的,所谓的链,不过是我们构建的理论模型

2、现实中的结点一般都是从堆上申请出来的

3、从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续

2.单链表

链表的种类是很多的,我们先来实现最不复杂的单链表的这几个功能吧

//打印
void SListPrint(SLTNode* phead);
//尾插
void SListPushBack(SLTNode** pphead, SLTDataType x);
//头插
void SListPushFront(SLTNode* pphead, SLTDataType x);
//动态申请一个节点
SLTNode* BuySListNode(SLTDataType x);
//尾删
void SListPopBack(SLTNode** pphead);
//头删
void SListPopFront(SLTNode** pphead);
//查找
void SListFind(SLTNode* phead, SLTDataType x);
//任意位置的插入和删除
void SListInsert(SLTNode** pphead,SLTNode * pos, SLTDataType x);
void SListErase(SLTNode** pphead, SLTNode* pos);
typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

首先我们来个简单的:

打印

我们知道,打印数组是从0下表打印到size下标,那么打印链表呢?我们怎么找到它的各个节点呢?

void SListPrint(SLTNode* phead)
{
	SLTNode* cur =phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;//相当于数组里面的i++
	}
	printf("NULL\n");
}

思路先找到第一个结构体的地址,进而找到首结构体中的next指针,进而找到next指针指向的结构体中的next指针...每次进入一个结构体都将其中的data打印出来,这就是链表的遍历过程的实现

我们再看个难度高的:

后插

我们知道在数组中后插是要考虑扩容和越界的,但是链表和数组不同,他要考虑的是指针所带来的的问题

思路:我们先把要插入的数据x放到一个动态申请的节点里,然后让这个节点的next指向NULL(因为是在最后插入)随后我们找到原链表中的最后一个节点,把这个节点的next指向我们新申请的节点就OK了。

注意:如果这个链表为空,那么首个节点的地址就是个空指针,这个时候我们找不到所谓的最后一个节点,所以这种情况时我们直接把我们申请的节点作为首元素(*phead=STLNode *newnode)

于是,我们有:

void SListPushBack(SLTNode* phead, SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	assert(newnode);
	newnode->data = x;
	newnode->next = NULL;
	if (phead == NULL)
	{
		phead = newnode;
	}
	else
	{     //找尾结点
	     SLTNode* tail = phead;
         while (tail != NULL)
	     {
		     tail = tail->next;
	     }
	     tail->next = newnode;//让原来的NULL指向newnode
	}
}

但是很遗憾,上面这串代码是纯小丑代码🤡

为什么小丑呢?

因为形参的改变无法让实参改变。而我们尾插到最后有可能要把第一个结构体的地址改变的(若首个结构体的地址为空,那直接吧newnode赋给它)
所以上面这串代码终归只是在自己的函数体内自导自演,没有意义。
 

我们知道,形参的改变不会影响实参,如果想要影响实参,就得传实参的地址!在这里,在链表为空时,改变的是首个节点的地址,因此我们得传:首个节点的地址的地址!

即:

void SListPushBack(SLTNode**pphead, SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//这个函数结束了newnode确实是会销毁的,但是malloc所创建的空间是不会销毁的
	assert(newnode);
	newnode->data = x;
	newnode->next = NULL;
	if (*pphead == NULL)//注意这个特殊情况
	{
		*pphead = newnode;
	}
	else
	{     //找尾结点
		SLTNode* tail = *pphead;
		while (tail != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;//让原来的NULL指向newnode
	}
}//大功告成!

克服了上面的这一难关,那接下来的实现,就没那么可怖了,比如:

前插

思路:我们直接申请个节点,然后让这个节点指向原来链表的首个节点,然后再让这个节点的地址成为链表的地址就好了

注意:因为这里我们同样要修改链表的地址,所以还是得传二级指针

void SListPushFront(SLTNode** pphead, SLTDataType x)
{
	//SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//这个函数结束了newnode确实是会销毁的,但是malloc所创建的空间是不会销毁的
	//assert(newnode);
	//newnode->data = x;
	//newnode->next = NULL;
	//由于只要插入基本都要上面这几步,所以我们直接把这一流程写成一个函数;
	SLTNode* newnode = BuySListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

意外收获:

动态申请节点

SLTNode* BuySListNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	assert(newnode);
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

那删除呢?看看

前删

void SListPopFront(SLTNode** pphead)
{
	assert(*pphead);
	/*if (*pphead == NULL)
		return;*///温柔类型的检查 
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

思路:吧第一个节点释放掉,然后让第二个节点成为第一个节点,简单

那下面这如何呢?

后删

后删时我们要考虑的特殊情况有了:

1.删除了最后一个节点,倒数第二节点的next变成了野指针

2.删完了这个链表就变成了空链表

void SListPopBack(SLTNode** pphead)
{
	assert(*pphead);
	//分两种情况:1.只有一个节点2.多个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
	//SLTNode* tailPrev = NULL;//定义这个指针是为了防止最后一个结构体被free之后上一个next指针成为了野指针
	//SLTNode* tail = *pphead;
	//while (tail->next != NULL)
	//{ 
	//	tailPrev = tail;
	//	tail = tail->next;
	//}
	//free(tail);
	//tailPrev ->next = NULL;
		SLTNode* tail = *pphead;
		while (tail->next->next != NULL)//这一套写法的思路是:找倒数第二个
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

思路:我们把最后一个节点释放,在这一过程中,我们找到倒数第二个节点的next指针,使其指向NULL即可(或者直接找倒数第二个节点,反正这个节点有指向最后一个节点的指针)另外当删完之后这个链表啥都不剩了的话,我们释放完了它最后一个节点之后,一定要记得吧*phead置为NULL;

最后是

查找

void SListFind(SLTNode* phead, SLTDataType x)//从前面我们可知:查找、打印这种函数不会改变链表就传一级指针
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
			return cur;

		cur = cur->next;
	}
	return NULL;
}//把找到的指针->data修改一下就是修改功能的实现了,即查找的附带功能就是修改。

思路:从前往后找,找到了所要找的data就停。

另外,这个查找函数也可以附带实现修改:把找到的的指针->data修改一下

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值